如何在MVVM视图模型中指定资源?

时间:2022-11-01 15:11:05

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).

假设我想显示每个对象应该具有名称和合适图像的对象列表(例如带有图标的MenuItems,或带有文本和图像的按钮)。

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).

所有示例和程序都将viewmodel中的图像作为PNG文件的路径公开,然后将图像的源绑定到该文件。但是如果我想使用矢量图像(例如在本地ResourceDictionary中作为DrawingImage)呢?从视图模型中公开DrawingImage似乎很糟糕,因为我必须存储对应用程序/窗口/用户控件/ ...的引用(并且建议不要从视图模型中公开这样的XAML对象)。

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!).

    第一个是使用正常绑定到图标的转换器,它在Application.Current中查找资源。如果资源存储在我认为的其他地方(并且我最初碰到这个问题的情况没有运行应用程序,因为它是一个Window选择要启动的应用程序!),这不起作用。

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

    第二种解决方法是使用从StaticResourceExtension派生的标记扩展,该扩展从传递的绑定中获取其ResourceKey:

    <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.

    这个看起来很整洁,因为它可以使用本地资源,也可以用于其他事情。但是在使用它时我总是遇到异常(“无法找到名为{FooIcon}的资源。”,显示正确的XAML文件和扩展名的位置)。即使从StaticResourceExtension派生的空资源扩展只是将资源键传递给基础构造函数也不起作用,我无法解释原因。只是使用StaticResourceExtension工作得很好。

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.

但是例如在DataTemplate中失败了。虽然正常的StaticResourceExtension可以在两种情况下都能正常工作,所以我很困惑会出现什么问题。

2 个解决方案

#1


3  

The first workaround you mention can be found here: Binding a datacontext string property to a StaticResource key

您可以在此处找到您提到的第一个解决方法:将datacontext字符串属性绑定到StaticResource键

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).

我尝试使用你提到的第二部作品(http://sweux.com/blogs/psampaio/index.php/2009/06/16/using-data-binding-with-static-resources-in-wpf/) ,但我从来没有工作过。由于DataContext为null,因此抛出了ArgumentNullException。我认为这与我使用DataTemplate从我的ViewModel创建View的事实有关,并且在调用ProvideValue方法之前没有设置DataContext(在该页面的示例中,DataContext设置在.xaml.vb类)。

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:

所以,我开始寻找一个解决方法,找到一个也涉及转换器,但是这个通过FrameworkElement方法找到资源,而不是在Application.Current周围。我找到的那个详细信息如下:

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

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.

它涉及一个实现IMultiValueConverter接口的ValueConverter,可以访问设置绑定的控件。

The code for the Convert method is the following:

Convert方法的代码如下:

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:

内容控件的XAML如下所示:

<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:

图像的XAML如下:

<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

像魅力一样:D

#2


1  

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

自定义MarkupExtensions中存在一个错误,它不允许您在类似的属性中使用它们。

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

解决方法1:将属性声明为元素,如下所示:

<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.

解决方法2:如果通过将MarkupExtension放入另一个程序集并引用它来预编译MarkupExtension,那么它将再次运行。这可能就是为什么你看到它在主窗口中工作,而不是在你的DataTemplate中。

#1


3  

The first workaround you mention can be found here: Binding a datacontext string property to a StaticResource key

您可以在此处找到您提到的第一个解决方法:将datacontext字符串属性绑定到StaticResource键

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).

我尝试使用你提到的第二部作品(http://sweux.com/blogs/psampaio/index.php/2009/06/16/using-data-binding-with-static-resources-in-wpf/) ,但我从来没有工作过。由于DataContext为null,因此抛出了ArgumentNullException。我认为这与我使用DataTemplate从我的ViewModel创建View的事实有关,并且在调用ProvideValue方法之前没有设置DataContext(在该页面的示例中,DataContext设置在.xaml.vb类)。

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:

所以,我开始寻找一个解决方法,找到一个也涉及转换器,但是这个通过FrameworkElement方法找到资源,而不是在Application.Current周围。我找到的那个详细信息如下:

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

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.

它涉及一个实现IMultiValueConverter接口的ValueConverter,可以访问设置绑定的控件。

The code for the Convert method is the following:

Convert方法的代码如下:

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:

内容控件的XAML如下所示:

<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:

图像的XAML如下:

<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

像魅力一样:D

#2


1  

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

自定义MarkupExtensions中存在一个错误,它不允许您在类似的属性中使用它们。

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

解决方法1:将属性声明为元素,如下所示:

<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.

解决方法2:如果通过将MarkupExtension放入另一个程序集并引用它来预编译MarkupExtension,那么它将再次运行。这可能就是为什么你看到它在主窗口中工作,而不是在你的DataTemplate中。