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

WPF and Property Dependencies - Part I

Rate me:
Please Sign up or sign in to vote.
4.76/5 (23 votes)
6 Feb 2008CPOL10 min read 72.9K   746   49   4
An introductory look at WPF data binding and property dependencies

Introduction

One of the most difficult aspects in creating applications from user requirements is the process of capturing the information that pertains to control behaviors. Situations where controls have many dependencies on both data and the state of other controls often lead to spaghetti code. In this article, I am going to look at some of the ways of capturing these requirements and turning them into manageable chunks of XAML and C#.

A Look at Data Binding

For a control to have its data saved somewhere, we can use a variety of methods. For example, we can respond to a Changed event in a textbox...

XML
<TextBox Name="textBox1" TextChanged="textBox1_TextChanged"/>

... and then store the value manually:

C#
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
  myString = textBox1.Text;
}

This method is fairly simple, but it has far too many disadvantages. In particular, if we handle all events for all controls, we'll end up with a lot of functions. And if these functions have any sort of dependencies, they will be unmanageable.

A better way to go about things is to use data binding. Data binding supports the model where a property in a control (such as the Text property of a TextBox) is mapped onto a variable in the code-behind class. When one changes, so does the other. Here's how it works. First, we need to give the enclosing window a Name attribute to identify it by:

XML
<Window x:Class="WpfDataBinding.Window1"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 Title="Window1" Height="300" Width="300" Name="myWindow">

Now that we have our window name, we provide it as a DataContext property to the grid that encloses our textbox:

XML
<Grid>
  <Grid.DataContext>
    <Binding ElementName="myWindow" Path="."/>
  </Grid.DataContext>
  <TextBox Height="23" Name="textBox1" VerticalAlignment="Bottom"/>
</Grid>

The DataContext definition establishes a scope for where the data source (myWindow, in our case) actually works. Inside the data context of our grid, we simply define the data source as myWindow and the path as a ".". It has to be pointed out here that when dealing with data binding, paths typically follow an XPath convention, which makes it easier to navigate the XAML tree.

Anyways, we have our data context. Now, if we want to bind the textbox to a variable, we need to specify a Binding element for the Text property of the textbox:

XML
<TextBox Height="23" Name="textBox1" VerticalAlignment="Bottom">
  <TextBox.Text>
    <Binding Path="MyString"/>
  </TextBox.Text>
</TextBox>

All that remains is to make a property in the code-behind that will store the value:

C#
private string MyString { get; set; }

There are two things to note here. First, as you type new characters into the textbox, the value of MyString will not be updated. It will only be updated when the control loses focus. In order to get the variable updating every time text is altered, we need to change the UpdateSourceTrigger property of the binding. This property determines the conditions under which the values are synchronized. To have the effect we desire, the binding XAML definition needs to be changed to the following:

XML
<TextBox Height="23" Name="textBox1" VerticalAlignment="Bottom">
  <TextBox.Text>
    <Binding Path="MyString" UpdateSourceTrigger="PropertyChanged"/>
  </TextBox.Text>
</TextBox>

The second problem is that when you change the value of MyString in code, the textbox will not be updated (even though the binding is two-way). The reason is that the property must tell the control that its value has changed. The most straightforward way of doing so is using the INotifyPropertyChanged interface, which can be used by classes to notify other components that their internal state has changed. So, to get this working, our window class has to implement INotifyPropertyChanged:

C#
public partial class Window1 : Window, INotifyPropertyChanged

If you let Visual Studio generate the actual event for you, you'll end up with the following code inside the window class:

C#
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion

You now have an event, but it is useful to have a function that actually checks for subscribers and fires the event when something changes. A naïve implementation would look as follows:

C#
private void NotifyPropertyChanged(string propertyName)
{
  if (PropertyChanged != null)
  {
    PropertyChanged(this,
      new PropertyChangedEventArgs(propertyName));
  }
}

