WPF:具有列/行边距/填充的网格?


137

是否可以轻松地为WPF网格中的行或列指定边距和/或填充?

我当然可以添加额外的列来使内容间隔开,但这似乎是一项填充/边距的工作(它将提供简单的XAML)。是否有人从标准Grid派生来添加此功能?


4
您可以在这里找到一个有用的示例:codeproject.com/Articles/107468/WPF-Padded-Grid
peter70

2
Kinda感到困惑,这不是网格基准功能的一部分...
uceumern

截止到今天,经过10年的回答证明,事实是这并非易事,最好的方法(以避免每次使用一个单元格时都要进行额外的容易出错的工作)是派生Grid(如@ peter70先前所建议的) )添加适当的单元格填充依赖性属性,该属性将控制单元格子级的Margin属性。这不是一项艰巨的任务,然后您就拥有了可重用的控件。旁注...网格实际上是设计不良的控件。
分钟

Answers:


10

您可以编写自己的GridWithMargin类,继承自Grid,并重写ArrangeOverride方法以应用边距


32
我不明白为什么人们会给您竖起大拇指,因为它远没有您在这里想像/描述的那么容易。只需尝试做30分钟,您就会很快发现您的答案不适用。还请考虑行和列的跨度
Eric Ouellet

89

RowDefinitionColumnDefinition是类型ContentElement,并且Margin严格来说是一个FrameworkElement属性。因此,对于您的问题,“是否可能”答案是最明确的答案。而且,我还没有看到任何展示这种功能的布局面板。

您可以按照建议添加额外的行或列。但是,您也可以在Grid元素本身或上的任何元素上设置边距Grid,因此,这是目前最好的解决方法。


OP不会尝试在RowDefinition或ColumnDefinition上设置边距。他试图在从FrameworkElement派生的网格的可视子级上设置边距。
18ee8f99-57ff-4f92-890c-b56153 '18 -10-26

58

使用Border单元格控件外部的控件并为此定义填充:

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


资源:


2
@RichardEverett检查返回机器:链接已更新为答案。
CJBS

我最喜欢这个答案。它为原始问题提供了解决方案,并且很简单。谢谢!
Tobias

这是简单而实用的
aggsol

19

您可以使用如下形式:

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

或者,如果您不需要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>

28
感谢JayGee,但是此解决方案适用于DataGrid-而不是标准Grid控件。
布莱德·利奇

单击填充空间不再选择该行。
JOR

9

以为我会添加我自己的解决方案,因为还没有人提到这一点。您可以使用样式声明来定位包含在网格中的控件,而不是设计基于Grid的UserControl。不必在每个元素上都定义填充,而是在所有元素上添加了填充/边距,这很麻烦而且很费力,例如,如果您的Grid除了TextBlocks之外什么都不包含,您可以执行以下操作:

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

这类似于“单元填充”。


这滴下来了吗?例如,如果您在网格行中有一个堆栈面板,那么堆栈面板的textblock子代是否继承此属性?
马斯洛

不知道它是否会滴落过去的直系子女,但是您可以通过简单的测试来找出答案。
James M

2
@Maslow答案绝对是“是”,但是您的措辞有些误导​​。并没有“继承”该Margin属性,而是Style在字典所有者元素的整个范围内,a 中的任何ResourceDictionary元素都会应用于其TargetType所有元素。因此,是trick流而不是财产。Style
Glenn Slayden '17

8

这并不困难。我不能说在2009年问这个问题有多困难,但是那是那时。

请注意,如果在使用此解决方案时直接在网格的子级上直接设置边距,则该边距将显示在设计器中,但不会在运行时显示。

此属性可以应用于Grid,StackPanel,WrapPanel,UniformGrid或Panel的任何其他后代。它影响到直系孩子。假定孩子将要管理自己内容的布局。

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

演示:

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

在此处输入图片说明

在此处输入图片说明

在此处输入图片说明


你知道为什么你的答案被否决吗?似乎是解决OP问题的
可行

4
@thesystem我认为有人为某事感到沮丧。没什么大不了的。但是也许其中有一个我错过的错误。它发生了。
15ee8f99-57ff-4f92-890c-b56153 '18

dw'是因为它需要一个build或个run才能显示实时预览?好又简单。1个
猫猫2012年

3

