Ioannis Panagopoulos blog

Tutorials on HTML5, Javascript, WinRT and .NET

User Control with a Business Object and Dependency Properties

by Ioannis Panagopoulos

It's been a while... Changes in my professional dependencies have kept me quite preoccupied. But I am back today with another kind of dependencies the ones that have to do with properties and live in the WPF world.

In an application, there is often the need of implementing a user control that uses a business object and is responsible for providing several manipulation mechanisms on its properties. The User Control – Business Object pair is an approach that isolates the presentation logic for the business object in one place and therefore is very flexible for modifications.

In this post I will demonstrate how this is done in WPF and some possible glitches that may occur for the ones that come from the WinForms approach.

Now suppose that you have the following business object (Person) in your application which holds the first name and last name of a person:

public class Person:System.ComponentModel.INotifyPropertyChanged

{

    private string _firstName = "";

    public string FirstName

    {

        get { return _firstName; }

        set

        {

            _firstName = value;

            if (PropertyChanged != null)

                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("FirstName"));

        }

    }

 

    private string _lastName = "";

    public string LastName

    {

        get { return _lastName; }

        set

        {

            _lastName = value;

            if (PropertyChanged != null)

                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("LastName"));

        }

    }

 

    #region INotifyPropertyChanged Members

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    #endregion

}


Note that the business object already implements the INotifyPropertyChanged interface in order to allow the Binding mechanism to use it.

We want to implement a User Control that uses a Person business object and provides two TextBoxes one for the first name and one for the last name. The User Control should look like this:

And the corresponding XAML code is:

<UserControl x:Class="WPFDPTests.UCPersonEdit"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <StackPanel x:Name="StackPanelPersonInfo" Orientation="Horizontal">

        <Label Content="First Name" VerticalAlignment="Center"/>

        <TextBox x:Name="TextBoxFirstName" Text="{Binding Path=FirstName}" VerticalAlignment="Center" Margin="4" MinWidth="120"/>

        <Label Content="Last Name"  VerticalAlignment="Center"/>

        <TextBox x:Name="TextBoxLastName" Text="{Binding Path=LastName}" VerticalAlignment="Center" Margin="4" MinWidth="120"/>

    </StackPanel>

</UserControl>


In XAML we have defined two data bound TextBoxes, one for the last name and one for the first name. The code-behind file is as follows:

public partial class UCPersonEdit : UserControl

{

    private Person _boundPerson = null;

    public Person BoundPerson

    {

        get { return _boundPerson; }

        set { _boundPerson = value; this.DataContext = value; }

    }

    public UCPersonEdit()

    {

        InitializeComponent();

    }

}


That is, we have implemented a new property for the User Control that holds the controlling Business Object and whenever the BO is changed we reset the DataContext property of the User Control to the new BO for the bindings to work.

To test this functionality, we implement a Window as follows:

<Window x:Class="WPFDPTests.WindowPersonsEdit"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:WPFDPTests"

    Title="User Control - Business Object Binding (c) 2009 - http://www.progware.org/Blog/" Height="153" Width="452">

    <StackPanel>

        <local:UCPersonEdit x:Name="UCPersonEdit1" Margin="4"/>

        <local:UCPersonEdit x:Name="UCPersonEdit2" Margin="4"/>

        <local:UCPersonEdit x:Name="UCPersonEdit3" Margin="4"/>

    </StackPanel>

</Window>


And the code-behind file:

public WindowPersonsEdit()

{

    InitializeComponent();

    Person TestPerson1 = new Person() { FirstName = "Ios", LastName = "Panlos" };

    this.UCPersonEdit1.BoundPerson = TestPerson1;

    this.UCPersonEdit2.BoundPerson = TestPerson1;

    this.UCPersonEdit3.BoundPerson = TestPerson1;

}


Pretty straightforward. We expect to run the application and have all the textboxes filled with the contents of the TestPerson1 BO. Moreover, we expect to be able to change, say, the first name in a textbox and see this change reflected to the other “first name” textboxes as well:

Download Solution 1(51,56 kb)


Remark #1.

Does the User Control really needs to carry the reference to the Business Object in the Property BoundPerson?

The answer is that in cases where the User Control is just used as a way of grouping visual elements that are bound to a single business object (like our case), this is not necessary. We could completely omit the BoundPerson property and use the following code in the window’s constructor:

public WindowPersonsEdit()

{

    InitializeComponent();

    Person TestPerson1 = new Person() { FirstName = "Ios", LastName = "Panlos" };

    this.UCPersonEdit1.DataContext = TestPerson1;

    this.UCPersonEdit2.DataContext = TestPerson1;

    this.UCPersonEdit3.DataContext = TestPerson1;

}


