In part 1, we covered how controls inside a TabItem that wasn’t visible aren’t inside the Visual Tree.
Why does this matter? There are some controls that don’t work well when taken in and out of the visual tree. One control in particular is the handy DataField control which wraps up input fields into a nice, standard format with label on the left, validation and tooltip text. Let’s take a look at a slightly different sample. This one uses a ComboBox inside a DataField, which is inside the TabControl. We are also going to be using a bit of data binding to bind the ItemsSource and SelectedItem properties.
Here is the new XAML:
And here is the new codebehind:
Pretty straightforward: a ComboBox with its SelectedItem bound and its ItemsSource bound to a property. The interesting part here is inside the AvailableCharacters property on line 20. This uses a LINQ query to return an enumerable list of characters from the current Type’s name. When run, everything works great if you stay on the first tab:
If you switch to Tab 2, then back, this is where you see the problem.
Our ComboBox has had its selected item cleared. Why? If we put a breakpoint inside the SelectedCharacter set method, we can see why:
When we switch from Tab 2 to Tab 1, someone is setting SelectedCharacter to null. Looking at the call stack, there are two key offenders:
When the tab containing our DataField is activated, it causes its Content to load, which calls the OnContentLoaded method, which eventually causes it to update its bindings. The first binding it comes to is ItemsSource. The downside to this is that when you set the ItemsSource on a ComboBox, it clears the SelectedItem value automatically for you.
But this shouldn’t happen, right? What’s happening? To figure out what’s happening, it’s helpful to know a bit of trivia about Silverlight data binding.
Q. What happens if I forcibly update a data binding expression? For instance, If I continually raise the INotifyPropertyChanged.PropertyChanged event, or if I use BindingExpression.UpdateSource.
A. The binding framework will ask for the current value of your property. If the value is the same, it won’t do anything else.
That last part is the key part here. If the value is the same, the binding framework would do nothing. Using this knowledge, how can we adapt the code to work correctly? Instead of returning a LINQ query from directly inside the property, store the LINQ query in a private member field and return that from the property.
By simply doing that, everything works correctly now. Laziness and a behavior of TabControl combined with the behavior of DataField caused me quite a bit of headache.
Laziness: not using a backing field for my enumerable property because I assumed it would never be called more than once
TabControl behavior: anything inside a non-visible tab aren’t in the Visual Tree
DataField behavior: re-adding to the Visual Tree causes the Content control to have its Load event fire, which causes the DataField to reevaluate all bindings.