Now this may be common sense once you know it, but as someone still learning WPF this just took me a while to debug so I thought I’d write a blog post about it and perhaps save someone else the same pain.
Create a new WPF application in Visual Studio and you’ll have a Window with a constructor that looks like the following:
public Window1()
{
InitializeComponent();
}
Now if you’re not careful and you initialize any properties you may be binding to AFTER the call to InitializeComponent you’re components aren’t going to bind to the objects because they’ll still be null. It took me some time to debug this, because moving a bound DataContext from the Window to the Grid fixes it and I assumed incorrectly the problem was with the DataContext, not the order of initialization. It fixes it because the Window1 is already initialized by the time the Grid gets initialized.
To explain a bit further, I was setting my DataContext on WIndow1 to itself as follows:
<Window
…
DataContext="{Binding RelativeSource={RelativeSource Self}}"
d:DataContext="{Binding Source={StaticResource SampleDataSource}}">
I’ve included the SampleDataSource for those that haven’t seen them, it’s something that Blend 3 offers. You can find out more about it from Corey Schuman’s blog post. They are definitely something you’ll want to use!
The reason I set the DataContext of the Window to itself is it makes further binding to properties of the window VERY simple, and keeps the XAML code clean. Here is my test ListBox with some simple bindings.
<ListBox HorizontalAlignment="Left" x:Name="listBox1" ItemsSource="{Binding Names}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding FirstName}"/>
<CheckBox IsChecked="{Binding IsValid}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So it’s binding to the public property of Window1 called Names. Names is a generic ObservableCollection<Name>.
/// <summary>
/// Gets or sets the names.
/// </summary>
/// <value>The names.</value>
public ObservableCollection<Name> Names { get; set; }
Here is the Name class which I created in about a minute using Resharper and MyLiveTemplates and GhostDoc:
using System;
using System.ComponentModel;
namespace WpfBindingTest
{
/// <summary>
///
/// </summary>
public class Name : INotifyPropertyChanged
{
private string _firstName;
/// <summary>
/// Gets or sets the first name.
/// </summary>
/// <value>The first name.</value>
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
InvokePropertyChanged( new PropertyChangedEventArgs( "FirstName" ) );
}
}
private bool _isValid;
/// <summary>
/// Gets or sets a value indicating whether this instance is valid.
/// </summary>
/// <value><c>true</c> if this instance is valid; otherwise, <c>false</c>.</value>
public bool IsValid
{
get { return _isValid; }
set
{
_isValid = value;
InvokePropertyChanged( new PropertyChangedEventArgs( "IsValid" ) );
}
}
#region Implementation of INotifyPropertyChanged
[NonSerialized] private PropertyChangedEventHandler _propertyChanged;
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged
{
add { _propertyChanged += value; }
remove { _propertyChanged -= value; }
}
private void InvokePropertyChanged( PropertyChangedEventArgs e )
{
PropertyChangedEventHandler changed = _propertyChanged;
if ( changed != null )
{
changed( this, e );
}
}
#endregion
}
}
Basically this implements a serialization safe version of INotifyPropertyChanged so that changes to the properties can be observed by WPF.
Now the problem I described at the start of this article was created when I modified the constructor for Window1 as:
public Window1()
{
InitializeComponent();
Names = new ObservableCollection<Name>();
}
So the ListBox gets initialized while Names is still null and none of my binding works as expected.
Do it the right way around:
public Window1()
{
Names = new ObservableCollection<Name>();
InitializeComponent();
}
Or initialize the Property via a backing field:
private ObservableCollection<Name> _names = new ObservableCollection<Name>();
/// <summary>
/// Gets or sets the names.
/// </summary>
/// <value>The names.</value>
public ObservableCollection<Name> Names
{
get { return _names; }
set { _names = value; }
}
and everything works as you’d expect.
I think I’ve written enough for one post. If you want to play with things for yourself you can get a copy of my test project from my SkyDrive: WpfBindingTest. It also has a UserControl using DependencyProperties to support binding to the properties of the control.
5 comments:
Not trying to be a smart arse, but you could save yourself hundreds of lines of code and not using databinding at all. Things are much simplier and you don't have weird problems.
It's only "weird" because it's new. Just like learning the page life cycle in ASP.NET.
I look forward to any suggestions you have for alternative ways of doing things, but at the moment I quite like the databinding and the separation of concerns it provides.
I just wanted a record of this here as it's somewhat related.
A detailed explanation of the Initialized vs Loaded events can be found at:
http://tinyurl.com/ycnrbjh
There are many different binding patterns that can be used in a project and you have done a very good job in providing an example with a detailed explanation. Nice job.
Just revisited this article and thought I'd mention what I now do differently.
First I implement INotifyPropertyChanged for the ObservableCollection property. Eg Names in this article.
I also set the DataContext on the root layout element (usually a Grid) instead of the Window.
Post a Comment