WPF: ¿Cuadrícula con margen de columna / fila / relleno?

137

¿Es fácilmente posible especificar un margen y / o relleno para filas o columnas en una cuadrícula WPF?

Por supuesto, podría agregar columnas adicionales para espaciar las cosas, pero esto parece un trabajo para rellenar / márgenes (dará XAML mucho más simple). ¿Alguien ha derivado de la cuadrícula estándar para agregar esta funcionalidad?

Brad Leach
fuente
44
Un ejemplo útil que puede encontrar aquí: codeproject.com/Articles/107468/WPF-Padded-Grid
peter70
2
Un poco desconcertado que esto no es parte de las características basales de la cuadrícula ...
uceumern
A partir de hoy, demostrado por 10 años de respuestas, la verdad es que no es posible fácilmente, y lo mejor que se puede hacer (para evitar un trabajo adicional propenso a errores cada vez que se usa una celda) es derivar Grid (como lo sugirió anteriormente @ peter70 ) para agregar la propiedad de dependencia de relleno de celda adecuada que controlará la propiedad Margen del elemento secundario de celda. Esta no es una tarea larga, y entonces tienes un control reutilizable. Comentario lateral ... Grid es realmente un control mal diseñado.
minutos

Respuestas:

10

Puede escribir su propia GridWithMarginclase, heredada Gridy anular el ArrangeOverridemétodo para aplicar los márgenes.

Thomas Levesque
fuente
32
No entiendo por qué la gente te da el visto bueno porque está lejos de ser tan fácil como crees / describes aquí. Solo trata de hacerlo 30 minutos y verás rápidamente que tu respuesta no es aplicable. También piense en la extensión de filas y columnas
Eric Ouellet
89

RowDefinitiony ColumnDefinitionson de tipo ContentElement, y Margines estrictamente una FrameworkElementpropiedad. Entonces a su pregunta, "¿es fácilmente posible" la respuesta es un no definitivo. Y no, no he visto ningún panel de diseño que demuestre este tipo de funcionalidad.

Puede agregar filas o columnas adicionales como sugirió. Pero también puede establecer márgenes en un Gridelemento en sí mismo, o cualquier cosa que vaya dentro de un Grid, así que esa es su mejor solución por ahora.

Charlie
fuente
OP no está intentando establecer un margen en RowDefinition o ColumnDefinition. Está intentando establecer un margen en los elementos visuales secundarios de la cuadrícula, que derivan de FrameworkElement.
15ee8f99-57ff-4f92-890c-b56153
58

Use un Bordercontrol fuera del control de celda y defina el relleno para eso:

    <Grid>
        <Grid.Resources >
            <Style TargetType="Border" >
                <Setter Property="Padding" Value="5,5,5,5" />
            </Style>
        </Grid.Resources>

        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <Border Grid.Row="0" Grid.Column="0">
            <YourGridControls/>
        </Border>
        <Border Grid.Row="1" Grid.Column="0">
            <YourGridControls/>
        </Border>

    </Grid>


Fuente:

samad
fuente
2
@RichardEverett Compruebe la máquina de regreso: enlace actualizado en respuesta.
CJBS
Me gusta más esta respuesta. Proporciona una solución a la pregunta original y es sencillo. ¡Gracias!
Tobias
Esto es fácil y práctico
aggsol
19

Podrías usar algo como esto:

<Style TargetType="{x:Type DataGridCell}">
  <Setter Property="Padding" Value="4" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type DataGridCell}">
        <Border Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
          <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>

O si no necesita los TemplateBindings:

<Style TargetType="{x:Type DataGridCell}">
   <Setter Property="Template">
      <Setter.Value>
          <ControlTemplate TargetType="{x:Type DataGridCell}">
              <Border Padding="4">
                  <ContentPresenter />
              </Border>
          </ControlTemplate>
      </Setter.Value>
  </Setter>
</Style>
JayGee
fuente
28
Gracias JayGee, sin embargo, esta solución es para DataGrid, no para el control Grid estándar.
Brad Leach el
Al hacer clic en el espacio de relleno ya no se selecciona la fila.
jor
9