最近在开发某些软件时遇到了这个问题,我想问为什么?他们为什么要这么做...答案就在我眼前。一行数据是一个对象,因此,如果我们保持面向对象,则应该分开特定行的设计(假设您以后需要重新使用行显示)。因此,我开始对大多数数据显示使用数据绑定堆栈面板和自定义控件。列表偶尔出现,但大多数网格仅用于主要页面组织(标题,菜单区域,内容区域,其他区域)。您的自定义对象可以轻松管理堆栈面板或网格中每一行的任何间距要求(单个网格单元可以包含整个行对象。这还具有额外的好处,即可以对方向变化做出适当的反应,

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

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

要么

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

如果您使用数据绑定,那么您的Custom控件也将继承DataContext……这是我个人最喜欢的方法。


您在此处尝试做的是ListViewGridView模式下创建WPF 控件的原始版本,但没有提供使所有“ RowObjects”共享相同列宽的条件。实际上,它不再与网格有太大关系。
格伦·斯莱登

3

在uwp中(Windows10FallCreatorsUpdate版本及更高版本)

<Grid RowSpacing="3" ColumnSpacing="3">

这也适用于Xamarin.Forms
James M

3

编辑:

为了给任何控件留有余量,您可以像这样用边框包裹控件

<!--...-->
    <Border Padding="10">
            <AnyControl>
<!--...-->

OP问题不是在网格周围留有余量,而是在网格单元周围留有余量(或实际上在行和列周围有问,后来与“ 添加额外的列/行恰恰是我试图避免的注释”相矛盾) 。
分钟

2

我现在是用我的一个网格做的。

  • 首先,将相同的边距应用于网格内的每个元素。您可以使用样式或任何您喜欢的样式来进行此操作。假设您希望水平间距为6px,垂直间距为2px。然后,向网格的每个子级添加“ 3px 1px”的边距。
  • 然后删除在网格周围创建的边距(如果您想将网格内控件的边框与网格的同一位置对齐)。这样做为网格设置了“ -3px -1px”的边距。这样,网格外部的其他控件将与网格内部的最外面的控件对齐。

将相同的边距应用于网格内的每个元素似乎是最简单的方法。
Envil

1

一种可能性是添加固定宽度的行和列,以充当您要查找的填充/边距。

您可能还认为您受到容器大小的限制,并且网格将变得与包含元素或其指定的宽度和高度一样大。您可以简单地使用没有设置宽度或高度的列和行。这样,它们默认将网格内的总空间平均分配。这样一来,您就可以在网格内将元素垂直和水平居中放置。

另一种方法可能是将所有网格元素包装在具有固定大小和边距的单行和列网格中。您的网格包含固定的宽度/高度框,其中包含您的实际元素。


1
谢谢亚当,但是添加额外的列/行正是我想要避免的。我只是想减少标记,并且能够指定边距或填充将对此有所帮助。
布莱德·利奇

您只需要定义额外的列和行,而无需为其添加标记。例如,如果要为3列在列之间添加N宽度,则将有5个列定义。第一个是自动宽度,第二个是固定宽度,然后是自动,然后固定,然后是自动。然后,仅将1、3和5列分配给实际的内容元素。我了解您关于额外的列标记的观点,但是除非您有数百个元素,否则对我而言这似乎微不足道。你的电话。另外,您是否尝试过使用设置了边距的堆叠面板的堆叠面板?
亚当·伦达

就我而言,添加“分隔符/边距”列是迄今为止最干净,最简单的列。谢谢!
jchristof

1

我最近在两列网格中遇到了类似的问题,我只需要在右列中的元素上留一点空白。两列中的所有元素均为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>

0

尽管您无法向网格中添加边距或填充,但可以使用诸如Frame(或类似容器)之类的东西来应用它。

这样(如果您在单击按钮时显示或隐藏该控件),则无需在每个可能与之交互的控件上添加边距。

可以将其视为将控件组隔离为单元,然后将样式应用于这些单元。


0

如前所述,创建GridWithMargins类。这是我的工作代码示例

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

用法:

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

1
引发System.ArgumentException:'宽度必须为非负数。实际宽度-= Math.Min(actual.Width,RowMargin.Left + RowMargin.Right); actual.Height-= Math.Min(actual.Height,RowMargin.Top + RowMargin.Bottom);
Jamie Mellway '17

有了Jamie的修复程序,它可以运行,但是仍然没有执行应有的操作。将行高和列宽设置为“自动”时,它以不同的方式处理错误的事情。
18ee8f99-57ff-4f92-890c-b56153 '18 -10-26

0

令我惊讶的是我还没有看到这个解决方案。

来自网络的引导程序之类的框架将使用负边距来拉回行/列。

它可能有点冗长(尽管还不错),但确实有效,并且元素的间距和大小均均匀。

在下面的示例中,我使用 StackPanel根来演示如何使用边距均匀地分布3个按钮。您可以使用其他元素,只需将内部x:Type从button更改为您的元素。

这个想法很简单,在外部使用网格将元素的边距拉出边界的一半,而不是内部网格的量(使用负边距),然后使用内部网格将元素与所需的量均匀地隔开。

更新: 用户的一些评论说它不起作用,这是一个快速视频演示:https : //youtu.be/rPx2OdtSOYI

在此处输入图片说明

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

1
我的错误-我没有注意到隐式的Button样式。我对负利润感到有些分心。
18ee8f99-57ff-4f92-890c-b56153 '18 -10-27

-6

有时候,简单的方法是最好的。只需在字符串上加上空格即可。如果只有几个文本框等,这是迄今为止最简单的方法。

您也可以简单地插入固定大小的空白列/行。非常简单,您可以轻松更改它。


可能因为这不是一个很好的方法。使用空间可能是“轻松的出路”,但实际上它更像是一个hack。这是不精确的,它可能会(并且可能会)在缩放比例不同的显示器上以不同且不可靠的方式呈现,并且您无法控制所创建的确切空间量。插入空白列/行会使您的网格混乱……
Mark
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.