Now, using those auto-properties is no longer an option, because the mutator (the property set) has to call this new function we just made. An altered MyString now has to have a backing field. The whole contraption would look as follows:

C#
private string myString = string.Empty;
public string MyString {
  get { return myString; }
  set
  {
    myString = value;
    NotifyPropertyChanged("MyString");
  }
}

If you change MyString now, the Text property of textBox1 will change accordingly.

Encapsulation

The code presented so far might work as an academic example, but it's still very far from real-life use. One of the reasons why it's unrealistic is that, often, developers wrap their properties with specifically designed structures which encapsulate the value, plus some other additional information to boot. For instance, developers might need to keep the same value and output it using different measurement systems (such as Metric vs. Imperial), or they might need a stack of previous values to implement Undo/Redo or the Command pattern. Clearly, our existing functionality is not right for this task, so let's try adjusting it a bit.

First, I'm going to define a simple wrapper class called Property that encapsulates the value we're binding to:

C#
public class Property<T>
{
  private T value;
  public Property() { }
  public Property(T value)
  {
    this.value = value;
  }
  public T Value
  {
    get
    {
      return value;
    }
    set
    {
      this.value = value;
    }
  }
}

As you can see, the class is just a simple wrapper. In real life, you would add extra functionality to make it a bit more useful. Now, let's change the definition of our MyString property in the window class:

C#
private readonly Property<string> myString = new Property<string>(string.Empty);
public string MyString
{
  get
  {
    return myString.Value;
  }
  set
  {
    myString.Value = value;
    NotifyPropertyChanged("MyString");
  }
}

The above solution isn't very smart. Consider what happens if we want, for example, to be notified when the Value inside changes. For instance, what if we want to turn the border of a control a different color depending on whether the property has been changed or not, we'd be in a real bind (no pun intended): we would need to create a new bool property not just inside the Property class, but also inside the window class. And this is just too much work.

To deal with this, let's try going about it in a different way. I'm going to change our Property class in order to incorporate this Changed property. I am also going to implement INotifyPropertyChanged right inside the Property class:

C#
public class Property<T> : INotifyPropertyChanged
{
  private T value;
  private bool changed = false;
  public Property() { }
  public Property(T value)
  {
    this.value = value;
  }
  public T Value
  {
    get
    {
      return value;
    }
    set
    {
      this.value = value;
      NotifyPropertyChanged("Value");
      Changed = true;
    }
  }
  public bool Changed
  {
    get
    {
      return changed;
    }
    set
    {
      changed = value;
      NotifyPropertyChanged("Changed");
    }
  }
  public void Commit() { Changed = false; }

  private void NotifyPropertyChanged(string propertyName)
  {
    if (PropertyChanged != null)
    {
      PropertyChanged(this,
        new PropertyChangedEventArgs(propertyName));
    }
  }
  public event PropertyChangedEventHandler PropertyChanged;
}

We now have a rather lengthy class which does its own notifications. For testing purposes, I've also added a Commit method so that we can reset its Changed state. The property now knows its value, and whether it has been changed. However, it cannot tell us whether the border color will be ordinary or red. We can now write the following...

XML
<TextBox Height="23" Name="textBox1" VerticalAlignment="Bottom">
  <TextBox.Text>
    <Binding Path="MyString.Value" UpdateSourceTrigger="PropertyChanged"/>
  </TextBox.Text>
  <TextBox.BorderBrush>
    <Binding Path="MyString.Changed"/>
  </TextBox.BorderBrush>
</TextBox>

... but the brush option will be meaningless because, guess what, you cannot just convert a boolean to a brush. There are three ways to go about it. One way is to expose a BorderBrush property from our Property class, which is meaningless because this feature might not be required for all types of controls (and Property, as you may have guessed, is meant to be reusable). More importantly, it's meaningless because Property has no business dealing with UI aspects – we need to preserve separation between data and UI. The second option is to expose some kind of MyStringBrush property from the window, doing the conversion each time. This option is even worse because if we do that, window has to subscribe to the Property's notification mechanism and... well, you can imagine how messy it will get. We are left with option #3 – creating a custom converter that changes bools into borders.