Pensé que agregaría mi propia solución porque aún nadie mencionó esto. En lugar de diseñar un UserControl basado en Grid, puede apuntar a controles contenidos en grid con una declaración de estilo. Se encarga de agregar relleno / margen a todos los elementos sin tener que definir para cada uno, lo cual es engorroso y requiere mucho trabajo. Por ejemplo, si su Grid no contiene más que TextBlocks, puede hacer esto:

<Style TargetType="{x:Type TextBlock}">
    <Setter Property="Margin" Value="10"/>
</Style>

Que es como el equivalente de "relleno de celda".

James M
fuente
¿esto gotea? por ejemplo, si tiene un panel de pila en una fila de cuadrícula, ¿heredan esta propiedad los hijos de bloque de texto del panel de pila?
Maslow
No estoy seguro si pasa a los niños inmediatos, pero puede averiguarlo con una prueba simple.
James M
2
@Maslow La respuesta es absolutamente 'Sí', pero su redacción es un poco engañosa. No existe una "herencia" de la Marginpropiedad, es que cualquiera Stylede ellos ResourceDictionaryse aplicará a cada elemento de TargetTypetodas partes dentro del alcance completo del elemento propietario de ese diccionario. Entonces es lo Styleque gotea, no la propiedad.
Glenn Slayden
8

Esto no es terriblemente difícil. No puedo decir lo difícil que fue en 2009 cuando se hizo la pregunta, pero eso fue entonces.

Tenga en cuenta que si establece explícitamente un margen directamente en un elemento secundario de la cuadrícula cuando usa esta solución, ese margen aparecerá en el diseñador pero no en el tiempo de ejecución.

Esta propiedad se puede aplicar a Grid, StackPanel, WrapPanel, UniformGrid o cualquier otro descendiente del Panel. Afecta a los niños inmediatos. Se presume que los niños querrán administrar el diseño de sus propios contenidos.

PanelExt.cs

public static class PanelExt
{
    public static Thickness? GetChildMargin(Panel obj)
    {
        return (Thickness?)obj.GetValue(ChildMarginProperty);
    }

    public static void SetChildMargin(Panel obj, Thickness? value)
    {
        obj.SetValue(ChildMarginProperty, value);
    }

    /// <summary>
    /// Apply a fixed margin to all direct children of the Panel, overriding all other margins.
    /// Panel descendants include Grid, StackPanel, WrapPanel, and UniformGrid
    /// </summary>
    public static readonly DependencyProperty ChildMarginProperty =
        DependencyProperty.RegisterAttached("ChildMargin", typeof(Thickness?), typeof(PanelExt),
            new PropertyMetadata(null, ChildMargin_PropertyChanged));

    private static void ChildMargin_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = d as Panel;

        target.Loaded += (s, e2) => ApplyChildMargin(target, (Thickness?)e.NewValue);

        ApplyChildMargin(target, (Thickness?)e.NewValue);
    }

    public static void ApplyChildMargin(Panel panel, Thickness? margin)
    {
        int count = VisualTreeHelper.GetChildrenCount(panel);

        object value = margin.HasValue ? margin.Value : DependencyProperty.UnsetValue;

        for (var i = 0; i < count; ++i)
        {
            var child = VisualTreeHelper.GetChild(panel, i) as FrameworkElement;

            if (child != null)
            {
                child.SetValue(FrameworkElement.MarginProperty, value);
            }
        }
    }
}

Manifestación:

MainWindow.xaml

<Grid
    local:PanelExt.ChildMargin="2"
    x:Name="MainGrid"
    >
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Rectangle Width="100" Height="40" Fill="Red" Grid.Row="0" Grid.Column="0" />
    <Rectangle Width="100" Height="40" Fill="Green" Grid.Row="1" Grid.Column="0" />
    <Rectangle Width="100" Height="40" Fill="Blue" Grid.Row="1" Grid.Column="1" />

    <Button Grid.Row="2" Grid.Column="0" Click="NoMarginClick">No Margin</Button>
    <Button Grid.Row="2" Grid.Column="1" Click="BigMarginClick">Big Margin</Button>
    <ComboBox Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" />
