A very common UI layout for editing data is a list of labelled fields/properties.  The label says what the field is and a textbox ( combo box, checkbox or datepicker ) is used to view and edit values.

In other technologies there are ready rolled controls such as the Asp.Net : DetailsView

Sometimes there are multiple columns of these paired controls to fit on a window. 

Another variant has the label above the box you edit in. 

Whichever variation used, this pair of controls is a very common pattern.

If you like this article and or the sample, please click on that 5th star rating up there (Thanks).

 

The WPF Version 

The obvious way to arrange a set of controls like this in WPF is to use a Grid with two columns and each pair of controls goes in a row.

Something like:

XAML
Edit|Remove
                    <Grid                        <Grid.ColumnDefinitions                            <ColumnDefinition Width="2*" /> 
                            <ColumnDefinition Width="3*" /> 
                        </Grid.ColumnDefinitions> 
                        <Grid.RowDefinitions                            <RowDefinition Height="*" /> 
                            <RowDefinition Height="24" /> 
                            <RowDefinition Height="24" /> 
                            <RowDefinition Height="24" /> 
                            <RowDefinition Height="24" /> 
                            <RowDefinition Height="24" /> 
                            <RowDefinition Height="24" /> 
                            <RowDefinition Height="24" /> 
                            <RowDefinition Height="24" /> 
                            <RowDefinition Height="3*" /> 
                            <RowDefinition Height="24" /> 
                            <RowDefinition Height="12" /> 
                        </Grid.RowDefinitions> 
 
                        <TextBlock Text="Name:" Grid.Row="1" /> 
                        <TextBlock Text="Address:" Grid.Row="2" /> 
                        <TextBlock Text="Town/City:" Grid.Row="5" /> 
                        <TextBlock Text="Post Code:" Grid.Row="6" /> 
                        <TextBlock Text="Credit Limit:" Grid.Row="7" /> 
                        <TextBlock Text="Outstanding Amount:" Grid.Row="8" /> 
                        <!-- --> 
                        <TextBox Text="{Binding CustomerName,   Mode=TwoWay}"  Grid.Column="1" Grid.Row="1"/> 
 
                         <TextBox Text="{Binding Address1,  Mode=TwoWay}"  Grid.Column="1"  Grid.Row="2" /> 
                          <TextBox Text="{Binding  EditVM.TheEntity.Address2,  Mode=TwoWay}" Grid.Column="1" Grid.Row="3" /> 
                         <TextBox Text="{Binding  Address3, Mode=TwoWay}" Grid.Column="1"   Grid.Row="4" /> 
                         <TextBox Text="{Binding TownCity, Mode=TwoWay}"  Grid.Column="1"  Grid.Row="5"  /> 
                         <TextBox Text="{Binding  PostCode,   Mode=TwoWay}"  Grid.Column="1" Grid.Row="6" /> 
                <TextBox  Text="{Binding  EditVM.TheEntity.CreditLimit, StringFormat={}{0:C0}, 
  Mode=TwoWay}"  Grid.Column="1"  Grid.Row="7" />
This is fine for just a few properties but as the list grows it’s increasingly fiddly and error prone arranging everything in the right row.

What we could do with would be some way of wrapping any old editing sort of a control with a label whilst allowing you to standardise formatting of these so they all line up nicely.

Anyone who has read the previous article on Keeping your MVVM views DRY or who knows WPF well might by now realise a content control can be used to encapsulate such formatting.

Template the contentcontrol.  Put a Grid in there with two columns.

A TextBlock acts as the label in the first column, that editing control is accepted as the content in the other and that's one of these things encapsulated in a re-usable fashion.

That's one done, but we also need. 

 

A List of Them

Defining and setting those rows is particularly tedious.

What we want is just some sort of a list of these content controls so we don’t need to worry about which index for what, how many rows you have, and such "fun" as ending up with the wrong label on a TextBox so the user is editing the wrong property.

WPF allows very flexible formatting and it's possible to present a list of UserControls in an ItemsControl or ListBox.

The sample uses a ListBox and looks like this:

OK, not the most interesting choice of field names but this is just a sample.

Show Me the Code

