背景
在使用WPF的过程中,经常需要对控件的外观进行定制,这个时候查看其原有的样式或者模板进行参考就很有必要了。这样一能够减少许多工作,只修改需要的部分,二能够避免修改模板或样式后失去某些功能。下面是我常用的方法。
创建控件并显示查看模板
原理
可通过手动创建控件,然后使用XamlWriter类将模板保存的方式来查看控件的当前模板。对此感兴趣的可查看《WPF编程宝典——C#2010版》(《Pro WPF in C# 2010》)17.2.2 剖析控件相关部分。
上述方法经过简单修改之后也可用来查看样式或者第三方控件的模板,比如我就利用该方法查看过DevExpress中图表控件的模板。
不足
- 使用XamlWriter类保存后的XAML代码有些冗余且不直观,因为其总是会使用属性元素语法而不是特性语法。
- 对于某些类型不能正确的查看。比如缺少默认构造函数的控件(多见于第三方控件,如DevExpress)。再比如某些不能直接在窗口中显示的类型(Tolltip、ContextMenu等)
示例代码
基于WPF编程宝典的基础,我自己改良了一个控件查看工具。具有以下功能:
- 可查看控件模板和样式。
- 支持查看第三方控件
- 支持语法高亮
效果如下:
控件实际显示之前,模板为空。所以在代码中使用了一个小技巧,将控件放入到一个1像素宽高的窗口中,以便查看控件模板。
博客园:
反编译查看BAML资源
原理
一般情况下控件提供商都在资源字典中为控件准备了样式和模板。资源字典存放于控件所在的程序集或外部程序集中。在需要时,可通过C#反编译工具(如ILSpy、dotPeek)查看其中的BAML资源。
主题
在WPF3.0中,WPF中为自带的控件准备了4套主题,分别是Aero、Classic、Luna、Royale。主题的资源字典文件名及对应含义如下:
在WPF4.0中又新增了两套主题,即Aero2和AeroLite。在Win8及以上版本的系统中,Framework会自动将Aero主题转为Aero2主题,可通过反编译PresentationFramework证实。AeroLite没有查询到权威说法,网上有人说是用于Windows Server 2012。
将上述代码整理,可以用来确定当前使用的主题名称和颜色 。
public static Tuple<string, string> GetThemeInfo() { StringBuilder themePathBuff = new StringBuilder(260); StringBuilder themeColorBuff = new StringBuilder(260); if (GetCurrentThemeName(themePathBuff, themePathBuff.Capacity, themeColorBuff, themeColorBuff.Capacity, null, 0) == 0) { string themeName = System.IO.Path.GetFileNameWithoutExtension(themePathBuff.ToString()); string themeColor = themeColorBuff.ToString(); if (string.Compare(themeName, "aero", StringComparison.OrdinalIgnoreCase) == 0 && Environment.OSVersion.Version >= new Version(6, 2)) { themeName = "Aero2"; } return new Tuple<string, string>(themeName, themeColor); } else { return new Tuple<string, string>(string.Empty, string.Empty); } } [DllImport("uxtheme.dll", CharSet = CharSet.Unicode, EntryPoint = "GetCurrentThemeName")] private static extern int GetCurrentThemeName(StringBuilder pszThemeFileName, int dwMaxNameChars, StringBuilder pszColorBuff, int cchMaxColorChars, StringBuilder pszSizeBuff, int cchMaxSizeChars);
查找控件样式顺序
Framework在查找控件样式时,首先尝试查找特定主题名的资源,如果找不到,则查找Classic.xaml。最后再寻找常规资源(
generic.xaml
)。
确定BAML资源所在程序集
可以通过反编译查看控件定义程序集元数据的ThemeInfoAttribute特性来确定控件样式位于定义程序集还是外部程序集。ThemeInfoAttribute构造函数包括两个参数,前者指定特定主题的资源的位置,后者指定常规资源的位置。
外部程序集
WPF自带的控件,通过ILSpy反编译查看PresentationFramework程序集元数据,发现其中ThemeInfoAttribute特性信息如下:
ThemeInfo(ResourceDictionaryLocation.ExternalAssembly, ResourceDictionaryLocation.None)
表明控件无常规资源,控件的主题位于外部程序集中。
每种主题对应的DLL名称是PresentationFramework.主题名。例如Aero对应PresentationFramework.Aero.dll、Classic对应PresentationFramework.Classic.dll。
定义程序集
比如常用的Devexpress,通过ILSpy反编译查看DevExpress.Xpf.Core。
表明控件无特定主题资源,只有常规资源,位于当前程序集中。
反编译查看BAML资源
下面以ILSpy为例,说明如何查看BAML资源。
- 利用ILSpy打开程序集
- 展开程序集
- 展开程序集资源
- 展开(程序集名称).g.resources
- 确定BAML资源文件。无论是特定主题资源或者常规资源的XAML都位于Themes文件夹下,根据前述命名规则即可确定。
- 在BAML资源文件中找到控件样式。值得注意的是,资源字典可以嵌套包含资源字典,可能需要通过仔细分析多次查找才能定位到所需的控件样式。
下图为PresentationFramework特定主题BAML
下图为DevExpress.Xpf.Core.v15.2常规资源BAML,可见嵌套包含了其他资源字典。
不足
在某些情况下会比较复杂,需要抽丝剥茧,仔细分析。PresentationFramework中的控件还是比较好找,我一般都是通过x:Type (控件名称) 直接查找。
查看源码
原理
这个标题只是为了整齐。都有代码了,还说个啥,不光知其然,还可以知其所以然。
不足
该方法仅适用于开源控件。如Extended WPF Toolkit。
好消息是微软把WPF也开源了,样式相关代码位于Microsoft.DotNet.Wpf/src/Themes/XAML目录下。同一控件所有主题样式都在对应控件的XMAL文件中。