First, we define a class that actually does the conversion:

C#
[ValueConversion(typeof(bool), typeof(Brush))]
public class BoolToBrushConverter : IValueConverter
{
  #region IValueConverter Members
  public object Convert(object value, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
  {
    return (bool)value ? Brushes.Red : Brushes.Gray;
  }
  public object ConvertBack(object value, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
  {
    return false;
  }
  #endregion
}

According to MSDN, the converter will not work until we define a namespace for our code. To do so, we add the following line to the Window tag:

XML
xmlns:custom="clr-namespace:WpfDataBinding"  

In the above line, we define our own namespace called custom and associate it with our assembly namespace. There is no need to specify the assembly. Now we add our custom converter to the grid resources in order to be able to use it within the elements of the grid:

XML
<Grid.Resources>
  <custom:BoolToBrushConverter x:Key="btbc"/>
</Grid.Resources>

Finally, we can specify the converter of our value:

XML
<TextBox.BorderBrush>
  <Binding Path="MyString.Changed" Converter="{StaticResource btbc}"/>
</TextBox.BorderBrush>

This is all we need to do. We have just bound a brush to a boolean value through a converter. The backward conversion is not functional (we just return false), because we assume the user will not manipulate the brush.

To summarize, here is what we have learned so far:

  • Properties notify their observers using INotifyPropertyChanged
  • Properties are often wrapped, and bindings to them become slightly elaborate (e.g. MyString.Value instead of just MyString)
  • Bindings can use custom conversions by specifying the Converter property

Binding in Code

There is a fairly annoying problem with these binding definitions: they tie the UI designer and the business logic designer together. In an ideal world, those two roles could synchronize, but in a large-scale development effort, it becomes somewhat impractical to have all those <Binding> tags all over the place. What I mean is, suppose the navigation logic changes and controls start acting differently: we don't want to be modifying XAML elements if it's not our primary job. So, let's remove the MyString bindings and make them in code instead.

First, we make a private field for the converter:

C#
private IValueConverter boolConv = new BoolToBrushConverter();

Strictly speaking, we should have a Singleton here (since spawning converters is pointless), but you can do this as homework. Now that we have the converter, we simply add the following to the constructor:

C#
Binding strBinding = new Binding();
strBinding.Path = new PropertyPath("MyString.Value");
strBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
textBox1.SetBinding(TextBox.TextProperty, strBinding);

Binding boolBinding = new Binding();
boolBinding.Path = new PropertyPath("MyString.Changed");
boolBinding.Converter = boolConv;
textBox1.SetBinding(TextBox.BorderBrushProperty, boolBinding);

All done! The end result is exactly the same as with XAML-based data binding. The reason why I'm showing this code here is because when you have a large number of data interdependencies (coming soon!), writing all this XAML by hand is impractical, and generating it effectively implies doing XSLT or XQuery transformations on XAML code, which is rather complex and is probably not cost effective.

Dependencies

In the previous examples, we relied on INotifyPropertyChanged as if it was some Holy Grail of change notification. Regrettably, it's not, and here's an example why.

Consider a situation where you have an edit box where a person types in her or his age. Below that, you have two radio buttons that allow the person to vote, but only if they are 16 or older:

wpfdatabinding_image1.jpg

The idea is fairly simple – if you're younger than 16, you can't vote. Now, just as with the previous example, we bind the age field to the Value of a Property<int>:

C#
private readonly Property<int> age = new Property<int>();
public Property<int> Age
{
  get
  {
    return age;
  }
}

We can now define a CanVote property in our window class to determine whether a person can vote:

C#
public bool CanVote
{
  get
  {
        return age.Value >= 16;
  }
}

And now we run into a problem: how does CanVote know that age.Value has changed? The problem is, it doesn't. These properties are in different classes, and even though, in theory, CanVote could sign up to listen to NotifyPropertyChanged of age.Value, in practice, this approach doesn't scale and is unrealistic. If they were in the same class, Age could call NotifyPropertyChanged("CanVote") whenever its value changed but, of course, this doesn't scale well either.

Let's generalize the problem a bit: how does property SomeClass.A get notified that OtherClass.B has changed without actually signing up to the other class' NotifyPropertyChanged? My guess is, it cannot; however, if we start doing this sort of manipulation en masse, it will pollute our code so greatly that eventually we'll end up with spaghetti. There has to be a better solution out there.

Identifying Dependencies

In this section, we are going to look at how property dependencies can be identified and made explicit by looking at the IL that they generate. As you can probably remember, the annoying thing about the CanVote property in our sample is that it is unable to notify its own observer about its own changes (which are a result of changes in age.Value). It knows because it uses the property. Here is the body of the get_CanVote function when seen through the IL disassembler:

MSIL
.method public hidebysig specialname instance bool
        get_CanVote() cil managed
{
  // Code size       24 (0x18)
  .maxstack  2
  .locals init ([0] bool CS$1$0000)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldfld      class WpfDataBinding.Property`1<int32> WpfDataBinding.Window1::age
  IL_0007:  callvirt   instance !0 class WpfDataBinding.Property`1<int32>::get_Value()
  IL_000c:  ldc.i4.s   16
  IL_000e:  clt
  IL_0010:  ldc.i4.0
  IL_0011:  ceq
  IL_0013:  stloc.0
  IL_0014:  br.s       IL_0016
  IL_0016:  ldloc.0
  IL_0017:  ret
} // end of method Window1::get_CanVote

If we take lines IL_2 and IL_7 and cut off the double colon :: and everything to the left, we get age and get_Value(), from which it is safe to infer that CanVote at least uses age.Value. It does not necessarily mean that it relies on it, but the fact that it is here must mean something.

Okay, let us assume for a second that we have a magical tool that tells us which properties a particular property is affected by. How would we act on this information in our example? Well, for instance, our window class could do the following:

  1. Sign up to listen on changes in Age:

    C#
    age.PropertyChanged += new PropertyChangedEventHandler(age_PropertyChanged);
  2. In the generated function, do an invocation on the window class:

    C#
    void age_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
      NotifyPropertyChanged("CanVote");
    }

There are two ways of dealing with these dependencies. One is to simply record them somewhere. What I mean is, you just record that A affects B in a configuration file, and then generate some code that acts in the way I've described below: catches the change and does the extra notification. The other, more interesting way is to analyse the code and pull out the dependencies, either by analysing IL or the C# source code. In part II of this article, we shall consider those options and derive a solution that takes care of the property dependency problem once and for all.

History

  • 7th February, 2008: Article published

License

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


Written By
Founder ActiveMesa
United Kingdom United Kingdom
I work primarily with the .NET technology stack, and specialize in accelerated code production via code generation (static or dynamic), aspect-oriented programming, MDA, domain-specific languages and anything else that gets products out the door faster. My languages of choice are C# and C++, though I'm open to suggestions.

Comments and Discussions

 
QuestionInterest and enthusiasm for part II Pin
SWadeT8-Jun-16 11:18
SWadeT8-Jun-16 11:18 
QuestionSecond part is missing Pin
midavik11-Jul-10 22:36
midavik11-Jul-10 22:36 
AnswerRe: Second part is missing Pin
Dmitri Nеstеruk11-Jul-10 23:08
Dmitri Nеstеruk11-Jul-10 23:08 
GeneralRe: Second part is missing Pin
Errr71730-Nov-13 10:06
Errr71730-Nov-13 10:06 

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.