说明:本系列基本上是《WPF揭秘》的读书笔记。在结构安排与文章内容上参照《WPF揭秘》的编排,对内容进行了总结并加入一些个人理解。
皮肤
皮肤是应用程序中样式与模板的集合,可以被整体动态的替换,从而让应用看上去是一种全新的风格。通过皮肤允许第三方任意改变应用程序的外观,WPF并没有内建一个叫皮肤的机制。皮肤及换肤功能可以通过动态资源机制加Style和模板来实现。
下面介绍一个WPF较常见的皮肤定制机制。如果我们希望一个元素的外观可以通过皮肤来定制,我们要将此元素的样式定义为引用动态资源,并把默认样式的定义放在App.xaml的<Resources>中,样式的key为元素属性定义中DictionaryResource所引用的名称。第三方定制皮肤时,将定制的元素的样式放在根元素为<ResourceDictionary>的"皮肤"文件中。
最后要做的是动态加载皮肤所在的XAML文件,并用其内容替换当前的Application.Resources字典:
ResourceDictionary resource = null;
using (FileStream fs = new FileStream("CustomSkin.xaml", FileMode.Open, FileAccess.Read))
{
//XAML的根元素必须是ResourceDictionary
resource = (ResourceDictionary)XamlReader.Load(fs);
}
Application.Current.Resources = resource;
下面是由Internet上获得皮肤XAML的例子,其中用到我们网络部分介绍过的WebClient类。
ResourceDictionary resource = null;
System.Net.WebClient client = new WebClient();
using (Stream s = client.OpenRead("http://cnblogs.com/sample.xaml"))
{
//XAML的根元素必须是ResourceDictionary
resource = (ResourceDictionary)XamlReader.Load(s);
}
Application.Current.Resources = resource;
另外,如果需要可以恢复默认皮肤的功能,则应当保存当前Resouce。
提示:如果按上文方式应用了新皮肤,而对于某元素的一个属性,新皮肤中没有对应的定义,这时WPF会将此元素切换为默认样式,输出一个调试跟踪记录:
System.Windows.ResourceDictionary Warning: 9 : Resource not found;
ResourceKey = 'CancelButtonStyle'
提示:如果皮肤中(或皮肤的模板中)需要使用程序代码,就不能将其放在XAML中了,我们可以将其所在的ResouceDictionary编译到一个程序集中,并以相同的方式作为皮肤使用,这中间有一步关键操作是用Application.LoadComponent获得已编译的资源(可以位于相同或不同的程序集中),下面是代码示例:
ResourceDictionary resource =(ResourceDictionary)Application.LoadComponent(new Uri("sample.xaml", UriKind.RelativeOrAbsolute));
Application.Current.Resources = resource;
提示:自定义皮肤(第三方皮肤)可能会带来不良的效果,如将界面某一元素通过样式隐藏了,将文本的颜色与背景色设为一致从而让文本难以读取等,这些只能由程序开发者设计方法来避免。这里的安全问题非一言一语可以给出方法,需在实际开发过程中谨慎对待。
主题
WPF中内建的控件都提供了多个独立的模板来对应不同的Windows主题,以使控件外观与Windows保持一致。
这一节我们的主要话题是怎样让用户自定义的模板适应用户所选的不同的Windows主题。有这种主要方法,前者简单但功能稍弱,后者灵活但相对复杂。
-
使用系统颜色,字体等参数
当Windows系统的主题变化时,SystemColors,SystemFonts和SystemParameters类提供的成员会自动更新,在样式与模板中使用这些类是与系统主题保持一致的好方法。如:
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
应用样式中含有这行代码的目标元素,背景色将随系统主题的变化而变。
-
为每种主题提供样式的模板
这种方式中我们需要使用编程方式在主题改变时加载相应的主题。WPF没有内置监听主题更改的事件,需要使用Win32.WM_THEMECHANGE消息获得主题更改通知。下面我们详细介绍下这种方式的使用:
-
第一步
把对应不同主题的资源(样式或模板)放入不同的XAML文件中(资源字典),这些XAML命名规则为ThemeName.ThemeColor.xaml(非大小写敏感),我们把这些文件放在程序集根目录下的Themes文件夹中这样资源字典就被指派为主题字典,这样编译这个程序集就可以了。这样当应用程序启动或主题改变时,WPF会自动加载并应用主题字典(及其中的主题样式)。
下面是Windows内置主题对应的WPF内置的主题字典的xaml文件,我们可以了解下它们的路径与命名:
- Vista Aero主题:themes\Aero.NormalColor.xaml
- Windows xp默认主题:themes\Luna.NormalColor.xaml
- Windows Classic主题:themes\Classic.xaml
另外,有一个最佳实践,添加一个通用字典,用于当前主题没有对应的主题字典的情况,这个字典的命名需要是themes\Generic.xaml。
-
第二步
有了主题字典和通用字典,我们需要使用程序集级的[ThemeInfo]特性启用主题机制,其构造函数接受两个类型为ResourceDictionaryLocation的参数,第一个参数告诉WPF主题字典的位置,第二个参数告诉WPF通用字典的位置。
ResourceDictionaryLocation类型参数接受如下几类值:
- None:这是默认值,不查找资源字典
- SourceAssembly:在当前程序集内寻找资源字典
- ExternalAssembly:在其他程序集内查找,这些程序集的命名格式为AssemblyName.ThemeName.dll。WPF内部实现中也使用这种方式查找主题字典,如PresentationFramework.Aero.dll等。
介绍完了原理,我们给一个例子:假设我们把主题字典放在了当前程序集中。程序集级特性将类似如下这样:
[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly,ResourceDictionaryLocation.SourceAssembly)]
对于主题不得不说的一点是,其主要用于为控件(尤其是自定义的元素)提供默认样式。所以常常主题样式都是放在定义控件的程序集自身中或伙伴程序集中。在一个控件所在程序集的主题字典中不能为外部定义的元素定义类型化样式或重载它们的样式,下面的提示中有关于这个话题进一步的分析。
提示:一个使用外部元素主题样式的方法
我们来看这样一个场景,我们在一个程序中用到一个外部控件(可以是WPF内置控件或者某个自定义控件),为了得到引用的外部控件在不同主题下的主题字典,我们可能需要引用一些定义了这些主题字典的程序集。这时我们就需要借助ThemeDictionary这个标记扩展。
ThemeDictionaryExtension用于引用任何包含了主题字典的程序集(甚至包括当前程序集)。从而引用或重载任何元素的样式。实际使用中我们常把ThemeDictionary作为ResourceDictionary的一个源。将下面代码:
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="{ThemeDictionary assemblyName}"/>
<ResourceDictionary .../>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
提示:给现有元素添加主题样式的一种方式
我们可以通过派生一个现有元素的子类来定义一个使用新主题样式的自定义控件。如我们由ProgressBar派生一个自定义控件,实现饼图外观,所需的代码很简单:
static ProgressPie()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(ProgressPie),
new FrameworkPropertyMetadata(typeof(ProgressPie))
);
}这样就可以引用为ProgressPie指定的类型化样式,实现饼状的ProgressBar。DefaultStyleKey是FrameworkElement和FrameworkContentFramework中定义的protected级的依赖属性,用于指定主题样式,这个样式定义同前文所述:
<ResourceDictionary>
<Style TargetType="{x:Type local:ProgressPie}"></Style>
</ResourceDictionary>
本文完
参考:
《WPF揭秘》