Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I specify resources in an MVVM view model?

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.

like image 934
gix Avatar asked Jul 08 '09 14:07

gix


People also ask

What is DataContext in MVVM?

DataContext is the head of everything. It makes sure that your View is hooked up with ViewModel. There are 3 ways to hook-up View with ViewModel. Within XAML. Code-Behind.

What is in model part in MVVM?

Using the MVVM pattern, the UI of the app and the underlying presentation and business logic is separated into three separate classes: the view, which encapsulates the UI and UI logic; the view model, which encapsulates presentation logic and state; and the model, which encapsulates the app's business logic and data.

How does the MVVM pattern relate to the command pattern?

When you use the Model-View-ViewModel (MVVM) design pattern, a command is exposed on the ViewModel as a property that implements the ICommand interface. Controls on the View bind to these properties. When a user interacts with that control, it executes the command.


2 Answers

The first workaround you mention can be found here: 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

like image 197
Jorge Vargas Avatar answered Oct 16 '22 11:10

Jorge Vargas


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.

like image 44
Cameron MacFarland Avatar answered Oct 16 '22 11:10

Cameron MacFarland