【问题标题】:Spacing between child controls in WPF GridWPF Grid 中子控件之间的间距
【发布时间】:2010-10-28 02:39:04
【问题描述】:

我有一组要在 WPF 窗口上显示的键/值对。我正在使用网格将它们布置成这样:

<Grid Margin="4">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <Label Grid.Row="0" Grid.Column="0">Code</Label>
    <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Code}"/>

    <Label Grid.Row="1" Grid.Column="0">Name</Label>
    <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Name}"/>
</Grid>

但是,当我显示它时,TextBoxes 被压扁,它们的顶部和底部边框接触到 TextBox 上方/下方。向此布局中的行添加垂直空间的最佳方法是什么?

【问题讨论】:

  • 非常简单的解决方案:只需添加具有设定高度的额外行。因此,如果您希望行之间有 3 个像素的空间,只需在它们之间创建一个设置高度为 3 的新行。

标签: wpf layout grid


【解决方案1】:

最简单的方法是在各个控件上设置边距。将它设置在 TextBoxes 上应该就足够了,因为一旦它们被隔开,标签将垂直设置在每行的中心并且无论如何都有足够的空间。

您可以使用样式设置一次:

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

    ...
</Grid>

这将在网格内任何 TextBox 的底部添加一个 4 像素的边距。

【讨论】:

  • 这似乎对我不起作用。如果相关控件已经具有样式,这是否不起作用?我可以在控件上手动设置边距,但不能通过您的示例。
  • 我找到了替代解决方案。我所做的只是在控件之间添加一个额外的行,高度为 3 像素。像魅力一样工作。
【解决方案2】:

可以看到另一个不错的方法here

您创建用于设置Margin 属性的类:

public class MarginSetter
{
    public static Thickness GetMargin(DependencyObject obj) => (Thickness)obj.GetValue(MarginProperty);

    public static void SetMargin(DependencyObject obj, Thickness value) => obj.SetValue(MarginProperty, value);

    // Using a DependencyProperty as the backing store for Margin. This enables animation, styling, binding, etc…
    public static readonly DependencyProperty MarginProperty =
        DependencyProperty.RegisterAttached(nameof(FrameworkElement.Margin), typeof(Thickness),
            typeof(MarginSetter), new UIPropertyMetadata(new Thickness(), MarginChangedCallback));

    public static void MarginChangedCallback(object sender, DependencyPropertyChangedEventArgs e)
    {
        // Make sure this is put on a panel
        var panel = sender as Panel;

        if (panel == null) return;

        panel.Loaded += Panel_Loaded;
    }

    private static void Panel_Loaded(object sender, EventArgs e)
    {
        var panel = sender as Panel;

        // Go over the children and set margin for them:
        foreach (FrameworkElement fe in panel.Children.OfType<FrameworkElement>())
            fe.Margin = GetMargin(panel);
    }
}

现在你已经附加了属性行为,这样这样的语法就可以工作了:

<StackPanel local:MarginSetter.Margin="5">
   <TextBox Text="hello" />
   <Button Content="hello" />
   <Button Content="hello" />
</StackPanel>

这是将Margin 设置为面板的多个子级的最简单、最快的方法,即使它们不是同一类型。 (即Buttons、TextBoxes、ComboBoxes 等)

【讨论】:

  • +1 太好了。我将类名更改为 LayoutSetter 并添加了一些其他属性,例如 VerticalAlignment 以同样的方式传播它。我希望我可以简单地为 FrameworkElement 定义一个样式...
  • 请注意,如果子项是动态添加/删除的,例如在绑定到变化集合的 ItemsControl 中,这将不起作用。
  • 我已经复制并重构了附加属性所需的类。希望你不要介意。
  • 有没有办法让这个附加属性尊重 XAML 中指定的本地 Margin 值?
【解决方案3】:

将TextBox的VerticalAlignment设置为Center怎么样?

<Grid Margin="4">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>

                <Label Grid.Row="0" Grid.Column="0">Code</Label>
                <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Code}" VerticalAlignment="Center"/>

                <Label Grid.Row="1" Grid.Column="0">Name</Label>
                <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Name}" VerticalAlignment="Center"/>
            </Grid>

【讨论】: