I stumbled upon some behavior awhile back that struck me as odd. I had a nullable property in my ViewModel that I was binding to in Silverlight, but I found that whenever the value was cleared out by the user, the property setter was not called.
I narrowed it down to a simple application that has properties for Name and Age, as follows:
1: public string Name
2: {
3: get { return _name; }
4: set
5: {
6: if (value != _name)
7: {
8: _name = value;
9: this.RaisePropertyChanged("Name");
10: }
11: }
12: }
13:
14: public int? Age
15: {
16: get { return _age; }
17: set
18: {
19: if (value != _age)
20: {
21: if (value.HasValue && (value != 30))
22: {
23: value = 30;
24: }
25:
26: _age = value;
27: this.RaisePropertyChanged("Age");
28: }
29: }
30: }
I then have XAML binding to these properties like this:
1: <TextBox Grid.Column="0" Grid.Row="1" Text="{Binding Name, Mode=TwoWay}" />
2: <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Age, Mode=TwoWay}" />
Then, I followed these steps, getting the stated results:
- Type in a Name, hit [Tab] – Get a PropertyChanged event for “Name” with the name I typed in
- Type in an Age, hit [Tab] – Get a PropertyChanged event for “Age” with the age I typed in
- Clear out the Name, hit [Tab] – Get a PropertyChanged event for “Name” with an empty string
- Clear out the Age, hit [Tab] – Nothing!
As it turned out, the property setter for Nullable<int> Age doesn’t get called when the value in the textbox is empty. Nothing converts empty string into a null. The interesting part of this is that if I had a button on the form, invoking a method on my ViewModel, when that method is invoked, the ViewModel would still have the original age value that was supplied, not knowing that the textbox had been cleared out.
I found this to be quite disturbing and I mentioned it to a couple of people, and they too were surprised by this behavior. But when I talked to Mitsu Furuta about it, he set me straight. He explained that Binding takes a very safe approach to calling property setters. Unless it knows the property setter will succeed*, it won’t call it. Therefore, Binding won’t call the property setter for blank values when it thinks the value is supposed to be something that it cannot supply a blank to. I realized that Binding cannot supply null to the property setter because the value isn’t really null, but rather an empty string.
Mitsu then showed me how to get the behavior that I wanted: create a NullableValueConverter, like this:
1: public class NullableValueConverter : IValueConverter
2: {
3:
4: #region IValueConverter Members
5:
6: public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
7: {
8: return value;
9: }
10:
11: public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
12: {
13: if (string.IsNullOrEmpty(value.ToString()))
14: return null;
15:
16: return value;
17: }
18:
19: #endregion
20: }
Very Simple! Now, I just change my markup to use the converter. This is a 2-step process where I first have to define the converter in my resources (can be done once, application-wide) and then specify that converter in my binding.
1: <UserControl.Resources>
2: <local:NullableValueConverter x:Key="NullableValueConverter" />
3: </UserControl.Resources>
4:
5: ...
6:
7: <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Age, Mode=TwoWay, Converter={StaticResource NullableValueConverter}}" />
Note that when you bind to a property that is a string, and you clear the value out, the property setter is called with an empty string, not with a null. So you could use this converter for string bindings too, allowing you to get null in your property setter if desired for consistency.
This behavior and solution work the same in both WPF and Silverlight 2. If you’d like to tinker with this, I have uploaded a demonstration solution here (VS 2008). It has both a Silverlight (Ag) project and a WPF project. Both of them are using the NullableValueConverter, but if you take that converter off of the binding, you’ll experience the behavior that could easily lead to bugs in applications.
Of course, now that I understand this behavior better and know that value converters are the key, I found a plethora of resources that discuss this.
* Saying that Binding “never” fails is hyperbole. I’m sure you can make it fail if you try to.