Both ListBox and ItemsControl have their own set of advantages so you could make a case for either.  The sample uses a Listbox and that makes any scrollviewer easier and more reliable than an ItemsControl.  The downside is the mouseover and selection effects.  Unless you like them some extra markup is required to suppress them.

MainWindow

There's not much markup in this:

 

XAML
Edit|Remove
    <Grid        <Grid.Resources            <!-- The ListboxItem template has a blue mouseover effect, replace the template to avoid that --> 
            <!-- Set IsTabStop false so you tab straight to the thing in the item --> 
            <Style TargetType="{x:Type ListBoxItem}"> 
                <Setter Property="IsTabStopValue="False"/> 
                <Setter Property="Template"> 
                    <Setter.Value> 
                        <ControlTemplate TargetType="{x:Type ListBoxItem}"> 
                            <Grid Background="{TemplateBinding Background}"> 
                                <ContentPresenter   
                                        ContentTemplate="{TemplateBinding ContentTemplate}"  
                                        Content="{TemplateBinding Content}"  
                                        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                        Margin="{TemplateBinding Padding}"> 
                                </ContentPresenter> 
                            </Grid> 
                        </ControlTemplate> 
                    </Setter.Value> 
                </Setter> 
            </Style> 
            <Style TargetType="DatePicker"> 
                <Setter Property="MarginValue="0,0,0,4"/> 
            </Style> 
        </Grid.Resources> 
 
        <ListBox HorizontalContentAlignment="Stretch"  
                 KeyboardNavigation.TabNavigation="Cycle"  
                 Background="AliceBlue"            <ListBox.Resources                <Style TargetType="{x:Type TextBox}"> 
                    <Setter Property="HorizontalAlignmentValue="Stretch"/> 
                </Style> 
            </ListBox.Resources> 
            <!--<local:EditRow LabelFor="Label for property One:"  LabelWidth="120" PropertyWidth="200"> 
                <TextBox Text="aaaa"/> 
            </local:EditRow>--> 
            <local:EditRow LabelFor="Label for property One:"                 <TextBox Text="aaaa"/> 
            </local:EditRow> 
            <local:EditRow  LabelFor="Label for Property Two:"                <TextBox Text="bbbb"/> 
            </local:EditRow> 
            <local:EditRow  LabelFor="Label for Property Three:"                <DatePicker/> 
            </local:EditRow> 
            <local:EditRow  LabelFor="Label for Property Four:"                <TextBox Text="dddddd"/> 
            </local:EditRow> 
            <local:EditRow  LabelFor="Label for Property Five:"                <TextBox Text="eeee"/> 
            </local:EditRow> 
            <local:EditRow  LabelFor="Label for Property Six:"                <TextBox Text="fffffff"/> 
            </local:EditRow> 
            <local:EditRow  LabelFor="Label for Property Seven:"                <TextBox Text="ggggg"/> 
            </local:EditRow> 
            <local:EditRow  LabelFor="Label for Property Eight:"                <TextBox Text="hhhhhhhhhhh"/> 
            </local:EditRow> 
        </ListBox> 
    </Grid>
As mentioned earlier, the listbox item is re-templated purely to avoid the blue MouseOver background on a selected item.

 

Comment that out and see if you prefer it to be much more obviously a ListBox.

If you do so and set the height of the Window to 150 as well you can see another aspect of using a ListBox.

The blue background will appeal to some.  Similarly you might prefer to try and line up the label text with the text in the textboxes.

The ListBox gives us a vertical slider for zero effort which is handy if the list of properties may not fit in the available space.  On those occasions when you're dashing out the UI you don't even need worry about how many properties you throw in there.

Back to the details of MainWindow. 

Since this is ( obviously ) only some generated test data rather than a database or some such, there is no viewmodel or model.

Let's move on to that Content Control.

EditRow

This inherits from ContentControl for the reasons touched on earlier.  

