如何基于每个系统主题定义图标资源?

时间:2011-08-24 18:30:11

标签: wpf icons resourcedictionary

我有一个WPF 4.0应用程序,它在菜单命令之类的东西中使用了一些自定义的16x16图标。我想(现在)有两组图标,默认的Vista / 7-ish和一些XP-ish。我想要的是让当前的操作系统确定使用哪些图标。

现在,我已经在主题资源词典(即Aero.NormalColor.xaml等)中定义了指向特定PNG资源的BitmapImage资源。

<!-- Aero.NormalColor.xaml -->
<BitmapImage x:Key="IconSave" UriSource="/MyWPFApp;component/Resources/Icons16/Aero/disk.png"/>

<!-- Luna.NormalColor.xaml -->
<BitmapImage x:Key="IconSave" UriSource="/MyWPFApp;component/Resources/Icons16/Luna/disk.png"/>

我的应用中想要显示图标的任何地方都将Image / Icon的source属性设置为这些BitmapImages之一的StaticResource。

<Image Source="{StaticResource IconSave}"/>

这个想法是因为WPF会根据当前的操作系统和主题自动加载一个主题词典,所以只会加载一组BitmapImage资源,而且这些图标会神奇地成为合适的图标。

然而,这不起作用,我在运行时遇到了可怕的“无法找到资源”异常。我的预感是,这是因为主题文件只搜索自定义控件,而Image不是。

Blend 4对这些没有任何问题,但已经定义了其特殊的DesignTimeResources.xaml文件,并在Aero.NormalColor.xaml上进行了合并。 VS2010扼流圈,但它也没有使用像DesignData文件这样的东西,所以我并不感到惊讶。我目前还有一个单独的资源字典文件(MainSkin.xaml),它被合并到Application资源中。引用样式等等在运行时工作正常。

我是在正确的轨道上,只是有一些轻微的错误?我是否需要做一些完全不同的事情才能获得理想的效果,如果是,那又是什么?

1 个答案:

答案 0 :(得分:7)

我发现你可以使用ComponentResourceKey来解决这个问题。在您的主题资源中,字典定义资源如下

<!-- themes\aero.normalcolor.xaml -->
<BitmapImage x:Key="{ComponentResourceKey ResourceId=IconSave, TypeInTargetAssembly={x:Type local:CustomControl}}" UriSource="/MyWPFApp;component/Resources/Icons16/Aero/disk.png"/>

<!-- themes\luna.normalcolor.xaml -->
<BitmapImage x:Key="{ComponentResourceKey ResourceId=IconSave, TypeInTargetAssembly={x:Type local:CustomControl}}" UriSource="/MyWPFApp;component/Resources/Icons16/Luna/disk.png"/>

此处local:CustomControl可以是您的主窗口或程序集中的自定义控件。有趣的是,只要它是自定义的,它实际上并不重要,因此它确保你强制它加载这些资源。

您还需要更新AssemblyInfo.cs以确保ThemeInfo使用以下内容查看主题资源字典的源程序集

[assembly:ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly )]

现在在您的XAML中(无论您喜欢什么控件,都不必是CustomControl),您可以编写以下内容来使用资源

<Image Source="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type local:CustomControl}, ResourceId=IconSave}}"/>

通过使用DynamicResource,您还可以在主题更改时使应用程序动态更新(而不是需要重新启动的StaticResource)。

我认为可以编写一个更清晰的ComponentResourceKey实现来隐藏TypeInTargetAssembly(我会给它一个),但至少这可以让你工作。


为了更新,我刚刚对ComponentResourceKey进行了改进,它将查看当前正在执行的程序集,并找到它可以用于TypeInTargetAssembly的第一个UIElement。

    public class ThemeResourceKey : ComponentResourceKey
    {
        public ThemeResourceKey(String resourceId)
        {
            ResourceId = resourceId;
            var assembly = Assembly.GetExecutingAssembly();

            var types = assembly.GetTypes().Where(t => typeof (UIElement).IsAssignableFrom(t));
            var uiElementType = types.FirstOrDefault();
            if(uiElementType == default(Type))
                throw new ArgumentException("No custom UIElements defined within this XAML");

            TypeInTargetAssembly = uiElementType;
        }
    }

您现在可以使用此

定义资源字典
<!-- themes\aero.normalcolor.xaml -->
<BitmapImage x:Key="{local:ThemeResourceKey IconSave}" UriSource="/MyWPFApp;component/Resources/Icons16/Aero/disk.png"/>

并在控件中引用它,如下所示

<Image Source="{DynamicResource {local:ThemeResourceKey IconSave}}"/>

哪个应该证明更清洁。如果您有任何问题,希望有所帮助并告诉我。