WPF: Grade com margem de coluna / linha / preenchimento?

137

É facilmente possível especificar uma margem e / ou preenchimento para linhas ou colunas em uma Grade WPF?

É claro que eu poderia adicionar colunas extras para separar as coisas, mas isso parece ser um trabalho para preenchimento / margens (ele fornecerá XAML muito mais simples). Alguém derivou da grade padrão para adicionar essa funcionalidade?

Brad Leach
fonte
4
Um exemplo útil você pode encontrar aqui: codeproject.com/Articles/107468/WPF-Padded-Grid
peter70
2
Meio intrigado que isso não faz parte de características de linha de base da grade ...
uceumern
Atualmente, demonstrado por 10 anos de respostas, a verdade é que não é possível com facilidade, e o melhor a ser feito (para evitar trabalho adicional propenso a erros toda vez que uma célula é usada) é derivar o Grid (como sugerido anteriormente por @ peter70 ) para adicionar a propriedade de dependência de preenchimento de célula apropriada que controlará a propriedade Margin do filho da célula. Essa não é uma tarefa longa e você terá um controle reutilizável. Comentário lateral ... Grade é realmente um controle mal projetado.
minutos

Respostas:

10

Você pode escrever sua própria GridWithMarginclasse, herdada Gride substituir o ArrangeOverridemétodo para aplicar as margens

Thomas Levesque
fonte
32
Eu não entendo por que as pessoas dão polegares para você, porque está longe de ser tão fácil quanto você pensa / descreve aqui. Apenas tente fazê-lo por 30 minutos e você verá rapidamente que sua resposta não é aplicável. Também pensar sobre linha e coluna spanning
Eric Ouellet
89

RowDefinitione ColumnDefinitionsão do tipo ContentElemente Marginsão estritamente uma FrameworkElementpropriedade. Portanto, para sua pergunta "é facilmente possível", a resposta é um não definitivo. E não, eu não vi nenhum painel de layout que demonstre esse tipo de funcionalidade.

Você pode adicionar linhas ou colunas extras, conforme sugerido. Mas você também pode definir margens em um Gridelemento em si, ou em qualquer coisa que contenha um Grid, portanto essa é sua melhor solução por enquanto.

Charlie
fonte
O OP não está tentando definir uma margem em RowDefinition ou ColumnDefinition. Ele está tentando definir uma margem nos filhos visuais da grade, que derivam de FrameworkElement.
15ee8f99-57ff-4f92-890c-b56153
58

Use um Bordercontrole fora do controle da célula e defina o preenchimento para isso:

    <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>


Fonte:

samad
fonte
2
@RichardEverett Verifique o caminho de volta à máquina: link atualizado em resposta.
CJBS
Eu gosto mais desta resposta. Ele fornece uma solução para a pergunta original e é direto. Obrigado!
Tobias
Isso é fácil e prático
aggsol 10/09/19
19

Você poderia usar algo como isto:

<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>

Ou se você não precisar do 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
fonte
28
Obrigado JayGee, no entanto, esta solução é para o DataGrid - não para o controle Grid padrão.
Brad Leach
Clicar no espaço de preenchimento não seleciona mais a linha.
jor
9

Pensei em adicionar minha própria solução porque ninguém ainda mencionou isso. Em vez de criar um UserControl baseado em Grid, você pode direcionar os controles contidos na grade com uma declaração de estilo. É necessário adicionar preenchimento / margem a todos os elementos sem precisar definir para cada um deles, o que é complicado e exige muito trabalho.

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

O que é como o equivalente a "preenchimento de célula".

James M
fonte
isso escorre? por exemplo, se você tiver um stackpanel em uma linha de grade, os filhos do bloco de texto do stackpanel herdam essa propriedade?
Maslow
Não tenho certeza se isso ultrapassa os filhos imediatos, mas você pode descobrir com um teste simples.
James M
2
@ Maslow A resposta é absolutamente 'Sim', mas você está um pouco enganador. Não há "herança" da Marginpropriedade, é que qualquer Styleum em a ResourceDictionaryse aplicará a todos os elementos em TargetTypequalquer lugar dentro de todo o escopo do elemento proprietário desse dicionário. Então é isso Styleque escorre, não a propriedade.
Glenn Slayden
8

Isso não é terrivelmente difícil. Não posso dizer o quão difícil foi em 2009 quando a pergunta foi feita, mas foi então.