</Grid>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void NoMarginClick(object sender, RoutedEventArgs e)
    {
        //  In real life, if we wanted to change PanelExt.ChildMargin at runtime, we 
        //  would prefer to bind it to something, probably a dependency property of 
        //  the view. But this will do for a demonstration. 
        PanelExt.SetChildMargin(MainGrid, null);
    }
    private void BigMarginClick(object sender, RoutedEventArgs e)
    {
        PanelExt.SetChildMargin(MainGrid, new Thickness(20));
    }
}

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

15ee8f99-57ff-4f92-890c-b56153
fuente
¿Sabes por qué tu respuesta es rechazada? Parece una solución funcional al problema de OP
sistema del
44
@thesystem Creo que alguien se molestó por algo. No es un gran trato. Pero tal vez hay un error que me perdí. Sucede.
15ee8f99-57ff-4f92-890c-b56153
¿Tienes dw porque requiere una buildo runantes de que se pueda mostrar la vista previa en vivo? Buena y simple. 1 de ventaja.
Meow Cat 2012
3

Me encontré con este problema mientras desarrollaba algún software recientemente y se me ocurrió preguntar ¿POR QUÉ? ¿Por qué han hecho esto? La respuesta estaba justo frente a mí. Una fila de datos es un objeto, por lo que si mantenemos la orientación del objeto, entonces el diseño de una fila en particular debe separarse (supongamos que necesita reutilizar la visualización de la fila más adelante en el futuro). Entonces comencé a usar paneles de pila de datos y controles personalizados para la mayoría de las pantallas de datos. Las listas han aparecido ocasionalmente, pero la cuadrícula se ha utilizado principalmente para la organización de la página principal (encabezado, área de menú, área de contenido, otras áreas). Sus objetos personalizados pueden administrar fácilmente cualquier requisito de espacio para cada fila dentro del panel de pila o la cuadrícula (una sola celda de cuadrícula puede contener todo el objeto de la fila. Esto también tiene el beneficio adicional de reaccionar adecuadamente a los cambios de orientación,

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
  </Grid.RowDefinitions>

  <custom:MyRowObject Style="YourStyleHereOrGeneralSetter" Grid.Row="0" />
  <custom:MyRowObject Style="YourStyleHere" Grid.Row="1" />
</Grid>

o

<StackPanel>
  <custom:MyRowObject Style="YourStyleHere" Grid.Row="0" />
  <custom:MyRowObject Style="YourStyleHere" Grid.Row="1" />
</StackPanel>

Sus controles personalizados también heredarán el DataContext si está utilizando el enlace de datos ... mi beneficio favorito personal de este enfoque.

Matt Meikle
fuente
Lo que ha intentado hacer aquí es crear una versión cruda del ListViewcontrol WPF en GridViewmodo, pero sin prever que todos los "RowObjects" compartan el mismo ancho de columna. En realidad, ya no tiene mucho que ver con una cuadrícula.
Glenn Slayden
3

en uwp (Windows10FallCreatorsUpdate versión y superior)

<Grid RowSpacing="3" ColumnSpacing="3">
Kursat Turkay
fuente
Esto también se aplica con Xamarin.Forms.
James M
3

Editado:

Para dar margen a cualquier control, puede ajustar el control con un borde como este

<!--...-->
    <Border Padding="10">
            <AnyControl>
<!--...-->
Juan Pablo
fuente
El problema de OP no es tener un margen alrededor de la cuadrícula, sino un margen alrededor de las celdas de la cuadrícula (o como realmente se preguntó alrededor de las filas y columnas, más tarde contradicho al comentar " agregar las columnas / filas adicionales es exactamente lo que estaba tratando de evitar ") .
minutos
2

Lo hice ahora con una de mis cuadrículas.

  • Primero aplique el mismo margen a cada elemento dentro de la cuadrícula. Puedes hacer esto manualmente, usando estilos, o lo que quieras. Digamos que desea un espacio horizontal de 6px y un espacio vertical de 2px. Luego agrega márgenes de "3px 1px" a cada hijo de la cuadrícula.
  • Luego, elimine los márgenes creados alrededor de la cuadrícula (si desea alinear los bordes de los controles dentro de la cuadrícula en la misma posición de la cuadrícula). Haga esto configurando un margen de "-3px -1px" a la cuadrícula. De esa manera, otros controles fuera de la cuadrícula se alinearán con los controles externos dentro de la cuadrícula.
isierra
fuente
Aplicar el mismo margen a cada elemento dentro de la cuadrícula parece ser la forma más fácil.
Envil
1

Una posibilidad sería agregar filas y columnas de ancho fijo para que actúen como el relleno / margen que está buscando.

También puede considerar que está limitado por el tamaño de su contenedor y que una cuadrícula será tan grande como el elemento contenedor o su ancho y alto especificados. Simplemente puede usar columnas y filas sin ancho o alto establecido. De esa manera, se separan por defecto para dividir el espacio total dentro de la cuadrícula. Entonces sería una cuestión de centrar sus elementos vertical y horizontalmente dentro de su cuadrícula.

Otro método podría ser envolver todos los elementos de la cuadrícula en una cuadrícula fija con una sola fila y columna que tenga un tamaño y margen fijos. Que su cuadrícula contiene cuadros de ancho / alto fijos que contienen sus elementos reales.

Adam Lenda
fuente
1
Gracias Adam, sin embargo, agregar las columnas / filas adicionales es exactamente lo que estaba tratando de evitar. Simplemente quiero reducir mi marcado y poder especificar un margen o relleno ayudaría con esto.
Brad Leach el
Solo tiene que definir las columnas y filas adicionales, no agregarles el marcado. Por ejemplo, si desea agregar N ancho entre columnas para 3 columnas, tendría 5 definiciones de columna. El primero sería el ancho automático, y el siguiente fijo, luego automático, luego fijo, luego automático. Luego, solo asigne las columnas 1, 3 y 5 a elementos de contenido reales. Veo su punto sobre el marcado de columna adicional, pero a menos que tenga cientos de elementos, eso me parece trivial. aunque tu llamada Además, ¿ha intentado usar un panel de pila de paneles de pila con los márgenes establecidos?
Adam Lenda
Para mi caso, agregar una columna "divisor / margen" fue, con mucho, la más limpia y fácil. ¡Gracias!
jchristof
1

Recientemente tuve un problema similar en la cuadrícula de dos columnas, necesitaba un margen en los elementos de la columna derecha solamente. Todos los elementos en ambas columnas eran del tipo TextBlock.

<Grid.Resources>
    <Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource OurLabelStyle}">
        <Style.Triggers>
            <Trigger Property="Grid.Column" Value="1">
                <Setter Property="Margin" Value="20,0" />
            </Trigger>
        </Style.Triggers>
    </Style>
