Click here to Skip to main content
15,867,704 members
Articles / Desktop Programming / WPF
Article

Exploring the use of Dependency Properties in User Controls

Rate me:
Please Sign up or sign in to vote.
4.44/5 (12 votes)
11 Jul 2011CPOL8 min read 112.9K   3K   24   3
This article shows how to access WPF Dependency Properties used to form a User Control.

Sample Image

Introduction

Suppose you have a WPF User Control that is composed of other WPF controls some of which have Dependency Properties (DPs) that you'd like to initialize and data bind to. The main point is that these DPs are already defined in the sub-controls. This article explores various ways to make use of these and highlights the various limitations of each method.

Background

I actually started off writing another article exploring the different ways to implement a customized ComboBox. One of these was a User Control. I'd been using DPs with the various approaches and ran into an issue with initializing a DP within a sub-control of a User Control from XAML. This required accessing the sub-control via the User Control's Content property. This didn't appeal as it meant that anybody using the control would have to have intimate knowledge of its structure, which seemed to violate the rule of encapsulation. Therefore I thought I'd experiment with other approaches and see what the rest of CodeProject and Internet had to offer. The result is this article.

Using the code

The various approaches are shown by examining different bits of code. These all follow the same format of a basic WPF application containing MainWindow.xaml with a default code-behind page and a User Control called UserControl1. The User Control is the same for each version, being a ComboBox specialzied to allow the selection of a red, green, or blue brush. This is then bound to a label which displays the text 'Look at me' in the selected colour. Depending on the sample, the code-behind will be the default or contain additions. The accompanying zip is a VS2010 solution containing each version as a separate project. A screenshot of the application is shown at the top.

Firstly, let's look at a basic implementation of the User Control. This is a very simple customization which uses a ComboBox to display a list of three colours: Red, Green, and Blue. The ComboBox is the User Control's Content, and the ComboBox's template for displaying individual items is overridden for displaying instances of SolidColorBrush as a 20x20 rectangle. The ComobBox is then bound to an Array of SolidColorBrushes created as a resource within the User Control. There is absolutely nothing exciting about this. There is no additional code-behind other than that which is generated by default. In the solution, this is the Initial project.

XML
<UserControl x:Class="Initial.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <x:Array x:Key="SomeBrushes" Type="{x:Type SolidColorBrush}">
            <SolidColorBrush>Red</SolidColorBrush>
            <SolidColorBrush>Green</SolidColorBrush>
            <SolidColorBrush>Blue</SolidColorBrush>
        </x:Array>

    </UserControl.Resources>
    <ComboBox Name="combo" ItemsSource="{StaticResource SomeBrushes}">
        <ComboBox.ItemTemplate>
            <DataTemplate DataType="SolidColorBrush">
                <Rectangle Width="20" Height="20" Fill="{Binding}"/>
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>
</UserControl>

The next section of XAML to look at is that of the main window. This has an instance of the UserControl1 and a Label to which the foreground colour is bound to the SelectedItem DP of the ComboBox embedded within the User Control.

XML
<Window x:Class="Initial.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:Initial"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel Orientation="Horizontal" 
      HorizontalAlignment="Left" VerticalAlignment="Top">
        <src:UserControl1 x:Name="UC1" 
          HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Label Content="Look at me" 
          Foreground="{Binding ElementName=UC1,Path=Content.SelectedItem}"/>
    </StackPanel>
</Window>

It is the last part of the previous sentence that is less than desirable. In order to enable the binding, the Label has to know the control type embedded within the User Control. This violates the principle of encapsulation. In some ways, it would be good if WPF/XAML would allow the content of User Controls to be opaque or private, much like C++ and C# allowing a class author to declare implementation details private but provide a public interface; quite possibly with just Dependency Properties forming the majority of the public interface.

When the project is run, the ComoBox is initialized in a blank state. This is because the SelectedIndex property has not been set. Given that a binding could be established to the SelectedItem property, it would appear that adding Content.SelectedIndex="0" to src:UserControl would work. However, this does not. The most likely reason is that whereas the bindings happen at run time and the specified path is a string which is used to find the underlying property via Reflection, when setting a property, this must be known at compile time. This is the other issue which led to looking at different ways to access Dependency Properties within User Controls.

Moving on to the first alternative implementation, which is the MakeDPInherit project. The main difference is additions to the code-behind file for UserControl1, which is shown below:

C#
public partial class UserControl1 : UserControl
{
    public static readonly DependencyProperty SelectedIndexProperty;