Observe que, se você definir explicitamente uma margem diretamente em um filho da grade ao usar esta solução, essa margem aparecerá no designer, mas não no tempo de execução.

Essa propriedade pode ser aplicada a Grid, StackPanel, WrapPanel, UniformGrid ou qualquer outro descendente do Panel. Afeta crianças imediatas. Presume-se que as crianças desejem gerenciar o layout de seu próprio conteúdo.

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);
            }
        }
    }
}

Demo:

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));
    }
}

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

15ee8f99-57ff-4f92-890c-b56153
fonte
Você sabe por que sua resposta foi rebaixada? Parece ser uma solução de trabalho para o problema de OP
thesystem
4
@ thesystem Acho que alguém ficou chateado com alguma coisa. Não é grande coisa. Mas talvez haja um bug que eu tenha perdido. Acontece.
15ee8f99-57ff-4f92-890c-b56153
tem dw porque requer um buildou runantes que a visualização ao vivo possa ser exibida? Bom e simples. 1 para cima.
Cat Meow 2012
3

Encontrei este problema ao desenvolver algum software recentemente e me ocorreu perguntar POR QUE? Por que eles fizeram isso ... a resposta estava bem na minha frente. Uma linha de dados é um objeto; portanto, se mantivermos a orientação do objeto, o design de uma linha específica deve ser separado (suponha que você precise reutilizar a exibição da linha posteriormente no futuro). Então comecei a usar painéis de pilha de banco de dados e controles personalizados para a maioria das exibições de dados. As listas apareceram ocasionalmente, mas principalmente a grade foi usada apenas para a organização da página principal (cabeçalho, área de menus, área de conteúdo e outras áreas). Seus objetos personalizados podem gerenciar facilmente quaisquer requisitos de espaçamento para cada linha no painel ou grade da pilha (uma única célula da grade pode conter o objeto inteiro da linha. Isso também tem o benefício adicional de reagir adequadamente a alterações na orientação,

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

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

ou

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

Seus controles personalizados também herdarão o DataContext se você estiver usando ligação de dados ... meu benefício favorito pessoal dessa abordagem.

Matt Meikle
fonte
O que você tentou fazer aqui é criar uma versão bruta do ListViewcontrole WPF no GridViewmodo, mas sem nenhuma provisão para fazer com que todos os “RowObjects” compartilhem a mesma largura de coluna. Na realidade, ele não tem mais muito a ver com uma grade.
perfil completo de Glenn Slayden
3

no uwp (versão Windows10FallCreatorsUpdate e superior)

<Grid RowSpacing="3" ColumnSpacing="3">
Kursat Turkay
fonte
Isso também se aplica a Xamarin.Forms.
26618 James M
3

Editado:

Para dar margem a qualquer controle, você pode envolver o controle com uma borda como esta

<!--...-->
    <Border Padding="10">
            <AnyControl>
<!--...-->
Juan Pablo
fonte
O problema do OP não é ter uma margem em torno da grade, mas uma margem em torno das células da grade (ou como realmente perguntado em torno de linhas e colunas, mais tarde contradito por " adicionar colunas / linhas extras é exatamente o que eu estava tentando evitar "). .
mins
2

Eu fiz isso agora com uma das minhas grades.

  • Primeiro, aplique a mesma margem a todos os elementos dentro da grade. Você pode fazer isso manualmente, usando estilos ou o que quiser. Digamos que você queira um espaçamento horizontal de 6 px e um espaçamento vertical de 2 px. Em seguida, você adiciona margens de "3px 1px" a todos os filhos da grade.
  • Em seguida, remova as margens criadas ao redor da grade (se você deseja alinhar as bordas dos controles dentro da grade para a mesma posição da grade). Faça essa configuração com uma margem de "-3px -1px" na grade. Dessa forma, outros controles fora da grade serão alinhados com os controles mais extremos dentro da grade.
isierra
fonte
Aplicar a mesma margem a todos os elementos dentro da grade parece ser a maneira mais fácil.
21715 Envil
1

Uma possibilidade seria adicionar linhas e colunas de largura fixa para atuar como o preenchimento / margem que você está procurando.

Você também pode considerar que está limitado pelo tamanho do seu contêiner e que uma grade se tornará tão grande quanto o elemento que contém ou sua largura e altura especificadas. Você pode simplesmente usar colunas e linhas sem largura ou altura definidas. Dessa forma, eles padronizam a divisão uniforme do espaço total na grade. Então seria apenas uma questão de centralizar seus elementos vertical e horizontalmente dentro de sua grade.

Outro método pode ser agrupar todos os elementos da grade em uma grade fixa com uma única linha e coluna que tenha tamanho e margem fixos. Que sua grade contém caixas fixas de largura / altura que contêm seus elementos reais.

Adam Lenda
fonte
1
Obrigado Adam, no entanto, adicionar as colunas / linhas extras é exatamente o que eu estava tentando evitar. Eu simplesmente quero reduzir minha marcação e poder especificar uma margem ou preenchimento ajudaria com isso.
Brad Leach
Você só precisa definir as colunas e linhas extras, não adicionar a marcação para elas. Por exemplo, se você quiser adicionar N largura entre colunas para 3 colunas, terá 5 definições de coluna. O primeiro seria a largura automática, e o próximo fixo, depois automático, depois fixo e depois automático. Em seguida, atribua apenas as colunas 1, 3 e 5 aos elementos de conteúdo reais. Entendo sua opinião sobre a marcação extra da coluna, mas, a menos que você tenha centenas de elementos, isso me parecerá trivial. sua ligação embora. Além disso, você já tentou usar um painel de pilhas com as margens definidas?
Adam Lenda
Para o meu caso, adicionar uma coluna "splitter / margin" foi de longe a mais limpa e fácil. Obrigado!
22418 jchristof
1

Recentemente, tive um problema semelhante na grade de duas colunas, precisava de uma margem apenas nos elementos da coluna da direita. Todos os elementos nas duas colunas eram do 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
fonte
0

Embora você não possa adicionar margem ou preenchimento a uma Grade, você pode usar algo como um Quadro (ou contêiner semelhante), ao qual pode aplicá-lo.

Dessa forma (se você mostrar ou ocultar o controle em um botão, clique em dizer), não será necessário adicionar margem a todos os controles que possam interagir com ele.

Pense nisso como isolando os grupos de controles em unidades e aplicando estilo a essas unidades.

ed13
fonte
0

Como foi dito antes, crie uma classe GridWithMargins. Aqui está o meu exemplo de código de trabalho

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
fonte
1
Lança System.ArgumentException: 'A largura deve ser não negativa.' real.Width - = Math.Min (real.Width, RowMargin.Left + RowMargin.Right); real.Height - = Math.Min (real.Height, RowMargin.Top + RowMargin.Bottom);
21717 Jamie Mellway
Com a correção de Jamie, ele roda, mas ainda não faz o que deveria. Com a altura da linha e a largura da coluna definidas como Automático, ele faz a coisa errada de uma maneira diferente.
15ee8f99-57ff-4f92-890c-b56153
0

Estou surpreso por ainda não ver esta solução publicada.

Vindo da web, estruturas como o bootstrap usarão uma margem negativa para retirar linhas / colunas.

Pode ser um pouco detalhado (embora não tão ruim), funciona e os elementos são espaçados e dimensionados de maneira uniforme.

No exemplo abaixo, uso uma StackPanelraiz para demonstrar como os três botões são espaçados igualmente usando margens. Você pode usar outros elementos, basta alterar o botão interno x: Type from para o seu elemento.

A idéia é simples: use uma grade do lado de fora para puxar as margens dos elementos para fora de seus limites pela metade da quantidade da grade interna (usando margens negativas), use a grade interna para espaçar uniformemente os elementos com a quantidade desejada.

Atualização: alguns comentários de um usuário disseram que não funciona. Aqui está um vídeo rápido demonstrando: https://youtu.be/rPx2OdtSOYI

insira a descrição da imagem aqui

    <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
fonte
1
Meu erro - não notei o estilo implícito do botão. Fiquei distraído com as margens negativas.
15ee8f99-57ff-4f92-890c-b56153
-6

Às vezes, o método simples é o melhor. Basta preencher suas cordas com espaços. Se são apenas algumas caixas de texto, etc, este é de longe o método mais simples.

Você também pode simplesmente inserir colunas / linhas em branco com um tamanho fixo. Extremamente simples e você pode alterá-lo facilmente.

rolos
fonte
Provavelmente porque não é um método muito bom. Usar espaços pode ser o "caminho mais fácil", mas na verdade é mais um truque. Não é preciso, pode (e provavelmente será) renderizado de maneira diferente e confiável em monitores com uma escala diferente e você não tem controle sobre a quantidade exata de espaço que cria. Inserindo colunas em branco / linhas vai fazer uma bagunça de sua grade ...
Mark