C#
Edit|Remove
    public class EditRow : ContentControl 
    { 
        public string LabelFor 
        { 
            get { return (string)GetValue(LabelForProperty); } 
            set { SetValue(LabelForProperty, value); } 
        } 
        public static readonly DependencyProperty LabelForProperty = DependencyProperty.RegisterAttached( 
                          "LabelFor", 
                          typeof(string), 
                          typeof(EditRow)); 
        public string LabelWidth 
        { 
            get { return (string)GetValue(LabelWidthProperty); } 
            set { SetValue(LabelWidthProperty, value); } 
        } 
        public static readonly DependencyProperty LabelWidthProperty = DependencyProperty.RegisterAttached( 
                          "LabelWidth", 
                          typeof(string), 
                          typeof(EditRow) 
                          ); 
        public string PropertyWidth 
        { 
            get { return (string)GetValue(PropertyWidthProperty); } 
            set { SetValue(PropertyWidthProperty, value); } 
        } 
        public static readonly DependencyProperty PropertyWidthProperty = DependencyProperty.RegisterAttached( 
                          "PropertyWidth", 
                          typeof(string), 
                          typeof(EditRow) 
                         ); 
        public EditRow() 
        { 
            this.IsTabStop = false; 
        } 
    }
The only code in there is to expose three dependency properties.
LabelFor is the string which will be displayed as the label.

LabelWidth and PropertyWidth are strings and you can set them like regular measures to proportional such as "2*" or fixed like "200".  These are given default values so you only need to set these in exceptional circumstances - they're not in set in MainWindow.

You can experiment with fixed widths easily using the piece of code which is commented out:

XAML
Edit|Remove
            <local:EditRow LabelFor="Label for property One:"  LabelWidth="120" PropertyWidth="200"                <TextBox Text="aaaa"/> 
            </local:EditRow>
Try that then play around a bit with * and 3* etc to prove these are definitely being used if you set them.
You might be wondering how EditRow gets any sort of format at all.
That's in:

Dictionary1

There's a generic style ( no key ) which will therefore apply to all EditRows.  This resource dictionary is merged in App.xaml.
XAML
Edit|Remove
    <Style TargetType="{x:Type local:EditRow}"> 
        <Setter Property="Template"> 
            <Setter.Value> 
                <ControlTemplate TargetType="{x:Type local:EditRow}"> 
                    <Grid> 
                    <Grid.ColumnDefinitions> 
                        <ColumnDefinition Width="{Binding RelativeSource={ 
                                                  RelativeSource FindAncestor, 
                                                  AncestorType=local:EditRow},  
                                                  Path=LabelWidthTargetNullValue=2*}"/> 
                 
                        <ColumnDefinition Width="{Binding RelativeSource={ 
                                                  RelativeSource FindAncestor, 
                                                  AncestorType=local:EditRow},  
                                                  Path=PropertyWidthTargetNullValue=3*}"/> 
              
                    </Grid.ColumnDefinitions> 
                        <TextBlock Text="{Binding RelativeSource={ 
                                                  RelativeSource FindAncestor, 
                                                  AncestorType=local:EditRow},  
                                                  Path=LabelFor}" 
                                           HorizontalAlignment="Right" 
                                           VerticalAlignment="Center" 
                                           Margin="0,4,8,6" 
                                           /> 
                        <Border Padding="6,4,6,0Grid.Column="1"> 
                            <ContentPresenter HorizontalAlignment="Stretch"/> 
                        </Border> 
                    </Grid> 
                </ControlTemplate> 
            </Setter.Value> 
        </Setter> 
    </Style>
As described in  Keeping your MVVM views DRY the template is used on a ContentControl to give us the Grid containing a Label and whatever is put in the ContentControl as it's content when it is used. That will often be a TextBox for the user to edit a property.
RelativeSource bindings are used to get the values of those three dependency properties in the control the template is applied to - EditRow.  
If either width is not set then they will be null and the default values are set here using TargetNullValue.  It is these default values the sample uses for all the EditRows.


Conclusion

Laying out a list of labelled properties to edit is rather tiresome if you go with the conventional Grid based approach.
This is far from being a DRY approach with a lot of repetitive and error prone markup.
Using a list of a custom content control makes things more straightforward.
Leaving the developer free to decide exactly which format they prefer for each item rather than worry about which index goes where for what.

 

Other Alternatives

Creating Custom Panels in WPF 

FishEyePanel/FanPanel - Examples of custom layout panels in WPF 

Workflow Foundation's property grid

Extended Toolkit Property grid