    static UserControl1()
    {
        // The DP must be set to inherit at the UserControl1 type level.
        SelectedIndexProperty = ComboBox.SelectedIndexProperty.AddOwner(
          typeof(UserControl1), new FrameworkPropertyMetadata() { Inherits = true });

        // This line is necessary
        ComboBox.SelectedIndexProperty.OverrideMetadata(typeof(ComboBox), 
          new FrameworkPropertyMetadata() { Inherits = true });

        // NOTE: The metadata at both levels needs to be set
        // to inherit so that the DP value is inherited
    }

    public int SelectedIndex
    {
        get { return (int)GetValue(SelectedIndexProperty); }
        set { SetValue(SelectedIndexProperty, value); }
    }

    public UserControl1()
    {
        InitializeComponent();
    }
}

The change is to allow the SelectedIndex property to be set. As this dependency property already exists on the underlying ComboBox, the desire is to make this visible directly from the control. This is achieved by having the control register as an owner. Just doing this alone however will not result in the SelectedIndex being set properly. This is because by default the SelectedIndex property does not inherit the value set on the same DP at a higher level. To enable this as well as setting the Inherits property of FrameworPropertyMetadata for the registration at the UserControl level, it was also necessary to override the existing metadata at the ComboBox level; otherwise the setting would not propagate downwards. These changes means the following in MainWindow.xaml now work.

XML
<src:UserControl1 x:Name="UC1"  HorizontalAlignment="Center" 
        VerticalAlignment="Center" SelectedIndex="0"/>

The amount of code required to enable the use of SelectedIndex combined with the need to modify the metadata of the underlying Dependency Property prompted a search for a simpler mechanism. This is contained in the Passthru project. This contains no further changes to MainWindow.xaml and UserControl1.xaml, but the code-behind for UserControl1 becomes:

C#
public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
    }

    public int SelectedIndex
    {
        get { return ((ComboBox)Content).SelectedIndex; }
        set { ((ComboBox)Content).SelectedIndex = value; }
    }
}

All this approach does is provide a publically accessible setter and getter for the underlying dependency property. This is essentially an application of the proxy pattern. This is significantly simpler and for a moment appears to be the perfect solution. However, whilst it solves the issues with setting SelectedIndex and allows the UserControl to be the target of a data binding, problems arise when it is the source of data binding.

In the examples shown so far, UserControl1 has not actually been the source of data binding. The line:

XML
<Label Content="Look at me" 
       Foreground="{Binding ElementName=UC1,Path=Content.SelectedItem}"/>

which establishes the binding of the selected colour to the foreground of the Label is between the Dependency Property within the ComboBox that is embedded within the UserControl; i.e., exactly the situation that we're trying to avoid.

If an additional control is added to MainWindow.xaml, in this case a slider which creates a binding between its own current value and the SelectedIndex property, things don't work too well. What happens is that any update made to the Slider is reflected in the ComboBox but despite the two-way binding, changes made to the ComboBox are not reciprocated. This is because the SelectedIndex accessor is not a proper Dependency Property so when the underlying Dependency Property is modified, these updates are not communicated to the binding as the binding is to a public property on UserControl1 which does not implement INotifyPropertyChanged.

XML
<Slider Height="23" Name="slider1" Width="100" Minimum="0" Maximum="2" 
       Value="{Binding ElementName=UC1, Path=SelectedIndex, Mode=TwoWay}"/>

A possible workaround might be to move the target of binding to UserControl1, keeping it as two-way, e.g.:

XML
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top">
    <src:UserControl1 x:Name="UC1"  HorizontalAlignment="Center" VerticalAlignment="Center" 
       SelectedIndex="{Binding ElementName=slider1, Path=Value, Mode=TwoWay}"/>    
    <Label Content="Look at me" 
      Foreground="{Binding ElementName=UC1,Path=Content.SelectedItem}"/>        
    <Slider Height="23" Name="slider1" Width="100" Minimum="0" Maximum="2" Value="0"/>
</StackPanel>

Note how initialization is by setting Value="0" on slider1.

However, this causes a compilation error as bindings can only be established against Dependency (and Attached) properties whereas in this case, SelectedIndex is just a standard property. Therefore, in order to support binding properly, there is no choice but to use an actual Dependency Property of some form. As such, the MakeDPInherit sample is worth revisiting.

Applying the change above to this example does not cause any compilations but nor does it work! The problem this time is that even with the inheritance property set in metadata, for instances of types in a hierarchy that implements the Dependency Property (via AddOwner for all but types that actually register it), if the value is set lower down in the hierarchy, then this overrides any value set higher. In this case, when the selection is changed in the ComboBox, this only changes the value of the Dependency Property instance of the ComboBox and the value does not propagate upwards, to UserControl1 in this case. As the binding is made against the Dependency Property on UserControl1, there is no change to report. Close, but not quite there!

