Thursday 31 December 2009

Specifying Resource Keys using Data Binding in WPF, Take 2: Introducing the ResourceKeyBinding markup extension

A few nights before Christmas, when, all through the house, not a distraction was stirring, not even a spouse, I posted about a technique that mixed up Resources and Data Binding in WPF, letting you use data binding to specify the key of the resource you wanted to use for a property. This trick helps to keep ViewModels unpolluted by Viewish things such as the URIs of images, while still retaining some control of those aspects - for example, over which Images are shown.

There was one thing I didn’t like about the technique that I showed: for every kind of Dependency Property that was to be the target of a data-bound resource key, you had to use my ResourceKeyBindingPropertyFactory to derive a new Dependency Property to hold the data binding.

Today I’m going to show a much more elegant technique: the ResourceKeyBinding markup extension. In the following snippet, I’ve modified the example I showed last time so that it now uses my new ResourceKeyBinding to data bind the keys of the resources used for the caption on the buttons:

<DataTemplate>
  <Button Command="{Binding}" Padding="2" Margin="2" Width="100" Height="100">
    <StackPanel>
      <Image HorizontalAlignment="Center"
             Width="60"
             app:ResourceKeyBindings.SourceResourceKeyBinding="{Binding Converter={StaticResource ResourceKeyConverter}, ConverterParameter=Image.{0}}"/>
      <TextBlock Text="{ext:ResourceKeyBinding Path=Name, StringFormat=Caption.{0} }" HorizontalAlignment="Center" FontWeight="Bold" Margin="0,2,0,0"/>
    </StackPanel>
  </Button>
</DataTemplate>

As you can see from line 7, you use ResourceKeyBinding in almost exactly the same way that you would use a normal Binding: not shown here are Source, RelativeSource or Element properties that work as you would expect; and, as per Binding, if all of these are omitted, the data source for the ResourceKeyBinding is the DataContext of the element. I’m also making use of the StringFormat capability of data bindings, which gets the value of the property indicated by Path and applies the given string format to it.

With this in place, the TextBlock should be given the appropriate value picked out from our amended App.xaml:

<Application.Resources>
  <BitmapImage x:Key="Image.AngryCommand" UriSource="Angry.png"/>
  <BitmapImage x:Key="Image.CoolCommand" UriSource="Cool.png"/>
  <BitmapImage x:Key="Image.HappyCommand" UriSource="Happy.png"/>

  <sys:String x:Key="Caption.Angry">Angry. Rrrr!</sys:String>
  <sys:String x:Key="Caption.Happy">Happy. Ha ha!</sys:String>
  <sys:String x:Key="Caption.Cool">Chilled out</sys:String>
</Application.Resources>

And sure enough, it is:

ResourceBindingSampleImage2

Behind the curtain

So how does it work? There are two parts to it. The first component is the markup extension itself:

public class ResourceKeyBindingExtension : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var resourceKeyBinding = new Binding()
        {
            BindsDirectlyToSource = BindsDirectlyToSource,
            Mode = BindingMode.OneWay,
            Path = Path,
            XPath = XPath,
        };

        //Binding throws an InvalidOperationException if we try setting all three
        // of the following properties simultaneously: thus make sure we only set one
        if (ElementName != null)
        {
            resourceKeyBinding.ElementName = ElementName;
        }
        else if (RelativeSource != null)
        {
            resourceKeyBinding.RelativeSource = RelativeSource;
        }
        else if (Source != null)
        {
            resourceKeyBinding.Source = Source;
        }

        var targetElementBinding = new Binding();
        targetElementBinding.RelativeSource = new RelativeSource()
        {
            Mode = RelativeSourceMode.Self
        };

        var multiBinding = new MultiBinding();
        multiBinding.Bindings.Add(targetElementBinding);
        multiBinding.Bindings.Add(resourceKeyBinding);

        // If we set the Converter on resourceKeyBinding then, for some reason,
        // MultiBinding wants it to produce a value matching the Target Type of the MultiBinding
        // When it doesn't, it throws a wobbly and passes DependencyProperty.UnsetValue through
        // to our MultiBinding ValueConverter. To circumvent this, we do the value conversion ourselves.
        // See http://social.msdn.microsoft.com/forums/en-US/wpf/thread/af4a19b4-6617-4a25-9a61-ee47f4b67e3b
        multiBinding.Converter = new ResourceKeyToResourceConverter()
        {
            ResourceKeyConverter = Converter,
            ConverterParameter = ConverterParameter,
            StringFormat = StringFormat,
        };

        return multiBinding.ProvideValue(serviceProvider);
    }

    [DefaultValue("")]
    public PropertyPath Path { get; set; }

    // [snipped rather uninteresting declarations for all the other properties]
}

