Windows XP 主题 API 的托管 C++ 包装器






4.97/5 (29投票s)
安全地从 C# 在任何操作系统上使用 XP 主题 API。
引言
Managed UxTheme 程序集是对 Windows XP 主题 API 的 .NET 封装。它可以在 C# 中安全使用,适用于支持 .NET 框架的任何 Windows 平台。除了暴露 UxTheme API 外,它还暴露了 TmSchema.h (Platform SDK 的一部分) 中的静态数据,这些数据用于定义哪些窗口类可以被主题化、这些类的哪些部分以及每个部分可以具有自定义外观的状态。
背景
Windows XP 使用一个 C 风格的 DLL 来暴露其主题功能,名为 UxTheme.dll ("Ux" 代表 User eXperience)。在我日常工作中,我正在用 C# 编写一些 .NET 自定义控件。当然,这些控件的一个要求是它们在适当的时候使用 Windows XP 主题,并反映用户当前的 the me 设置。
UxTheme.dll 的问题在于它只在 Windows XP 上可用,所以任何尝试直接使用它的 P/Invoke 代码在其他版本的 Windows 上都会失败。 Pierre Arnaud 编写了一个 C++ 封装 DLL,可以通过 P/Invoke 在任何支持 .NET 的 Windows 版本上安全调用。他的实现使用了 David Y. Zhao 的那个小巧的 C++ 封装类,该类动态链接到 UxTheme.dll,同时如果无法加载 UxTheme 库,还提供每个方法的安全故障转移实现。
我喜欢 Pierre 的解决方案,并开始使用它。然而,它并没有提供我需要的 UxTheme.dll 的功能级别。我的下一个想法是扩展他的工作,并继续从 C# 使用 P/Invoke。但我一直在寻找一个机会去做一些超越 "Hello World!" 的事情,而 Managed C++ 似乎是完美的时机。
此实现也使用了 David 的封装类 (CodeProject 上几乎所有 XP 主题相关的代码示例都是如此。这确实是一项很棒的工作!)。
实现
从外部视角来看,这个程序集有两个独立的部分。第一部分是 UxTheme
类。这个类提供了一个托管的 HTHEME
句柄封装。它暴露了 UxTheme DLL 中所有接受 HTHEME
作为实例方法的函数。不接受主题句柄的方法被暴露为静态方法。使用 DLL API 可以做到的任何事情,都可以通过这个类来实现。
与此并行的是一个对象层次结构,它封装了 TmSchema.h 和 Schemadefs.h 创建的属性表数据。这两个文件是 Platform 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
的实例,或者通过调用静态方法 UxTheme::OpenTheme
或 UxTheme::GetWindowTheme
来获取。
在尝试使用 UxTheme
的其他方法之前,请确保在代码中查看 UxTheme::IsAppThemed
。这将告诉你当前操作系统是否支持主题,以及当前是否启用了主题。而且别忘了,这在你应用程序的生命周期中可能会发生变化,因为用户可以随时关闭主题。
该程序集有一个强名称,所以你可以把它放到 GAC 中,如果你愿意的话。
演示项目包含了一个用 C# 编写并使用托管 API 的 David 的 Theme Explorer 应用程序 的重新实现。它还包括对 Pierre 提供的 TabPage 兼容控件 的轻微重构。它们仅仅作为演示,展示了该程序集如何从自定义控件实现中使用。
关注点
Managed C++ DLL 在入口点方面有一些有趣的限制。由于此 DLL 使用 C-Runtime,因此它是一个 混合模式 DLL。它使用 /NOENTRY 链接器标志进行编译。使用此标志链接的程序集没有显式的入口点,这会阻止任何静态数据的初始化,除了简单的、整型类型。如果能够将 CVisualStylesXp
类用作静态实例将会很好,但由于运行时不会初始化它,所以这是不可能的。
每个 UxTheme
实例都会创建一个指向 CVisualStylesXp
类实例的成员指针。UxThemes
的静态方法会在本地创建一个该类的实例。结果是大量对 LoadLibrary
和 FreeLibrary
的调用。幸运的是,Windows 对动态加载的 DLL 保持引用计数,所以这不会造成显著的性能损失。
尽管如此,这是我的实现中让我感到困扰的一个地方。如果有人知道如何只加载和释放 UxTheme DLL 一次,并避免创建如此多的 C++ 封装类实例的好方法,我非常乐意听到。MSDN 建议实现显式的 Initialize
和 Deinitialize
方法,但我不想强迫这个程序集的用户这样做。
历史
- 版本 1 - 初始发布
- 08/26/2003
- Bug 修复
- API 的一些重组