</Grid.Resources>
Miloš Auder
fuente
0

Aunque no puede agregar margen o relleno a una Cuadrícula, puede usar algo como un Marco (o contenedor similar), al que puede aplicarlo.

De esa manera (si muestra u oculta el control en un botón, haga clic en decir), no necesitará agregar margen en cada control que pueda interactuar con él.

Piense en ello como aislando los grupos de controles en unidades, luego aplicando estilo a esas unidades.

ed13
fuente
0

Como se indicó antes, cree una clase GridWithMargins. Aquí está mi ejemplo de código de trabajo

public class GridWithMargins : Grid
{
    public Thickness RowMargin { get; set; } = new Thickness(10, 10, 10, 10);
    protected override Size ArrangeOverride(Size arrangeSize)
    {
        var basesize = base.ArrangeOverride(arrangeSize);

        foreach (UIElement child in InternalChildren)
        {
            var pos = GetPosition(child);
            pos.X += RowMargin.Left;
            pos.Y += RowMargin.Top;

            var actual = child.RenderSize;
            actual.Width -= (RowMargin.Left + RowMargin.Right);
            actual.Height -= (RowMargin.Top + RowMargin.Bottom);
            var rec = new Rect(pos, actual);
            child.Arrange(rec);
        }
        return arrangeSize;
    }

