Click here to Skip to main content
Click here to Skip to main content
Technical Blog

A Simple Pattern for Creating Re-useable UserControls in WPF / Silverlight

, 7 Feb 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
This blog post provides step-by-step instructions for creating a user control, which exposes bindable properties, in WPF and Silverlight. The post covers dependency properties, and how to manage DataContext inheritance.

This blog post provides step-by-step instructions for creating a user control, which exposes bindable properties, in WPF and Silverlight. The post covers dependency properties, and how to manage DataContext inheritance.

When building user interfaces, you will often find yourself repeating the same UI patterns across your application. After all, users like to be presented with a consistent interface, so re-use makes sense. The WPF and Silverlight frameworks provide custom controls and user controls as a mechanism for re-using blocks of UI elements. Custom controls are rather special, with the logic being de-coupled from the XAML in order to support templating. For most needs, the simpler user control is more appropriate.

From participating in sites like StackOverflow, I have noticed that whilst most people understand how to create a user control, which allows them to ‘stamp out’ the same XAML in multiple places, many struggle with how to make their user controls flexible by exposing properties that configure how it looks or behaves. This blog post will walk through a simple example, showing you how to create a user control, add dependency properties, wire them to the user control XAML and make a truly re-useable control.

We’ll start with a very simple example, an application that displays a simple form field which consists of a name and a value:

<UserControl x:Class="UserControlExample.MainPage"
    ...>
 
  <Grid x:Name="LayoutRoot" Background="White">
 
    <StackPanel Orientation="Horizontal"
                VerticalAlignment="Top">
      <TextBlock Text="Shoesize:"
                 Width="100"
                 VerticalAlignment="Center"/>   
      <TextBox Text="{Binding Path=Shoesize}"
               Width="100"/>
    </StackPanel>
  </Grid>
</UserControl>

This UI is bound to a simple model object that implements INotifyPropertyChanged (not shown for the sake of brevity):

public class ModelObject : INotifyPropertyChanged
{
  public int Shoesize {...}
 
  public double Height {...}
}

The constructor instantiates the model object and sets it as the DataContext:

public MainPage()
{
  InitializeComponent();
 
  var model = new ModelObject()
  {
    Shoesize = 12,
    Height = 34.5
  };
 
  this.DataContext = model;
}

This produces the expected behaviour, a label and a text field that allows you to edit the Shoesize property:

Let’s say we want to allow the user to edit the Height property as well. We could cut and paste our current XAML, but this will only cause maintenance issues in future. Instead, the preferred approach would be to move the XAML into a user control, allowing it to be re-used.

The first step is to create a new user control, FieldUserControl, and move our XAML into there:

<UserControl x:Class="UserControlExample.FieldUserControl"
    ...>
  <StackPanel Orientation="Horizontal">
    <TextBlock Text="Shoesize:"
               Width="100"
               VerticalAlignment="Center"/>
    <TextBox Text="{Binding Path=Shoesize}"
             Width="100"/>
  </StackPanel>
</UserControl>

We can now replace the XAML we have moved with an instance of this user control:

<UserControl x:Class="UserControlExample.MainPage"
    xmlns:local="clr-namespace:UserControlExample"
    ...>
  <Grid x:Name="LayoutRoot">
    <local:FieldUserControl VerticalAlignment="Top"/>
  </Grid>
</UserControl>

Compiling and running this code proves that this still works; we can see the model property and edit it:

For trivial user controls, this is all we need to do. However, in most cases, like this one, you will find that there are some elements of your user control that you wish to configure. In order to use this control for editing the Height property, we need to make the label configurable. We do this by adding a Label property to our FieldUserControl.

This is where things get a bit tricky! Ideally this property should support binding, just like any other property of the framework UI controls. The WPF / Silverlight binding framework revolves around the concept of dependency properties, you can make any property the source of a binding, but the target must be a dependency property (DP). So let’s go ahead and add a Label dependency property to our user control:

public partial class FieldUserControl : UserControl
{
  #region Label DP
 