Whilst researching this, the following CodeProject article was discovered. This is similar to the previous mechanism but rather than adding an additional owner for a Dependency Property, it created a new one but with the same name. The additional step was to create an internal binding between the new registered Dependency Property and the one on the sub-control. This technique is shown below. This is present in the EnableBinding sample project.

C#
public partial class UserControl1 : UserControl
{
    public static readonly DependencyProperty SelectedIndexProperty;

    static UserControl1()
    {
        SelectedIndexProperty = DependencyProperty.RegisterAttached("SelectedIndex", 
          typeof(int), typeof(UserControl1), 
          new FrameworkPropertyMetadata() { BindsTwoWayByDefault = true });
    }

    public int SelectedIndex
    {
        get { return ((ComboBox)Content).SelectedIndex; }
        set { ((ComboBox)Content).SelectedIndex = value; }
    }

    public UserControl1()
    {
        InitializeComponent();

        Binding b = new Binding("SelectedIndex");
        b.Source = this;
        b.Mode = BindingMode.TwoWay;

        combo.SetBinding(ComboBox.SelectedIndexProperty, b);
    }

    public IEnumerable ItemsSource
    {
        get { return ((ComboBox)Content).ItemsSource; }
        set { ((ComboBox)Content).ItemsSource = value; }

    }
}

Another change compared to the previous examples is the addition of the Passthru style implementation for the ItemsSource Dependency Property. This is to show that this rather important property can be used in this fashion. As it stands, it can't be bound to, but it's fine for loading a static resource. As such, the Array of SolidColorBrush has been moved to MainWindow.xaml and ItemsSource referenced here, as can be seen below:

XML
<Window x:Class="EnableBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:EnableBinding"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <x:Array x:Key="SomeBrushes" Type="{x:Type SolidColorBrush}">
            <SolidColorBrush>Red</SolidColorBrush>
            <SolidColorBrush>Green</SolidColorBrush>
            <SolidColorBrush>Blue</SolidColorBrush>
        </x:Array>
    </Window.Resources>
        <StackPanel Orientation="Horizontal" 
                   HorizontalAlignment="Left" VerticalAlignment="Top">
            <src:UserControl1 x:Name="UC1"  HorizontalAlignment="Center" 
               VerticalAlignment="Center" 
               ItemsSource="{StaticResource SomeBrushes}" SelectedIndex="0"/>
            <Label Content="Look at me" 
               Foreground="{Binding ElementName=UC1,Path=Content.SelectedItem}"/>
            <Slider Height="23" Name="slider1" Width="100" Minimum="0" 
               Maximum="2" 
               Value="{Binding ElementName=UC1, Path=SelectedIndex, Mode=TwoWay}"/>
        </StackPanel>
</Window>

In order to establish the two-way binding between the Slider and UserControl1, it doesn't matter which control the binding is created on as both fields are Dependency Properties. In the project, both versions are provided.

Note that in the solution, there is an additional project that just shows the original example modified to use the synchronized Dependency Property mechanism. This is entitled MakeDPSync.

Points of interest

The use of a UserControl to provide the custom control is probably quite wrong. Of all the mechanisms available from WPF, this is the most ill-suited to this particular application, and I wouldn't actually use it.

None of these mechanisms is anywhere near perfect as they all involve some sort of compromise, be it lack of functionality or the addition of what appears to be code duplicating the underlying Dependency Properties implementation. Additionally, if multiple Dependency Properties need supporting, then as the required code is closely tied to the actual names of the Dependency Properties, a generic solution is difficult to implement.

If a UserControl must be used and Dependency Properties of sub-controls need modifying, then as this article shows, there are various options available. If a full binding support is required, then the only real option is to use the final approach, but if the requirements are only that the Dependency Properties be set (either at compile time or as binding target), then the mechanism shown in the Passthru sample will suffice.

History

  • 9 July 2010 - First version.

License

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


Written By
Team Leader
United Kingdom United Kingdom
My day job is mostly working in C++ with a bit of C#. I write a fair amount of command line based tools and really wish they could have a GUI front-end to them hence why I spend my spare time working with WPF.

I started a blog few years back but didn't do a lot with it. I've started describing some of the interesting programming things I come across on it. Please take a look.

Comments and Discussions

 
QuestionBetter described in the following article Pin
borisbergman3-Oct-14 0:00
borisbergman3-Oct-14 0:00 
GeneralMy vote of 3 Pin
maryam-NSR12-Jan-14 2:14
maryam-NSR12-Jan-14 2:14 
that good
GeneralExcellent article Pin
sha409620-Oct-11 3:03
sha409620-Oct-11 3:03 

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

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