    private Point GetPosition(Visual element)
    {
        var posTransForm = element.TransformToAncestor(this);
        var areaTransForm = posTransForm.Transform(new Point(0, 0));
        return areaTransForm;
    }
}

Uso:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApplication1"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:GridWithMargins ShowGridLines="True">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Rectangle Fill="Red" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
            <Rectangle Fill="Green" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
            <Rectangle Fill="Blue" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
        </local:GridWithMargins>
    </Grid>
</Window>
Paul Baxter
fuente
1
Lanza System.ArgumentException: 'El ancho no debe ser negativo'. actual.Width - = Math.Min (actual.Width, RowMargin.Left + RowMargin.Right); actual.Height - = Math.Min (actual.Height, RowMargin.Top + RowMargin.Bottom);
Jamie Mellway
Con la corrección de Jamie, se ejecuta, pero aún no hace lo que debería. Con el alto de fila y el ancho de columna en Auto, hace lo incorrecto de una manera diferente.
15ee8f99-57ff-4f92-890c-b56153
0

Me sorprende no haber visto esta solución publicada todavía.

Viniendo de la web, los marcos como bootstrap usarán un margen negativo para retirar filas / columnas.

Puede ser un poco detallado (aunque no es tan malo), funciona y los elementos están espaciados y dimensionados de manera uniforme.

En el siguiente ejemplo, uso un StackPanel raíz para demostrar cómo los 3 botones están espaciados uniformemente usando márgenes. Podrías usar otros elementos, solo cambia el x: Type interno del botón a tu elemento.

La idea es simple: use una cuadrícula en el exterior para sacar los márgenes de los elementos de sus límites a la mitad de la cantidad de la cuadrícula interna (usando márgenes negativos), use la cuadrícula interna para espaciar uniformemente los elementos con la cantidad que desee.

Actualización: Algunos comentarios de un usuario dicen que no funciona, aquí hay un video rápido que demuestra: https://youtu.be/rPx2OdtSOYI

ingrese la descripción de la imagen aquí

    <StackPanel>
        <Grid>
            <Grid.Resources>
                <Style TargetType="{x:Type Grid}">
                    <Setter Property="Margin" Value="-5 0"/>
                </Style>
            </Grid.Resources>

            <Grid>
                <Grid.Resources>
                    <Style TargetType="{x:Type Button}">
                        <Setter Property="Margin" Value="10 0"/>
                    </Style>
                </Grid.Resources>

                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <Button Grid.Column="0" Content="Btn 1" />
                <Button Grid.Column="1" Content="Btn 2" />
                <Button Grid.Column="2" Content="Btn 3" />
            </Grid>

        </Grid>

        <TextBlock FontWeight="Bold" Margin="0 10">
            Test
        </TextBlock>
    </StackPanel>
AntonB
fuente
1
Mi error: no noté el estilo implícito de Button. Me distrajo un poco acerca de los márgenes negativos.
15ee8f99-57ff-4f92-890c-b56153
-6

A veces el método simple es el mejor. Simplemente rellena tus cuerdas con espacios. Si solo se trata de unos pocos cuadros de texto, etc., este es, con mucho, el método más simple.

También puede simplemente insertar columnas / filas en blanco con un tamaño fijo. Extremadamente simple y puedes cambiarlo fácilmente.

rollos
fuente
Probablemente porque no es un método muy bueno. El uso de espacios puede ser la "salida fácil", pero en realidad es más un truco. No es preciso, podría (y probablemente lo hará) renderizar de manera diferente y poco confiable en monitores con una escala diferente y no tiene control sobre la cantidad exacta de espacio que crea. Insertar columnas / filas en blanco hará un desastre de su cuadrícula ...
Marque el