2011-12-09

Databinding to enum properties in WPF MVVM

This post assumes you're familiar with MVVM (Model-View-View Model) as
applied to WPF. If you're familiar with WPF but not MVVM, I found Jason Dolinger's presentation a very nice introduction. If
you're not familiar with WPF, you'd better skip this altogether, as it's too
esoteric.

Let's start off with a trivial UI, like so:

<Window x:Class="WpfApplication2.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:local="clr-namespace:WpfApplication2"

Title="MainWindow"

Height="350"

Width="525">

<Grid>

<TextBlock Height="23"

HorizontalAlignment="Left"

Margin="12,12,0,0"

VerticalAlignment="Top"

Text="{Binding Foo}" />

<RadioButton Content="Bar"

Height="16"

HorizontalAlignment="Left"

Margin="12,35,0,0"

VerticalAlignment="Top" />

<RadioButton Content="Baz"

Height="16"

HorizontalAlignment="Left"

Margin="12,50,0,0"

VerticalAlignment="Top" />

<RadioButton Content="Quux"

Height="16"

HorizontalAlignment="Left"

Margin="12,65,0,0"

VerticalAlignment="Top" />

</Grid>

</Window>

As is usual with MVVM, the
code-behind for this is extremely bare-bones (I'm ignoring stuff like
dependency injection for purposes of illustration):

publicpartialclassMainWindow : Window {

privateViewModel viewModel = newViewModel();

public MainWindow() {

DataContext = viewModel;

InitializeComponent();

}

}

And let's start off with a
view model that looks like this:

enumFoo { None, Bar, Baz, Quux };

classViewModel : INotifyPropertyChanged {

Foo foo;

publicFoo Foo {

get { return foo; }

set {

if (foo != value) {

foo = value;

PropertyChanged(this, newPropertyChangedEventArgs("Foo"));

}

}

}

publiceventPropertyChangedEventHandler PropertyChanged;

}

The problem, as you might have
guessed, is how we are supposed to bind the radio buttons to our view model in
such a way that the check state corresponds to the enum value and vice versa.

A standard WPF solution for situations like this, if we ignore MVVM for a
moment, is to implement a value converter. We need something that can map an
enum constant to a boolean and back. This isn't hard. First, introduce the
converter:

We can polish up the binding syntax a little by allowing our converter to take
strings and map them back to enum tags, instead of having to specify actual
typed enum values in our XAML. While we're at it, let's make the class generic
enough to handle all enumerations so we don't need a separate class for every
enum:

One inherent limitation of
this technique is that you cannot use it to bind checkboxes to flag enums,
because the converter can only convert a single value, not the combined state
of all checkboxes. You can't get around this by using IMultiValueConverter and MultiBinding, at least not in an intuitive
fashion, because the binding is "the wrong way around": MultiBinding
takes different values to produce one new value, so we'd need to bind the enum
property in the view model to the checkboxes, rather than the other way around.
If we do that, however, the parameter value becomes useless -- binding to the
"IsChecked" property only tells us that the checkbox is checked, not
which enum flag it represents. Frankly, thinking about this approach gives me
headaches.

A far more approachable alternative is to break down the enum into separate
boolean properties and bind those to the checkboxes:

[Flags]

enumFoo { None = 0, Bar = 1, Baz = 2, Quux = 4 };

classViewModel : INotifyPropertyChanged {

Foo foo;

privatevoid fooChanged() {

PropertyChanged(this, newPropertyChangedEventArgs("Foo"));

PropertyChanged(this, newPropertyChangedEventArgs("FooBar"));

PropertyChanged(this, newPropertyChangedEventArgs("FooBaz"));

PropertyChanged(this, newPropertyChangedEventArgs("FooQuux"));

}

publicFoo Foo {

get { return foo; }

set {

if (foo != value) {

foo = value;

fooChanged();

}

}

}

publicbool FooBar {

get { return foo.HasFlag(Foo.Bar); }

set {

if (foo.HasFlag(Foo.Bar) == value) return;

if (value) {

foo |= Foo.Bar;

} else {

foo &= ~Foo.Bar;

}

fooChanged();

}

}

// And similarly for Baz and Quux

publiceventPropertyChangedEventHandler PropertyChanged;

}

And

<Window x:Class="WpfApplication2.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:local="clr-namespace:WpfApplication2"

Title="MainWindow"

Height="350"

Width="525">

<Grid>

<TextBlock Height="23"

HorizontalAlignment="Left"

Margin="12,12,0,0"

Name="textBlock1"

VerticalAlignment="Top"

Text="{Binding Foo}" />

<CheckBox Content="Bar"

Height="16"

HorizontalAlignment="Left"

Margin="12,35,0,0"

VerticalAlignment="Top"

IsChecked="{Binding FooBar}" />

<CheckBox Content="Baz"

Height="16"

HorizontalAlignment="Left"

Margin="12,50,0,0"

VerticalAlignment="Top"

IsChecked="{Binding FooBaz}" />

<CheckBox Content="Quux"

Height="16"

HorizontalAlignment="Left"

Margin="12,65,0,0"

VerticalAlignment="Top"

IsChecked="{Binding FooQuux}" />