As you can see we play with the DataContext inheritance. That is, the user control has its data context property set to the business object. The StackPanel in the User Control does not have any DataContext property set and neither have the two TextBoxes. Therefore the first available DataContext property for the User Control’s bindings in the textboxes is the one supplied by the user control itself:

 


Of course in cases where the BO is used inside the User Control for some other calculations this approach cannot be applied.

Download Solution 2(50,87 kb)

Now, let us forget Remark #1 and return to the case where we actually need the BoundPerson BO reference there. And we want this BoundPerson property to be data bound to a Business Object when we use the control.

For testing purposes our goal is as follows:


Meaning that we want our BoundPerson property to be bound to a Person object in the main Window. Note that in normal circumstances the User Control handles the visualization of a BO which is part of a larger BO in the Window. The code-behind file for the Window looks now like that:

private Person _thisPerson = new Person() { FirstName = "Ios", LastName = "Panlos" };

public Person ThisPerson

{

    get { return _thisPerson; }

    set { _thisPerson = value; }

}

public WindowPersonsEdit()

{

    InitializeComponent();

    this.UCPersonEdit1.DataContext = this;

    this.UCPersonEdit2.DataContext = this;

    this.UCPersonEdit3.DataContext = this;

}


And we modify XAML as follows:

<StackPanel>

     <local:UCPersonEdit x:Name="UCPersonEdit1" BoundPerson="{Binding Path=ThisPerson}" Margin="4"/>

     <local:UCPersonEdit x:Name="UCPersonEdit2" BoundPerson="{Binding Path=ThisPerson}" Margin="4"/>

     <local:UCPersonEdit x:Name="UCPersonEdit3" BoundPerson="{Binding Path=ThisPerson}" Margin="4"/>

 </StackPanel>


But the dreadful blue curly line appears to haunt us just below the Binding and this is due to the fact that in WPF Data Binding works only with Dependency Properties. Therefore we need to modify BoundPerson and make it a dependency property.

No problem a little googling for DPs and there it is:

public static readonly DependencyProperty BoundPersonProperty =

    DependencyProperty.Register("BoundPerson", typeof(Person), typeof(UCPersonEdit),

    new FrameworkPropertyMetadata(new PropertyChangedCallback(OnBoundPersonChanged)));

public Person BoundPerson

{

    get { return (Person)GetValue(BoundPersonProperty); }

    set { SetValue(BoundPersonProperty,value); }

}

public UCPersonEdit()

{

    InitializeComponent();

}


Ok. Lots of changes. First there is no need for the private field. Second the BoundPerson property is now a mere accessor for the actual dependency property. But wait a second, what is that OnBoundPersonChanged definition there? Well, if you remember before, whenever the _boundPerson field was set we were also setting the DataContext of the User Control to point to the new _boundPerson. We cannot do the same in the present setter since we have no idea who is going to set the dependency property. In other words, the property BoundPerson is only going to be used from out code. WPF will propably be using SetValue directly. Therefore we cannot place the DataContext there.

We need to place it in a method that gets called whenever the dependency property is changed and this is all about the OnBoundPersonChanged callback. Actually, the implementation is as follows:

public static void OnBoundPersonChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)

{

    (sender as UCPersonEdit).DataContext = args.NewValue;

}


Splendid! Now everything should be working. The project compiles. We run it and all TextBoxes are empty. There is something missing. The Binding points to the BO ThisPerson. The DataContext of the UserControl is set to the Window so the binding should be able to find ThisPerson. So, when the binding provides ThisPerson, the OnBoundPersonChanged is called to set the DataContext of the UserControl to ThisPerson, in order for the FirstName and LastName paths to find it. But wait a second? Did we just set the UserControl’s DataContext twice? That is exactly what we did by mistake and now nothing works.

In fact, the solution is quite simple. We will provide the DataContext of the StackPanel in the callback:

public static void OnBoundPersonChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)

{

    (sender as UCPersonEdit).StackPanelPersonInfo.DataContext = args.NewValue;

}


Test it now and it works.

So keep in mind that Data Binding works only with dependency properties, dependency properties do not support the INotifyPropertyInterface (if someone can even say that) and DataContext inheritance may be a blessing in terms of creating simple code but it turns into a curse when debugging data bindings.

Download Solution 3 (53,79 kb)

kick it on DotNetKicks.com
blog comments powered by Disqus
hire me