58

I have a set of Key/Value pairs I want to display on a WPF Window. I'm using a grid to lay them out like so:

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

However when I display this, the TextBoxes are squashed up with their top and bottom borders touching the TextBox above/below. What is the best way to add vertical space to the rows in this layout?

Grokys
  • 16,228
  • 14
  • 69
  • 101
  • 1
    Pretty simple solution: just add additional rows with a set height. So if you want a space of 3 pixels between rows, just create a new row between them with a set height of 3. – Krythic Dec 11 '17 at 02:45

3 Answers3

89

Easiest way is to set a margin on the individual controls. Setting it on the TextBoxes should be enough, because once they're spaced out the Labels will set vertically in the center of each row and have plenty of space anyway.

You can set it once using a style:

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

    ...
</Grid>

This will add a 4-pixel margin to the bottom of any TextBox inside your grid.

Matt Hamilton
  • 200,371
  • 61
  • 386
  • 320
  • This does not seem to be working for me. Does this not work if the control in question already has a style? I can set the margin manually on the control, but not via your example. – Krythic Dec 11 '17 at 02:07
  • I found an alternate solution. All I did was add an additional row between the controls with a height of 3 pixels. Works like a charm. – Krythic Dec 11 '17 at 02:37
53

Another nice approach can be seen here.

You create class for setting Margin property:

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

Now you have attached property behavior, so that syntax like this would work:

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

This is the easiest & fastest way to set Margin to several children of a panel, even if they are not of the same type. (I.e. Buttons, TextBoxes, ComboBoxes, etc.)

Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
Elad Katz
  • 7,483
  • 5
  • 35
  • 66
  • 4
    +1 Great. I changed the class name to LayoutSetter and added some other properties like VerticalAlignment to propagate it in the same way. I wish I could simply define a style for FrameworkElement... – surfen Jan 31 '12 at 16:56
  • Note that this doesn't work if the child items are added/removed dynamically, such as in an ItemsControl bound to a changing collection. – Drew Noakes Oct 09 '17 at 14:56
  • I've copied and refactored class needed for attached properties. Hope you don't mind. – Vadim Ovchinnikov Jun 21 '18 at 12:40
  • 1
    Are there any way to make this attached property respect local `Margin` values specified in XAML? – Vadim Ovchinnikov Jun 21 '18 at 12:47
2

What about setting TextBox's VerticalAlignment to 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>
Norman
  • 73
  • 5