Under the covers, ResourceKeyBindingExtension is being rather cunning. It constructs a MultiBinding with two child bindings: one binding is used to get hold of the resource key: this is initialised with the parameters that ResourceKeyBinding is given – the property path and data source, for example. The other child binding is set up with a RelativeSource mode of Self so that it grabs a reference to the ultimate target element (in the case of the example above, the TextBlock).

Every MultiBinding needs a converter, and we configure ours in line 45. The job of this converter is to use the resource key obtained by the second child binding to find the appropriate resource in the pool of resources available to the target element obtained by the first child binding – FrameworkElement.TryFindResource does the heavy lifting for us here:

class ResourceKeyToResourceConverter : IMultiValueConverter
{
    // expects the target object as the first parameter, and the resource key as the second
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (values.Length < 2)
        {
            return null;
        }

        var element = values[0] as FrameworkElement;
        var resourceKey = values[1];
        if (ResourceKeyConverter != null)
        {
            resourceKey = ResourceKeyConverter.Convert(resourceKey, targetType, ConverterParameter, culture);
        }
        else if (StringFormat != null && resourceKey is string)
        {
            resourceKey = string.Format(StringFormat, resourceKey);
        }

        var resource = element.TryFindResource(resourceKey);

        return resource;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    public IValueConverter ResourceKeyConverter { get; set; }

    public object ConverterParameter { get;set;}

    public string StringFormat { get; set; }
}

You’ll notice that if ResourceKeyBinding is given a Converter or a StringFormat it doesn’t give these to resourceKeyBinding as you might expect. Instead it passes them on to the ResourceKeyToResourceConverter, which handles the conversion or string formatting itself. I’ve not done it this way just for fun: I found out the hard way that if you include a converter in any of the child Bindings of a MultiBinding, then WPF, rather unreasonably in my opinion, expects that converter to produce a result that is of the same Type as the property that the MultiBinding is targeting. If the Converter on the child Binding produces a result of some other type, then the MultiBinding passes DependencyProperty.UnsetValue to its converter rather than that result. There’s a forum thread discussing this behaviour but no real answer as to whether this is by design or a bug.

Watch this bug don’t getcha

One other gotcha with custom markup extensions, this one definitely a bug in Visual Studio. If you define a custom markup extension, and then, in Xaml that is part of the same assembly, you set one of the properties of that markup extension using a StaticResource you’ll get a compile-time error similar to:

Unknown property '***' for type 'MS.Internal.Markup.MarkupExtensionParser+UnknownMarkupExtension' encountered while parsing a Markup Extension.

The workaround, as Clint discovered, is either to put your markup extension in a separate assembly (which is what I’ve done) or use Property Element syntax for the markup extension in XAML.

Try it yourself

I’ve updated the code on the MSDN Code Gallery page – go see if for yourself.

Wednesday 23 December 2009

Specifying Resource Keys using Data Binding in WPF

Imagine you’re wanting to show a list of things in an ItemsControl, with each item having a different image. Using WPF’s implicit Data Templating support, and giving each item Type its own Data Template is one way of implementing this: but if there are many items, and the image is the only thing that’s different in each case, and the Data Template is of any complexity, your code will soon start to suffer DRY rot.

You could just pinch your nose and put the image in your ViewModel so that it can be databound in the normal way. Of course, images should really live in a ResourceDictionary: but how can you pick a resource out of a ResourceDictionary using data binding? Let me show you.

The example: AutoTherapist

Here’s what I want to build:ResourceBindingSampleImage

I’ve got a very simple ViewModel with a property exposing a list of the commands that sit behind the buttons in my Window:

public class WindowViewModel
{
    public IList<ICommand> Commands
    {
        get
        {
            return new ICommand[] { new AngryCommand(), new HappyCommand(), new CoolCommand() };
        }
    }
}

And here’s the relevant part of the View:

