Happened upon a Silverlight bug today. In essence, if you have a ComboBox with its SelectedValue bound to a property and set that property to null, the ComboBox throws out your binding. No, really. There’s even a Connect bug about it.
For posterity, here is an incredibly simple reproduction scenario. No view models or anything fancy, just one user control:
This code sets up a ComboBox with the appropriate settings, a TextBlock bound to the same property as the ComboBox’s SelectedValue binding, and a button to clear our selection (by setting said property to null). Before we get into code we have to define our “Item” class – when using SelectedValue and SelectedValuePath, we need to be binding to a set of objects:
Very simple, just a class with a Value property. Now for the codebehind of the user control:
This defines the SelectedValue property (and necessary INotifyPropertyChanged implementation), sets the DataContext to itself, and when the button is clicked it sets the SelectedValue property to null. If you’re familiar with Silverlight, nothing here should look out of place.
When the application is run and the ComboBox selection is changed, the TextBlock’s text updates as expected.
When you click Clear selection, both the ComboBox and TextBlock clear:
Finally, making another selection in the ComboBox demonstrates the issue:
…execution never stops there! This means my binding, which was working before, is either broken or the ComboBox removed it. Because I’m writing this after the fact, I know that the ComboBox, on a null value in its SelectedValue binding, will clear out the SelectedValue binding. In my application, I like the way the view model is structured and the XAML is laid out and I don’t want to change the way things work, so what do I do? If my binding is being deleted on a null value, can I re-add it? Is that a valid workaround?
Yes, and yes (in my opinion). There are other workarounds such as subclassing ComboBox, but I’m of the opinion that much of the Silverlight and WPF framework code wasn’t written with subclassing in mind and it can be more of a pain to subclass than it is to attach a behavior of some sort to an existing class. In addition to this, I personally find it easier to attach a behavior and if the reason for the behavior is ever fixed, I can simply remove the behavior wherever it’s used. Nice and neat.
So instead of subclassing, I’m going to go with behavior attaching, and this is where the Expression SDK comes in. In a previous post, I mentioned Behaviors, Triggers and Actions and mostly dismissed Behaviors because a behavior is essentially a trigger and an action combined. In this case, it makes no sense to separate our trigger (a null value set on a ComboBox) and our action (fixing the binding on said ComboBox) so it makes sense to keep it all together.
To start, what we need to do is whenever the SelectedValue changes, if it’s non-null, store the current binding. If SelectedValue is ever null, we need to restore the SelectedValue binding onto the ComboBox. To do this, we’ll create a class that inherits from Behavior<T>:
This class has two virtual methods that are important to override: OnAttached and OnDetaching. OnAttached is called when the behavior is loaded and attached to an associated element (ComboBox in my case). On Detaching is called when it is being unloaded from an element (and thus is being detached). In our case, we want to handle the ComboBox.SelectedValueChanged method:
In the event handler, we need to cache the binding when it’s available and restore it when it’s been cleared. To do this, we’ll use the FrameworkElement.GetBindingExpression method to get the binding on the SelectedValue property.
The CacheExistingBinding method stores the binding in a local field, and the SelectionChanged handler restores the cached binding if it’s discovered to be null. With this behavior added to our ComboBox:
The bug no longer affects us and the ComboBox continually updates the binding so the TextBlock displays the active selection.