如何在每个系统主题的基础上定义图标资源



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

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

<!-- 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"/>

在我的应用程序的任何地方,想要显示一个图标设置图像/图标的源属性作为一个StaticResource到这些BitmapImages之一。

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

这个想法是,由于WPF根据当前操作系统和主题自动加载主题字典,因此只会加载一组BitmapImage资源,并且图标将神奇地成为合适的图标。

但是,这不起作用,并且我在运行时得到可怕的"无法找到资源"异常。我的直觉是,这是因为主题文件只搜索自定义控件,而图像不是。

Blend 4在这些方面没有问题,但是它已经定义了它的特殊designtimeresource。xaml文件与Aero.NormalColor.xaml合并。VS2010令人窒息,但它也不能使用像DesignData文件之类的东西,所以我并不感到惊讶。我目前还有一个单独的资源字典文件(MainSkin.xaml),它被合并到应用程序资源中。引用样式等在运行时工作得很好。

我是在正确的轨道上,只是有一些轻微的错误?我是否需要做一些完全不同的事情来达到预期的效果?如果是,该怎么做?

我发现您可以使用ComponentResourceKey使其工作。在主题资源字典中,按如下方式定义资源

<!-- themesaero.normalcolor.xaml -->
<BitmapImage x:Key="{ComponentResourceKey ResourceId=IconSave, TypeInTargetAssembly={x:Type local:CustomControl}}" UriSource="/MyWPFApp;component/Resources/Icons16/Aero/disk.png"/>
<!-- themesluna.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;
        }
    }

你现在可以用这个

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

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

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

这应该证明更干净。希望这对你有帮助,如果有任何问题请告诉我

最新更新