How do I layout a form in WPF using grid or other controls for maintainability

26,480

Solution 1

is there some WPF equivalent of nobr?

Remember that you can nest panels:

<WrapPanel Orientation="Horizontal">
   <StackPanel Orientation="Horizontal">
      <Label>Some field</Label>
      <TextBox>Some value</TextBox>
   </StackPanel>
   <StackPanel Orientation="Horizontal">
      <Label>Another field</Label>
      <TextBox>Another value</TextBox>
   </StackPanel>
   ...
</WrapPanel>

Also, for columnar layouts, the shared size scope of the Grid can coordinate any number of grids that use it:

<StackPanel Orientation="Vertical" Grid.IsSharedSizeScope="True">
   <Grid>
      <Grid.ColumnDefinitions>
         <ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
         <ColumnDefinition Width="*"/>
      </Grid.ColumnDefinitions>
      <Label Grid.Column="0">Some field</Label>
      <TextBox Grid.Column="1">Some value</TextBox>
   </Grid>
   <Grid>
      <Grid.ColumnDefinitions>
         <ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
         <ColumnDefinition Width="*"/>
      </Grid.ColumnDefinitions>
      <Label Grid.Column="0">Another field</Label>
      <TextBox Grid.Column="1">Another value</TextBox>
   </Grid>
</StackPanel>

I kind of hate how verbose the XAML for this is, especially that you have to repeat the column definitions. Though if you structure your classes properly and use templates it's not so terrible. And notice that you aren't keep track of row numbers anywhere in this scheme, so reordering fields is simple.

Solution 2

What you might be looking for is a stack panel. Using a vertical StackPanel will allow you to arrange your label and controls consistently. For each label and control you might want a horizontal stack panel like so

<StackPanel Orientation="Vertical">
  <StackPanel Orientation="Horizontal">
     <Label Width="150">Name</Label>
     <TextBox Width="200" />
  </StackPanel>
  <StackPanel Orientation="Horizontal">
     <Label Width="150">Date of Birth</Label>
     <DatePicker Width="200" />
  </StackPanel>
</StackPanel>

Now you can add, remove, edit, and reorder to your heart's content without worrying about columns and rows.

Solution 3

Try the UniformGrid control.

Solution 4

I came across this post today while having the same question, using the answers in this thread I came up with a manageable solution to simple text/text pairs. To add new fields simply expand the "FormItems" collection.

xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"

<Window.Resources>
    <c:ArrayList x:Key="FormItems">
        <c:DictionaryEntry Key="First        Name" Value="John"/>
        <c:DictionaryEntry Key="Last Name" Value="Smith"/>
    </c:ArrayList>
</Window.Resources>

<ItemsControl ItemsSource="{StaticResource FormItems}" Grid.IsSharedSizeScope="True">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
                <TextBlock>
                    <Run Text="{Binding Key}"/><Run Text=": "/>
                </TextBlock>
                <TextBox Grid.Column="1" Text="{Binding Value}"/>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Basic Form Layout Result

There has been no consideration for margins and padding, it just shows the concept of using a DataTemplate to reuse the layout of each item. It can be adapted to include other data types and controls fairly easily. You could even use the ItemTemplateSelector to select a different template based on the type of the DictionaryEntry.Value

EDIT

Another approach that I found that made it easier for DataBinding was to use Visual Studio to create a new WPF "Custom Control". Doing so will create a new file named Themes/Generic.xaml with a new default layout defined there. A few simple edits and we can use the ItemsControl to display our new control.

New class:

public class FormControlItem : ContentControl
{
    public object Field {
        get { return base.GetValue(FieldProperty); }
        set { base.SetValue(FieldProperty, value); }
    }

    static FormControlItem() {
        DefaultStyleKeyProperty.OverrideMetadata(
            typeof(FormControlItem), 
            new FrameworkPropertyMetadata(typeof(FormControlItem)));
    }

    public static readonly DependencyProperty FieldProperty =
        DependencyProperty.Register(
            "Field",
            typeof(object),
            typeof(FormControlItem),
            new FrameworkPropertyMetadata());
}

Themes/Generic.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:MyApplication">


    <Style TargetType="{x:Type local:FormControlItem}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:FormControlItem}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>
                            <ContentPresenter ContentSource="Field"/>
                            <ContentPresenter Grid.Column="1" ContentSource="Content"/>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

Usage example:

<ItemsControl Grid.IsSharedSizeScope="True">
    <local:FormControlItem Field="Name: ">
        <TextBox Text="{Binding Path=Name}"/>
    </local:FormControlItem>
    <local:FormControlItem Field="Type: ">
        <ComboBox 
            SelectedItem="{Binding Path=Type}"
            ItemsSource="{Binding Path=TypeValues}"/>
    </local:FormControlItem>
    <local:FormControlItem Field="Category: ">
        <TextBox Text="{Binding Path=Category}"/>
    </local:FormControlItem>
</ItemsControl>

Custom Control Layout Result

Solution 5

If you can accord it, I recommend Expression Blend if you are going to be doing much UI design. It allows a simpler view of the items. Nesting controls into various containers is a good way to get the UI to be dynamic, but structured.

Typically I will use a Grid panel to break a window up into functional areas. Then I will use a series of StackPanels (often a vertical stack panel with horizontal StackPanels inside it, each with a label and textbox).

Unfortunately Grids only work as you stated. Elements in them specify the row and/or column that they reside in. If you used Blend, adding Grid Columns or Rows will have controls auto-magically change the row/column specification to stay in the position they were placed.

Hope it helps.

Expression Blend Trial at Microsoft.

UPDATE:

VS2012 has a lot of the Expression Blend functionality baked into the WPF designer. Much of the need for a copy of Blend is no longer there as developers have access to a lot of the cool tools from Blend.

Share:
26,480
Jason Coyne
Author by

Jason Coyne

#SOreadytohelp

Updated on June 06, 2020

Comments

  • Jason Coyne
    Jason Coyne about 4 years

    I have a WPF form, I want to lay out a standard form onto it. Each form element will have a label, and then a control. Pretty standard stuff.

    If I use a wrap panel, it can cause the label and the control to be separated, but I want them to stay together. Is there some WPF equivalent of <nobr/>?

    Grid works, and allows for column spanning etc, however I really really hate that you specify the column and row on each control. This makes it extremely inconvenient to reorder or insert things into the list.

    Is there a way to get the grid to use more HTML style column/rows where the items are a child of the row they are in, so that I can re-order easily?

    Is there some other control that will let me layout a form easily?

  • Alex Klaus
    Alex Klaus about 11 years
    Nice looking code. But the control was written in VB.NET. Do you know the same, but in C#?
  • GorillaApe
    GorillaApe over 10 years
    nice solution but its rude how much xaml you need. Also what about performance ?
  • Fandi Susanto
    Fandi Susanto about 8 years
    May be for better syntax, put the widths in StackPanel.Resources.
  • Sean Kearon
    Sean Kearon over 7 years
    It looks like the site above no longer exists.
  • O. R. Mapper
    O. R. Mapper over 5 years
    @AlexKlaus: If it's compiled to an assembly and you want to use the control as is, what's the problem with mixing VB.NET and C#?