views:

704

answers:

2

Suppose I want to show list of objects where each object should have a name and a suitable image (for example MenuItems with Icons, or buttons with text and image).

All examples and programs exposed the image in the viewmodel as a path to a PNG file and then bound the Source of an Image to that. But what if I want to use vector images (for example as a DrawingImage in a local ResourceDictionary)? Exposing the DrawingImage from the view model seems bad because I would have to store a reference to the application/window/user control/... (and it is advised to not expose such XAML objects from view models).

So a better approach would be to use a string identifier in the view model and then somehow select the appropriate resource. If that identifier is the resource key this snippet looks tempting but does not work:

<Image Source="{StaticResource {Binding Icon}}"/>

I found two workarounds for that though they did not work for me.

  1. The first one was using a normal binding to the icon with a converter that looked up the resource in Application.Current. This does not work if the resource is stored somewhere else I think (and the situation where I initially bumped into this problem had no Application running yet since it was a Window choosing the Application to launch!).

  2. The second workaround was using a markup extension derived from StaticResourceExtension that fetched its ResourceKey from the passed binding:

    <Image Source="{local:BindableStaticResource {Binding Icon}"/>
    

    This one looks really neat because it could use local resources, also be used for other things. But when using it I always got an exception ("Resource named {FooIcon} could not be found.", showing the correct XAML file and position of the extension). Even an empty resource extension derived from StaticResourceExtension that just passed the resource key to the base constructor did not work and I cannot explain why. Just using StaticResourceExtension worked just fine.

Any ideas how I could fix the second approach, or even better solutions?

Edit

I noticed that it does work when used directly like this:

<Window>
    <Window.Resources>
        <DrawingImage x:Key="SomeIcon"/>
    </Window.Resources>
    <Image Source="{BindableStaticResource {Binding Icon}}"/>
</Window>

but fails for example in a DataTemplate. Though a normal StaticResourceExtension works on both occasions so I am puzzled what is going wrong.

+1  A: 

There's a bug in custom MarkupExtensions that doesn't allow you to use them in an attribute like that.

Workaround 1: Declare the attribute as an element, like so:

<Image>
    <Image.Source>
        <local:BindableStaticResource Binding={Binding Icon}" />
    </Image.Source>
</Image>

Workaround 2: If you precompile the MarkupExtension by putting it in another assembly and referencing it, then it works again. This might be why you're seeing it work in the main window, but not in your DataTemplate.

Cameron MacFarland
I've researched the bug you seem to mention and the only thing I found was a compile-time error. This is not the case here, it compiles fine, but at runtime does not find the resource. Making the custom MarkupExtension an element instead of attribute sadly does not solve this.
gix
A: 

The first workaround you mention can be found here: http://stackoverflow.com/questions/695624/binding-a-datacontext-string-property-to-a-staticresource-key

I tried to use the second work around you mention ( http://sweux.com/blogs/psampaio/index.php/2009/06/16/using-data-binding-with-static-resources-in-wpf/), but I never got it working. It throwed an ArgumentNullException since the DataContext was null. I think this had something to do with the fact that I was using a DataTemplate to create my View from my ViewModel, and somehow the DataContext was not set before the ProvideValue method was called (in the example on that page, the DataContext is set in the .xaml.vb class).

So, I started looking for a workaround and found one which also involves a converter, but this one finds the resource through a FrameworkElement method instead of poking around Application.Current. The one I found is detailed here:

http://drwpf.com/blog/2007/08/18/can-my-value-converter-access-the-target-of-the-binding/

I'll copy the relevant information here:

It involves a ValueConverter implementing the interface IMultiValueConverter, to have access to the control on which the binding is set.

The code for the Convert method is the following:

public object Convert(object[] values, Type targetType, 
    object parameter, CultureInfo culture)
{
    FrameworkElement targetObject = values[0] as FrameworkElement;

    if (targetObject == null)
    {
        return DependencyProperty.UnsetValue;
    }
    return targetObject.TryFindResource(values[1]);
}

And the XAML for a content control would look like this:

<ContentControl>
  <ContentControl.Content>
    <MultiBinding Converter="{StaticResource Converter}">
      <MultiBinding.Bindings>
        <Binding RelativeSource="{RelativeSource Self}" />
        <Binding Path="ResourceKey" />
      </MultiBinding.Bindings>
    </MultiBinding>
  </ContentControl.Content>
</ContentControl>

And the XAML for an Image is the following:

<Image Height="16" Width="16">
    <Image.Source>
        <MultiBinding Converter="{StaticResource Converter}">
            <MultiBinding.Bindings>
                <Binding RelativeSource="{RelativeSource Self}" />
                <Binding Path="ResourceKey" />
            </MultiBinding.Bindings>
        </MultiBinding>
    </Image.Source>
</Image>

Works like a charm :D

Jorge Vargas