Jerry Nixon @Work: Let’s code! In WinRT-XAML you cannot bind to a nullable type. Or can you?

Jerry Nixon on Windows

Wednesday, July 2, 2014

Let’s code! In WinRT-XAML you cannot bind to a nullable type. Or can you?

imageIn WinRT, you cannot bind to a nullable type. It’s just the way it is. This is strange when you consider the ComboBox and ToggleButton; they both have an IsChecked property of type bool?. Binding to them with a simple true or false works great. Attempting to bind the third state (null) doesn’t work – it is ignored by the control altogether. You can’t work around it with a converter. And, should you peek at your Output window, you will see a silent error that reads:

Error: Converter failed to convert value of type 'Boolean' to type 'IReference1<Boolean>'; BindingExpression: Path='State' DataItem='App4.MainPage'; target element is 'Windows.UI.Xaml.Controls.CheckBox' (Name='checkBox'); target property is 'IsChecked' (type 'IReference1').

But we are developers! We are the masters of our fates, we are the captains of our souls, the solvers of esoteric business dilemma. Who are they to tell us to what we can and can’t bind? We are born to resolve, facing the impossible amid fanned apathy, flat Mountain Dew, Spiderman t-shirts, stale doughnuts, and cold pizza. We create options out of hopeless limitations. We don’t believe in the no-win scenario. Because with enough time and enough money, we could rewrite all of Windows if we wanted. But, we don’t want to do that, do we? No. We just want to bind to a nullable type. But how?

In XAML, there are three primary ways to add functionality to an existing control. Though this article will be discussing the XAML of Windows Runtime, these fundamental principles are universal across the entirety of the Windows Platform – every incarnation of XAML on the Microsoft platform.

1. Subclass the existing control

class SuperCheckbox: Checkbox {} is awesome. You get all the goodness native to the control with nearly unlimited extensibility. A subclassed control can be added to your Visual Studio Toolbox and enable you to do almost anything. The downside, of course, is that you need to stop using the standard controls and start using your custom controls. It could mean a significant change to your project or a new habit for your development team. It also means that performance problems are always blamed on you. Reality is, not everyone likes this approach. I love it. Custom controls solve 99% of custom problems.

My blog on Custom Controls: here

3. An attached property

Another reason to love XAML: attached properties. These gems allow you to add properties to existing controls without having to subclass them. When those properties wire up events to the control they are frequently referred to as attached behaviors, but that’s just a second term for attached property. These are specially decorated dependency properties, and are as equally simple to implement.

However, attached properties do not initialize until the property changes; this means your logic may lie idle when it should be listening for something. Additionally annoying is that since it’s a static implementation, references to the control or simply storing values in fields can require some heavy lifting and a clear mind while writing them. They are powerful but not simple. And, they don’t have a designer experience – but we aren’t afraid to write a little XAML.

My blog on attached properties: here

2. A custom Blend behavior

Behaviors are almost magical. Designed as a way to add encapsulated functionality to a user interface through the designer at designtime, behaviors implement IBehavior (part of the Blend SDK that installs with Visual Studio) and are best understood as listeners. Behaviors listen for things to happen, events, data changes, or to anything else you want to attach.

Out of the box, you get these behaviors: 1) DataTriggerBehavior – listens for property value changes, 2) EventTriggerBehavior – listens for events to be raised, and 3) IncrementalUpdateBehavior – a special behavior specific to the ControlContentChanging event in virtualized repeaters, like GridView.

The kissing-cousin to the Behavior is the Action. And, if you consider that a Behavior is a listener, then once the thing you are listening for has occurred, you want to do something (sometimes more than one thing). That’s an Action and just as a Behavior implements IBehavior, Actions implement IAction. Both are so simplistic, it’s hard to believe they really work.

My blog on behaviors: here

My blog on Blend: here

Solving the problem

<rant>This would be easier if propdp supported nullables</rant>

image

The problem, at its heart, is that dependency properties don’t properly support nullables – that is to say, not at all. They do, however, support object. For example, creating an IsChecked2 dependency property of type object and converting the result to Nullable<Boolean> for to set the CheckBox.IsChecked property is simple. That is, until you want to use two-way binding – which you do, of course.

Allow me to explain the problem. Let’s pretend you have a boolean property in your ViewModel called IsEnabled. You two-way bind that property to the IsChecked property of your CheckBox. This works perfectly. But to support Nullable<boolean> you introduce a second property, a dependency property, called IsChecked2 of type object. Where is the binding?

Because you need to two-way bind from the IsChecked property on the CheckBox –> to your new IsChecked2 property in your solution, and you need to two-way bind from your IsChecked2 property –> to your IsEnabled property in your ViewModel, you actually need to introduce two new properties, one of them is internal and manually bound to the IsChecked property of the CheckBox.

Setting up a Custom binding

Setting up a code-behind custom binding is easy stuff. Granted, declaring it in XAML is even better. But here’s how you would bind the IsChecked property of the CheckBox to the internal property of your behavior:

image

You would use near-exactly the same syntax for each approach.

In addition to the binding itself, we are handling the initial set of IsChecked to the value bound to our internal property (which will be the property of the ViewModel) – ensuring the render value matches the ViewModel value.

Three solutions

Because this can be solved all three ways, I thought it would educational to create the solution all three ways. My preferred solution remains the subclass approach, but it’s important to point out that the resulting solutions are otherwise identical. The only notable exception I that the subclassed CheckBox and the Blend Behavior are the only with design-time support.

image

Here’s the source for the attached property: on CodePlex

And, here’s how to use it:

image

Please note the syntax requires Enabled. That’s the quirky part. Remember when I said earlier that attached properties don’t have a way to initialize until their property is actually set? Setting up a binding doesn’t count, either! As a result, Enabled is used as the attached behavior’s initializer.

Here’s the source for the blend behavior: on CodePlex

And, here’s how to use it:

image

Please note that there is no design-time experience unless you are in Blend. Then, having your behavior in scope is all you need for it to appear in the toolbox. The, you get full design-time support, including data binding the IsChecked property on the behavior itself. Otherwise, you’re doing it by hand.

Here’s the source for the sub-classed CheckBox: on CodePlex

And, here’s how to use it:

image

Remembering to bind to IsChecked2 is the hard part of this syntax. If you mistakenly bind to the IsChecked property (which is easy to forget) it works fine, just doesn’t support nullables. Switch to IsChecked2 and you get all the goodness you want. Other than that, there’s no syntax change at all.

Let’s consider

Now that you see the XAML syntax for each, the delta is pretty trivial, don’t you think? It’s hard to choose a solution based on XAML syntax alone.

Conclusion

My hope is that these implementations teach you the various approaches, how similar they are, and how different they may be. Hopefully, too, you can take my simplistic implementations and extend them to the nullable scenarios in your own app. And, hopefully, you will share your results back with me.

It’s worth saying, you can get a working project @ Codeplex, too.

Best of luck!