WPF Binding, INotifyPropertyChanged and Linq
This is the first of a couple of tips I would like to share reguarding WPF. WPF binding is extremely powerful, but you are bound to run into a few issues, especially if, like myself, you have no WinForms experience. As I was writing my small LiveSpaceToBlogML GUI, I used binding in order to populate an object called ConversionOptions, which pretty much held all the data on the form.
The form look something like this (a pretty simplified version, in order to focus on what matters):
<Window x:Class="LiveSpaceToBlogML.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">
<Window.Resources>
<local:ConversionOptions x:Key="Options" BlogName="MyBlog" SecretWord="MySecret"/>
</Window.Resources>
<StackPanel DataContext="{Binding Source={StaticResource Options}}">
<TextBlock >Your Windows Live Spaces blog address:</TextBlock>
<TextBox Text="{Binding Path=BlogName}"/>
<TextBlock >Your Windows Live Spaces e-mail publishing secret word:</TextBlock>
<TextBox Text="{Binding Path=SecretWord}"/>
<TextBlock>Output file:</TextBlock>
<TextBox Text="{Binding Path=OutputPath}"/>
<Button Content="Convert!" x:Name="Convert"
HorizontalAlignment="Center" Click="Convert_Click"/>
</StackPanel>
</Window>
As you can see I am binding the StackPanel and text-boxes inside of it to an object of type ConversionOptions. At first, that object looked something like this:
public class ConversionOptions
{
public string BlogName { get; set; }
public string OutputPath { get; set; }
public string SecretWord { get; set; }
}
As you can see, pretty simple. Just three properties, and C# 3.0 new property syntax makes things pretty short for us. No need to actually declare the private fields and all. Anyway, this works nice enough and when the user clicks "Convert!" I have a ConversionOptions object filled with all the data from the text-boxes.
But something is missing. I want to be able to set default values on the text-boxes, by changing my Options object, and not the controls themselves. In fact, what I need is for the binding to work in the other direction. Changes to the ConversionOptions object should be reflected in the controls. Digging a little, I found the useful INotifyPropertyChanged interface, which apparently has been with us since the WinForms days. This interface has only one member - the PropertyChanged event, which you are supposed to call whenever a property changes. This, in turn, will notify WPF to update the controls on the screen.
Here's a simple implementation for my ConversionOptions:
public class ConversionOptions : INotifyPropertyChanged
{
private string _blogName;
public string BlogName
{
get { return _blogName;}
set
{
_blogName = value;
OnPropertyChanged("BlogName");
}
}
private string _secretWord;
public string SecretWord
{
get { return _secretWord; }
set
{
_secretWord = value;
OnPropertyChanged("SecretWord");
}
}
private string _outputPath;
public string OutputPath
{
get { return _outputPath;}
set
{
_outputPath = value;
OnPropertyChanged("OutputPath");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
As you can see, I am calling the PropertyChanged event whenever a property changes. There were two annoying things about writing this class:
- I had to give up the cool property syntax. This was especially annoying as the new "prop" snippet only supports the short syntax, so I actually had to write the properties by myself (ReSharper doesn't support C# 3.0 yet, and that makes me sad).
- I had to name my properties in strings. That's never fun to do, as you don't get Refactoring (renaming a property will not change the string as well), intellisense and all that jazz. Strings suck.
This reminded me of a cool post I once read, by Jafar Husain. This extremely clever post talks about my problem exactly, and provides a great Linq solution. He uses the Linq Expression engine in order to enable access to property names in a strongly typed manner. This is the method Jafar wrote:
public static class SymbolExtensions
{
public static string GetPropertySymbol<T,R>(this T obj, Expression<Func<T,R>> expr)
{
return ((MemberExpression)expr.Body).Member.Name;
}
}
I won't explain it here, you can read about it in Jafar's post. The important thing is, this enabled me to change my class to this:
public class ConversionOptions : INotifyPropertyChanged
{
private string _outputPath;
public string OutputPath
{
get { return _outputPath;}
set
{
_outputPath = value;
OnPropertyChanged(o => o.OutputPath);
}
}
private string _blogName;
public string BlogName
{
get { return _blogName;}
set
{
_blogName = value;
OnPropertyChanged(o => o.BlogName);
}
}
private string _secretWord;
public string SecretWord
{
get { return _secretWord; }
set
{
_secretWord = value;
OnPropertyChanged(o => o.SecretWord);
}
}
protected virtual void OnPropertyChanged<R>(Expression<Func<ConversionOptions, R>> propertyExpr)
{
OnPropertyChanged(this.GetPropertySymbol(propertyExpr));
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
As you can see, the strings are no more! All I had to do is reference Jafar's GetPropertySymbol extension method, and add an overload to my own OnPropertyChanged method. The overload accepts an expression. Every property passes it a delegate that takes a ConversionOptions object and returns the property. In this manner, GetPropertySymbol can extract the property name for the method. And it's all type-safe, refactoring and intellisense friendly. Thank you Linq, and thank you Jafar.
Now everything works. We have two-way binding, and I can count on the fact that my window and my data are always consistent. A couple of notes for the end:
- I have no idea what is the performance for GetPropertySymbol. I have a feeling it might be rather reflection-like slow, but I need to study the issue more thoroughly to tell you for sure.
- The version of LiveSpaceToBlogML that I uploaded to this blog does uses the standard way of calling OnPropertyChanged. The reason being that I targeted the .NET 3.0 Framework when I built it, and using Linq requires .NET 3.5.
I will conclude by recommending again that you read Jafar's blog. He has some really pretty (or handsome?) Linq-related code there.