<ItemsControl Grid.Row="1" ItemsSource="{Binding Commands}">
    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <Button Command="{Binding}" Padding="2" Margin="2" Width="100" Height="100">
          <StackPanel>
            <Image HorizontalAlignment="Center"
                   Width="60"
                   app:ResourceKeyBindings.SourceResourceKeyBinding="{Binding Converter={StaticResource ResourceKeyConverter}, ConverterParameter=Image.{0}}"/>
            <TextBlock Text="{Binding Name}" HorizontalAlignment="Center" FontWeight="Bold" Margin="0,2,0,0"/>
          </StackPanel>
        </Button>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
        <StackPanel Orientation="Horizontal"/>
      </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

The ItemsControl is bound to the Commands property on my ViewModel. Each command is rendered with the same DataTemplate. The magic happens in line 8 where I’m using the attached property ResourceKeyBindings.SourceResourceKeyBinding. This property allows me to data-bind the key of the resource I want to use for the Image.Source property. I’ll show you how that works in a minute, but first: where are the resource keys coming from?

You’ll notice that, since there’s no Path specified in the Binding, we’re binding directly to the data object - in this case, one of the commands. Then we’re using a converter to turn that object into the appropriate key. Here’s the code for the converter:

class TypeToResourceKeyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var formatString = parameter as string;
        var type = value.GetType();
        var typeName = type.Name;

        var result = string.Format(formatString, typeName);

        return result;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

What this is doing is getting the name of the Type of the data object and pushing that through the format string given as the parameter to the converter. Given the way the converter is set up in our case, this will produce resource keys like “Image.AngryCommand”, “Image.HappyCommand”, etc.

So now all we need to make the AutoTherapist work is to define those resources. Here’s App.xaml:

<Application x:Class="ResourceKeyBindingSample.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml">
    <Application.Resources>
      <BitmapImage x:Key="Image.AngryCommand" UriSource="Angry.png"/>
      <BitmapImage x:Key="Image.CoolCommand" UriSource="Cool.png"/>
      <BitmapImage x:Key="Image.HappyCommand" UriSource="Happy.png"/>
    </Application.Resources>
</Application>

(The icons are from VistaIcons.com, by the way).

The implementation

So what does that attached property look like? It’s actually rather simple:

public static class ResourceKeyBindings
{
    public static DependencyProperty SourceResourceKeyBindingProperty = ResourceKeyBindingPropertyFactory.CreateResourceKeyBindingProperty(
        Image.SourceProperty,
        typeof(ResourceKeyBindings));

    public static void SetSourceResourceKeyBinding(DependencyObject dp, object resourceKey)
    {
        dp.SetValue(SourceResourceKeyBindingProperty, resourceKey);
    }

    public static object GetSourceResourceKeyBinding(DependencyObject dp)
    {
        return dp.GetValue(SourceResourceKeyBindingProperty);
    }
}

As you can see, I’ve factored out the magic into ResourceKeyBindingPropertyFactory. This makes it easy to create equivalent properties for any other target property (in the download, for example, I’ve made a StyleResourceKeyBinding property for binding to FrameworkElement.Style). ResourceKeyBindingPropertyFactory looks like this:

public static class ResourceKeyBindingPropertyFactory
{
     public static DependencyProperty CreateResourceKeyBindingProperty(DependencyProperty boundProperty, Type ownerClass)
     {
         var property = DependencyProperty.RegisterAttached(
             boundProperty.Name + "ResourceKeyBinding",
             typeof(object),
             ownerClass,
             new PropertyMetadata(null, (dp, e) =>
             {
                 var element = dp as FrameworkElement;
                 if (element == null)
                 {
                     return;
                 }

                 element.SetResourceReference(boundProperty, e.NewValue);
             }));

         return property;
     }
}

All we do here is register an attached property and set it up with a PropertyChanged handler: the handler simply takes the new value of the property – which in our case will be the resource key – and passes it to SetResourceReference along with the target property. SetResourceReference is the programmatic equivalent of using DynamicResource in XAML – it looks up the appropriate resource (from the current element’s ResourceDictionary or one of its ancestors’) and assigns it to the given property.

So there you have it: data binding for Resource Keys. Full source code for this sample is available from the MSDN Code Gallery.

Not for Silverlight

Unfortunately I don’t think it is possible to port this to Silverlight without a lot of work because Silverlight has no support for Dynamic Resources. From a cursory look, I don’t think there is even runtime support built in for finding a Resource in the Resource Dictionary chain up the ancestor tree of an element as there is in WPF: it looks like the Silverlight parser is responsible for doing this when required by StaticResource references. I would be delighted if someone could show me otherwise