  /// <span class="code-SummaryComment"><summary>
</span>  /// Gets or sets the Label which is displayed next to the field
  /// <span class="code-SummaryComment"></summary>
</span>  public String Label
  {
    get { return (String)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
  }
 
  /// <span class="code-SummaryComment"><summary>
</span>  /// Identified the Label dependency property
  /// <span class="code-SummaryComment"></summary>
</span>  public static readonly DependencyProperty LabelProperty =
      DependencyProperty.Register("Label", typeof(string),
        typeof(FieldUserControl), new PropertyMetadata(""));
 
  #endregion
 
  public FieldUserControl()
  {
    InitializeComponent();
  }
}

A lot of code, isn’t it? It’s all boiler-plate stuff, you just have to live with it (I’d recommend either using code-snippets, or code generation for DPs).

We can now go ahead and bind the label text to this property:

<UserControl x:Class="UserControlExample.FieldUserControl"
    ...>
  <StackPanel Orientation="Horizontal" x:Name="LayoutRoot">
    <TextBlock Text="{Binding Path=Label}"
               Width="100"
               VerticalAlignment="Center"/>
    <TextBox Text="{Binding Path=Shoesize}"
             Width="100"/>
  </StackPanel>
</UserControl>

However, if you compile and run the above code, you’ll find that it doesn’t work. The model property value is still displayed but the label is not.

So why is this?

Bindings have both a source and a target; where the binding framework is responsible for handling change notifications from the source and (optionally) the target, keeping the two synchronized. The bindings in our FieldUserControl have a value for the Path, which specifies the target, but what is the source?

If you create a binding in XAML but do not specify the source (which is probably the most common use case), the source will be set to the DataContext of the control the binding has been specified on. The DataContext is inherited down the visual tree, from each control’s parent to child. The DataContext is most often set to a view model or business / model object, as in our case where the top level control, the MainPage, has its DataContext set to an instance of ModelObject.

As a result, the DataContext for FieldUserControl and all of its child elements is also ModelObject. This is why our Value binding is failing. Value is a property of FieldUserControl, not our model object.

So how do we go about fixing this? Most people’s first reaction is to set the DataContext of the user control to itself (I distinctly recall doing this myself the first time I encountered this problem!). We’ll find out later that this is a mistake – but for now let’s just go with it!

public FieldUserControl()
{
  InitializeComponent();
 
  this.DataContext = this;
}

With the DataContext of the control now set to itself, our label is now working:

However, now our value has disappeared! Again, this is a DataContext issue, the binding in our user control is on a Shoesize property, whilst the DataContext is now the FieldUserControl instance.

This is not such a big problem, we were going to have to change that anyway, a hard-coded binding to the Shoesize property means that we cannot re-use this control to edit other properties of the model object.

So we add another dependency property to our user control. We already have the Label dependency property, we now add a Value property:

public partial class FieldUserControl : UserControl
{
  #region Label DP
 
  ...
 
  #endregion
 
  #region Value DP
 
  /// <span class="code-SummaryComment"><summary>
</span>  /// Gets or sets the Value which is being displayed
  /// <span class="code-SummaryComment"></summary>
</span>  public object Value
  {
    get { return (object)GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
  }
 
  /// <span class="code-SummaryComment"><summary>
</span>  /// Identified the Label dependency property
  /// <span class="code-SummaryComment"></summary>
</span>  public static readonly DependencyProperty ValueProperty =
      DependencyProperty.Register("Value", typeof(object),
        typeof(FieldUserControl), new PropertyMetadata(null));
 
  #endregion
 
  public FieldUserControl()
  {
    InitializeComponent();
 
    this.DataContext = this;
  }
}

This value property is bound to the user control UI as follows:

<UserControl x:Class="UserControlExample.FieldUserControl"
    ...>
  <StackPanel Orientation="Horizontal" x:Name="LayoutRoot">
    <TextBlock Text="{Binding Path=Label}"
               Width="100"
               VerticalAlignment="Center"/>
    <TextBox Text="{Binding Path=Value}"
             Width="100"/>
  </StackPanel>
</UserControl>

The idea here is that the exposed Value property ‘relays’ the value of the binding in our MainPage.xaml, which now has a binding which tells us which model object property is being displayed in our user control:

<Grid>
  <StackPanel Orientation="Vertical"
              HorizontalAlignment="Left"
              Margin="10">
    <local:FieldUserControl VerticalAlignment="Top"
                            Label="Height:"
                            Value="{Binding Height}"/>
    <local:FieldUserControl VerticalAlignment="Top"
                            Label="Shoesize:"
                            Value="{Binding Height}"/>
  </StackPanel>
</Grid>

If you compile and run this code, you will find that … it doesn’t work!

Remember earlier when I said that setting the user control’s DataContext to itself is a mistake? We have just found out why!

The source of a binding is the DataContext of the control it is defined upon. In our MainPage.xaml, we have attempted to bind the Value property of the FieldUserControl to the Height property on our model object. However, the code within the FieldUserControl constructor means that it no longer inherits its parent’s DataContext (i.e. our model object), so this binding does not work.

This problem can be fixed by setting the DataContext of the FieldUserControl’s root element to itself.

Note that the user control has a StackPanel as its root element and that this is named LayoutRoot:

<UserControl x:Class="UserControlExample.FieldUserControl"
    ...>
  <StackPanel Orientation="Horizontal"
              x:Name="LayoutRoot">
    ...
  </StackPanel>
</UserControl>

We change the constructor so that it sets the LayoutRoot DataContext to itself.

public FieldUserControl()
{
  InitializeComponent();
 
  //this.DataContext = this;
  LayoutRoot.DataContext = this;
}

This means that the FieldUserControl still inherits its parent’s DataContext, so bindings to our model object will work. Furthermore, the FieldUserControl and its children all have the FieldUserControl as their DataContext, so their bindings work also:

Finally, we’re done!

If the technique of binding the layout root of the user control to itself is a bit confusing – the following diagram, which shows the visual tree of our simple application, might help:

Again, notice that the DataContext of FieldUserControl is inherited from its parent. This means that any bindings we add to FieldUserControl have the ModelObect as their source.

We can now create multiple instances of FieldUserControl to edit different properties:

<UserControl x:Class="UserControlExample.MainPage"
    xmlns:local="clr-namespace:UserControlExample"
    ...>
 
