How can I display my business objects in a consistent way across different forms in WPF?

Defining something in a single place for re-use in multiple places is a common goal for developers. In UI frameworks like ASP.NET or windows forms re-using pieces of UI is typically done by creating user controls or custom controls. Unfortunately the places where you can easily use these is often limited. For example if you create a UserControl in ASP.NET to display an employee there is no easy way to re-use that control when displaying a list of customers in a combo box. WPF allows the display of business objects and other data to be templated like controls, and because of WPF's rich content model these data templates can be used in many different places.

Lets look at the steps we would go through to create a simple data template for displaying employee data. First lets assume that our employee type has the following public properties, all of which are strings:

  • Department
  • EmailAddress
  • ImagePath
  • JobDescription 
  • Name
  • Title

We might wish to lay out our employee data as shown below: an image of the employee on the left followed by with the employee name, email address and other details layed out as shown.

desired layout for employee display

In order to create the DataTemplate we'll first examine the regular Xaml to display the layout shown above statically (without any data binding) and then add a little data binding code to turn this into a DataTemplate we can use anywhere we want to show employees. The Xaml markup below creates the static layout we want, with appropriate text placeholders for the fields we'll eventually be exposing from the Employee object. To achieve this layout we used a Grid panel, one of the most flexible layout panels in WPF. We created a 3x3 grid of rows and columns by adding the appropriate <ColumnDefinition/> and <RowDefinition/> elements to the Grid. Then we add the actual placehoders to the Grid - TextBlock elements and a single Image element. We control the placement of these elements in the Grid by setting the Grid.Row and Grid.Column attributes. This notation in the form of ParentElement.PropertyName is called Property-Element syntax. This Xaml is actually setting properties on the parent Grid element, placing child elements in the appropriate rows and columns.

<Grid ShowGridLines="True" Background="Transparent">
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="55" />
    <ColumnDefinition />
    <ColumnDefinition />
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
    <RowDefinition />
  </Grid.RowDefinitions>
  <Image Width="50" Stretch="Uniform"
         Source="pack://application:,,/Media/head-male-sillouette.gif"
         Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"/>
  <TextBlock FontSize="20" Grid.Column="1" Grid.Row="0"
             Foreground="Orange" TextDecorations="Underline"
             Text="Data Bound: Name"/>
  <TextBlock FontSize="12" Grid.Column="1" Grid.Row="1"
             Foreground="Blue" TextDecorations="Underline"
             Text="Data Bound: EmailAddress" />
  <TextBlock FontSize="16" Margin="10,0,10,0" Grid.Column="2"
             Grid.Row="0" Foreground="Black"
             Text="Data Bound: Title" />
  <TextBlock FontSize="16" Margin="10,0,10,0" Grid.Column="2"
             Grid.Row="1" Foreground="Black"
             Text="Data Bound: Department" />
  <TextBlock FontSize="12" Grid.Column="0" Grid.ColumnSpan="3" 
             Grid.Row="2" Foreground="Black"
             Text="Data Bound: JobDescription (this one can span multiple columns)"
             TextWrapping="Wrap" />
</Grid>

To Convert this Xaml into a DataTemplate, all that is required is to change the currently static TextBlock text to something bound to the employee object. This is done with the Binding markup extension {Binding Path=PropertyName}, using the name of the property you wish to mind to in the Path portion of the binding statement. The image source is also just a string, so we can bind the source of the image to that to. We also need to place our Xaml in a DataTemplate element and assign it a DataType. By placing the DataTemplate in the Application resource dictionary in MyApp.xaml we can use this template anywhere in our application we are binding to employee data. The finished data template is shown below. The text in orange shows the changes necessary to convert from static Xaml to the DataTemplate.

<DataTemplate DataType="{x:Type l:Employee}">
  <Grid Background="Transparent">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="55" />
      <ColumnDefinition />
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
      <RowDefinition />
    </Grid.RowDefinitions>
    <Image Width="50" Stretch="Uniform"
           Source="{Binding Path=ImagePath}"
           Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"/>
    <TextBlock FontSize="20" Grid.Column="1" Grid.Row="0"
           Foreground="Orange" TextDecorations="Underline"
           Text="{Binding Path=Name}"/>
    <TextBlock FontSize="12" Grid.Column="1" Grid.Row="1"
           Foreground="Blue" TextDecorations="Underline"
           Text="{Binding Path=EmailAddress}" />
    <TextBlock FontSize="16" Margin="10,0,10,0" Grid.Column="2"
           Grid.Row="0" Foreground="Black" Text="{Binding Path=Title}"/>
    <TextBlock FontSize="16" Margin="10,0,10,0" Grid.Column="2"
           Grid.Row="1" Foreground="Black" Text="{Binding Path=Department}"/>
    <TextBlock FontSize="12" Grid.Column="0" Grid.ColumnSpan="3" 
           Grid.Row="2" Foreground="Black" Text="{Binding Path=JobDescription}"
           TextWrapping="Wrap" />
  </Grid>
</DataTemplate>

We don't need to do anything special in our application to use the DataTemplate. Any time an element is bound to an employee, or list of employees the DataTemplate will be used. For this example we added an ObjectDataProvider to the Application resource dictionary (in MyApp.xaml) which returns a collection containing 4 employee objects. We then set it as the DataContext for all the items in our main window. Below is the Xaml for binding the contents of a button to a single employee, and for binding a ComboBox to the list of employees. You can download the full source for this sample here.

<StackPanel Margin="10" DataContext="{StaticResource Employees}">
    ...
    <Button Content="{Binding Path=[0]}" />
    <ComboBox ItemsSource="{Binding}" SelectedIndex="0" />
 </StackPanel>

Button and combo box