动态更换WPF主题的实现

在WPF应用程序中,主题的更换通常需要重新编译应用程序。但是,通过采用一种可插拔的架构,可以在不重新编译应用程序的情况下更换或添加样式。本文将介绍如何实现这一功能。

为了实现这一功能,选择了一种可插拔的架构。这意味着应用程序中有一个专门的文件夹,用于存放所有的主题。如果用户想要一个新的主题,他们只需要向该文件夹添加一个新的程序集。应用程序启动时,会使用反射加载这些程序集。

主题通过资源字典来表示,这些资源字典还附有代码后端类,以便在代码中访问它们。程序集只需要包含代表主题的资源字典即可。文件数量和资源键名称都不重要,重要的是使用默认应用于控件的样式(没有资源键的样式)来应用模板。

应用程序解决方案包含三个项目。一个是使用主题的主项目,另外两个项目展示了如何构建主题以便正确加载。首先,将介绍这两个项目,最后将展示加载这些程序集的反射代码。

主题程序集 主题将由一个程序集表示。这个程序集将包含一个或多个资源字典,这些资源字典将包含主题的资源(画笔、动画、控件模板、样式等)。这可以在下面的图片中看到:

如上图所示,每个资源字典都有一个关联的代码后端文件。这个代码后端文件在加载资源时非常有用。为了将资源字典文件链接到代码后端文件,需要做几件事情。

首先,需要在资源字典中设置x:Class属性为想要的类名。这必须是一个完全限定的名称。代码如下所示。代码是为ListBoxStyle.xaml字典。

<ResourceDictionary x:Class="Theme1.ListBoxDictionary" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

下一步是添加一个新的部分类来链接这个文件。类的代码非常少,如下所示:

namespace Theme1 { public partial class ListBoxDictionary : ResourceDictionary { public ListBoxDictionary() { InitializeComponent(); } } }

如所见,需要一个从ResourceDictionary派生的部分类。因为这个类与XAML文件链接,所以它将有一个由设计器自动生成的InitializeComponent()方法。所有提供的字典都需要这样的类。

第二个文件的代码类似。XAML类名如下所示:

<ResourceDictionary x:Class="Theme1.ScrollBarDictionary" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

代码后端文件同样简单:

namespace Theme1 { public partial class ScrollBarDictionary : ResourceDictionary { public ScrollBarDictionary() { InitializeComponent(); } } }

在为所有主题设置了样式之后,需要编译程序集,然后就完成了。现在可以将程序集添加到主应用程序中预留的文件夹中。

主应用程序 主应用程序非常小。它有两个窗口。主窗口将包含将被样式化的列表框控件,第二个窗口将允许选择要应用的主题。当应用程序启动时,没有应用主题,但可以轻松更改此设置。两个窗口如下所示:

在左侧,可以看到第二个主题,在右侧,可以看到选项窗口。鉴于打开选项窗口的代码(当选择工具->选项菜单时调用ShowDialog())是微不足道的,将只讨论选项窗口中的代码。

当选项窗口加载时,加载MyThemes目录中的每个程序集,并将引用存储在泛型列表中。代码如下所示:

DirectoryInfo di = new DirectoryInfo("../../MyThemes"); foreach (FileInfo fi in di.GetFiles()) { try { themes.Add(Assembly.LoadFile(fi.FullName)); } catch { } } cbThemes.ItemsSource = themes.Select(p => p.GetName().Name).ToList();

用户可以从组合框中选择一个主题,然后按下应用按钮。当他们这样做时,根据名称检索所选的程序集:

Assembly a = themes.Where(p => p.GetName().Name.Equals(cbThemes.SelectedValue)).SingleOrDefault();

如果程序集存在于程序集列表中,首先清除应用程序的资源字典:

if (a != null) { ((App)Application.Current).Resources.MergedDictionaries.Clear(); } foreach (Type t in a.GetTypes()) { Trace.WriteLine(t.FullName); if (t.IsSubclassOf(typeof(ResourceDictionary))) { ConstructorInfo ci = t.GetConstructor(Type.EmptyTypes); ResourceDictionary rd = (ResourceDictionary)ci.Invoke(new object[] { }); ((App)Application.Current).Resources.MergedDictionaries.Add(rd); } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485