  <Grid>
    <StackPanel Orientation="Vertical"
                HorizontalAlignment="Left"
                Margin="10">
      <local:FieldUserControl VerticalAlignment="Top"
                              Label="Height:"
                              Value="{Binding Height}"/>
      <local:FieldUserControl VerticalAlignment="Top"
                              Label="Shoesize:"
                              Value="{Binding Shoesize}"/>
    </StackPanel>
  </Grid>
</UserControl>

With an update of the FieldUserControl styling, the result looks like this:

We now have a truly re-useable user control!

As an aside, for bonus points, you can bind the layout root DataContext without any code-behind by using an ElementName binding as follows:

<UserControl x:Class="UserControlExample.FieldUserControl"
             x:Name="parent" ...>
  <StackPanel Orientation="Horizontal"
              DataContext="{Binding ElementName=parent}">
    <TextBlock Text="{Binding Path=Label}"
               Width="100"
               VerticalAlignment="Center"/>
    <TextBox Text="{Binding Path=Shoesize}"
             Width="100"/>
  </StackPanel>
</UserControl>

Or, in WPF you could even use a RelativeSource FindAncestor binding, with AncestorType set to the type of FieldUserControl (but that would just be showing off!).

Hopefully this blog post will help anyone who is confused about how to create user controls which expose properties in WPF or Silverlight.

You can download the source code for the example from here.

Regards, Colin E.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Colin Eberhardt
Architect Scott Logic
United Kingdom United Kingdom
I am CTO at ShinobiControls, a team of iOS developers who are carefully crafting iOS charts, grids and controls for making your applications awesome.
 
I am a Technical Architect for Visiblox which have developed the world's fastest WPF / Silverlight and WP7 charts.
 
I am also a Technical Evangelist at Scott Logic, a provider of bespoke financial software and consultancy for the retail and investment banking, stockbroking, asset management and hedge fund communities.
 
Visit my blog - Colin Eberhardt's Adventures in .NET.
 
Follow me on Twitter - @ColinEberhardt
 
-
Follow on   Twitter   Google+

Comments and Discussions

 
QuestionNice Work Pinmember1.21 Gigawatts4-Aug-14 3:38 
GeneralMy vote 5 star PinmemberSor Emi12-May-14 7:34 
QuestionLast code example: small mistake? PinmemberFlorian Leeber6-May-14 22:06 
QuestionGreat article !! Pinmembersync.gz2-Feb-14 12:23 
QuestionThank you for making my day :) Pinmemberpasx29-Aug-13 15:45 
GeneralMy vote of 5 PinmemberIgalk18-Jun-13 5:02 
QuestionMy vote of 5 Pinmemberolonge12-Jun-13 2:25 
GeneralMy vote of 5 PinmemberAdam786931-Jan-13 8:33 
QuestionReadOnlyProperty ? Pinmemberap8221-Aug-12 1:35 
GeneralMy vote of 5 PinmemberCharalampos120-Aug-12 5:04 
GeneralMy vote of 5 Pinmemberthe_traveller24-Jun-12 16:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.1411022.1 | Last Updated 7 Feb 2012
Article Copyright 2012 by Colin Eberhardt
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid