在Windows XP系统中,UxTheme.dll 提供了丰富的用户界面主题功能。然而,直接使用这个 DLL 会遇到兼容性问题,因为它仅在 Windows XP 上可用。为了解决这个问题,开发者们通常会创建一个.NET封装层,以便在任何支持 .NET Framework 的 Windows 平台上安全地使用这些主题功能。本文将介绍如何实现这样的封装,并探讨一些关键的技术细节和使用场景。
UxTheme.dll 是Windows XP系统中用于提供主题功能的动态链接库。在日常工作中,正在开发一些使用C#编写的.NET自定义控件。这些控件的一个要求是,它们需要在适当的时候使用Windows XP主题,并反映用户当前的主题设置。
但是,UxTheme.dll 只在 Windows XP 上可用,因此任何尝试直接使用它的P/Invoke代码都会在其他版本的 Windows 上失败。为了解决这个问题,Pierre Arnaud 编写了一个 C++ 封装 DLL,可以通过 P/Invoke 在任何.NET支持的 Windows 版本上安全调用。他的实现使用了 David Y. Zhao 的 C++ 封装类,这个类可以动态链接到 UxTheme.dll,并且在无法加载 UxTheme 库时提供每个方法的安全回退实现。
这个封装库从外部视角来看有两个独立的部分。首先是 UxTheme 类,它提供了一个托管的 HTHEME 句柄的包装。它公开了 UxTheme DLL 上的所有方法,这些方法接受 HTHEME 作为实例方法。不接受主题句柄的方法则作为静态方法公开。任何可以通过 DLL API 实现的操作都应该可以通过这个类来完成。
与此并行的是一个对象层次结构,它包装了由 TmSchema.h 和 Schemadefs.h 创建的属性表数据。这两个文件是平台 SDK 的一部分,定义了 Windows UI 的哪些部分可以被主题化。
这个对象层次结构从 ThemeInfo 类开始。这个类包含了一些关于当前主题的简单元数据,但也包含了一个 WindowThemes 的集合。WindowTheme 表示特定窗口类的部分的数据。WindowTheme 类有一个 ThemeParts 的集合,以及 ThemePartStates。
ThemePart 代表窗口类的一个离散部分。例如,向下按钮是 ScrollBar 窗口类的一部分。每个 WindowTheme 和 ThemePart 可以有 0 到 n 个状态。ThemePartStates 包括 normal、disabled 或 hot 等状态。WindowTheme 类有一个 UxTheme 属性,可以用来获取特定于窗口类的 UxTheme 类的实例。
ThemePart 和 ThemePartStates 也包含一些实例方法,以便它们可以直接渲染到图形上下文中。UxTheme 类可以在没有 ThemeInfo 层次结构的情况下使用,但这个层次结构确实为整个事情提供了一个更面向对象的面貌。
使用代码相当简单。所有的类都在 System.Windows.Forms.Themes 命名空间中。唯一可以公开创建的类是 ThemeInfo。要深入了解窗口类、它们的部分和状态,创建一个 ThemeInfo 的实例,然后开始查看它的 WindowThemes 集合。
可以通过 WindowTheme 的实例,或者通过调用静态方法 UxTheme::OpenTheme 或 UxTheme::GetWindowTheme 来获取 UxTheme 的实例。在尝试使用 UxTheme 的其他方法之前,请确保查看 UxTheme::IsAppThemed,这将告诉当前操作系统是否支持主题,如果是的话,它是否当前被主题化。不要忘记,这种情况在应用程序的生命周期中可能会发生变化,因为用户可以随时关闭主题。
这个程序集有一个强名称,所以如果想的话,可以将其放入 GAC 中。演示项目包含了 David 的 Theme Explorer 应用程序的重新实现,它是用C#编写的,并使用了托管 API。它还包括 Pierre 提供的 TabPage 兼容控件的轻微重构。它们只是为了展示这个程序集如何从自定义控件实现中使用。
托管的 C++ DLL 在入口点方面有一些有趣的限制。由于这个 DLL 使用了 C-Runtime,它是一个混合模式 DLL。它使用 /NOENTRY 链接器标志进行编译。与这个标志链接的程序集没有显式的入口点,阻止了除简单、整型之外的任何静态数据的初始化。使用 CVisualStylesXp 类作为静态实例会很好,但由于运行时不初始化它,这是不可能的。
每个 UxTheme 的实例都会创建一个指向 CVisualStylesXp 类实例的成员指针。UxThemes 的静态方法在本地创建这个类的实例。结果是对 LoadLibrary 和 FreeLibrary 的大量调用。幸运的是,Windows 对动态加载的 DLL 保持引用计数,所以这不应该造成显著的性能损失。