</Grid>

</Window>

Of course, you can apply the same solution (slightly simplified) to the original
problem of binding radio buttons.

This solution is nice and very MVVM-y, as opposed to the value converter, but
it does have the unfortunate drawback of requiring boilerplate code. If you
have lots of flags, this solution is not particularly attractive. On the other
hand, if you have lots of flags, presenting this in the interface as a mass of
radio buttons or checkboxes is not attractive either. In this case, you should
probably find another way of representing these choices, like two listboxes (one
representing the items that are not present, the other the items that are
present, with buttons between them for transferring them).

I want to round things out with a fully generic way of handling two-way binding
from enums to radio buttons or checkboxes, without the need for writing more
than a single line per enum property. This requires WPF 4.0 (and
correspondingly .NET 4.0) as we now have the ability to introduce dynamic objects: objects whose member
accesses are resolved with custom code at runtime. The idea is that, just like
in the example above, we bind our controls to individual boolean properties,
but instead of defining these statically, we look them up at runtime. This means
we lose static typing and IntelliSense, but since WPF binding is already
inherently dynamic, this doesn't matter much.

Here's what the XAML for that looks like:

<Window x:Class="WpfApplication2.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:local="clr-namespace:WpfApplication2"

Title="MainWindow"

Height="350"

Width="525">

<Grid>

<TextBlock Height="23"

HorizontalAlignment="Left"

Margin="12,12,0,0"

Name="textBlock1"

VerticalAlignment="Top"

Text="{Binding Foo}" />

<CheckBox Content="Bar"

Height="16"

HorizontalAlignment="Left"

Margin="12,35,0,0"

VerticalAlignment="Top"

IsChecked="{Binding Path=Foo.Bar}" />

<CheckBox Content="Baz"

Height="16"

HorizontalAlignment="Left"

Margin="12,50,0,0"

VerticalAlignment="Top"

IsChecked="{Binding Path=Foo.Baz}" />

<CheckBox Content="Quux"

Height="16"

HorizontalAlignment="Left"

Margin="12,65,0,0"

VerticalAlignment="Top"

IsChecked="{Binding Path=Foo.Quux}" />

</Grid>

</Window>

Almost the same as our
previous approach. The view model code is quite different:

The trick here is that the
property "Foo" is of type "dynamic". When a property like
"Foo.Bar" is bound, a call to DynamicObject.TryGetMember() is produced to look up the
value. As you can imagine, the real magic is in the class ModelEnumProperty:

This code looks more complicated than it is. Let's break it down to the
interesting pieces.

First note that we need to check explicitly if we're dealing with an enum type.
It's not possible to do this with a type constraint. The previous examples
omitted this check altogether.

In the constructor, we set up a quick lookup from tag names to values, not so
much for performance but because it makes the lookup a lot less tedious. We
also record whether or not the enum is a flags enum (as can be determined by
checking for the "Flags" attribute on the type) to vary the logic
later.

GetDynamicMemberNames() and TryGetMember() are fairly straightforward.
TrySetMember() is more interesting because it needs to handle a few special
cases:

If the enum is not a flags enum, setting the value simply overwrites
the old one, but clearing the value only has effect if the value is set in
the first place. If Foo has value "Bar", setting "Baz"
to false has no effect.

If the enum is a flags enum, we have to handle the special case of the
"None" value being set, as this should clear the enum (rather
than do nothing, which is what would happen if we simply "added"
the flag).

ModelEnumProperty implements
INotifyPropertyChanged, but as we will see later, the actual property names
aren't used.

TryConvert() is more complicated than necessary because, in addition to
allowing conversion to the enum type, it also allows conversion to the
underlying integer type of the enum (but not arbitrary integer types). This is
not strictly necessary.

This binds a delegate straight
to the event so we can initialize the property in one go, because we will
always want to notify the parent of changes (anything binding directly to the
enum property rather than its members should get property change notifications
as well). You can see that this ignores the specific member that was changed
altogether.

Although neat, I don't really recommend this solution in production code over
the previous solutions. There are a few reasons. First, this code is not as
accessible as the previous approaches. Second, there is significant memory and
runtime overhead associated with maintaining these dynamic property lookups --
you wouldn't want to bind lots of enum properties this way. Perhaps most
significantly, you lose strong typing and IntelliSense for users of the view
model other than the XAML (which can't use type safety).

In other to set a ModelEnumProperty to a particular value, you have to use the
"Foo.Bar = true" syntax, which may require multiple statements. It's
possible to work around this by making the class more intelligent (for example,
by providing a dynamic "SetValue" method), but you can't get around
the fact that the property must be of type "dynamic". Of course you
can declare a second, statically typed property and name it something like
"FooValue" (or rename the dynamic property to "FooFlags")
but overall, the solution is too much of a clever hack. I thought I'd
present it anyway.

1 comment:

Anonymous
said...

Hi Jeroen! I've just seen your code. I'm very interested in your last approach. Although it's complex, it may be useful for things like a "flagged enum editor": an custom control that is able to set/unset the flags in an enum.

However, I need your opinion. Would it be easy to do it? Some ideas?Thanks a lot.