65.9K
CodeProject 正在变化。 阅读更多。
Home

SkinX,一个皮肤插件包的框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (35投票s)

2004年5月17日

7分钟阅读

viewsIcon

229747

downloadIcon

12417

介绍了一个皮肤插件实现的框架。

Sample Image - SkinX.jpg

引言

我不确定那些商业皮肤组件使用了什么样的技术,但本文将为您提供一个这样的皮肤框架的实现。该代码是一个未完成项目的组成部分。该项目最终由于某些商业原因而被放弃,从未投入使用。项目的初衷是创建具有相同接口的不同 COM 对象,以便宿主应用程序可以选择其中一个对象,从而获得不同的外观和感觉。

附加的源代码包含了一个小型子集的 Mac OS X 外观/感觉对象和一个演示宿主应用程序。该应用程序只需创建皮肤对象并调用 InstallHook 方法,应用程序中的所有按钮就会获得 Mac 的外观/感觉。为了简化,我删除了 COM 对象实现中的其他代码。总之,本文旨在解释框架,而不是提供一个可立即使用的项目。

皮肤理论

皮肤组件需要修改应用程序窗口的默认外观/感觉,例如按钮、组合框等。为此,必须将自定义窗口过程插入到窗口类中。Windows 挂钩功能允许我们这样做。使用 WH_CALLWNDPROC 作为挂钩 ID 调用 SetWindowsHookEx 允许我们在 Windows 消息发送到目标窗口过程之前监视所有消息,因此我们可以拦截某些消息并对其进行处理,然后将其传递给原始窗口过程,甚至丢弃一些消息。

安装挂钩后,所有 Windows 消息都会进入我们的挂钩过程。但是,选择必须处理的消息并非易事。有数千条消息与不同类型的窗口相关。对于同一条消息,我们必须以不同的方式处理不同类型的窗口。

关于挂钩和消息处理还有很多要谈的,但我们还是直接给出解决方案。我们为每种不同的窗口定义不同的类。例如,我们定义一个 CMacButton 类来包装窗口过程,该过程将为按钮窗口提供 Mac OS 的外观/感觉。在我们的挂钩过程中,我们只处理一条消息:WM_CREATE。然后我们检查它是否是即将创建的按钮窗口。如果是,我们创建一个 CMacButton 实例,并使用 SubclassWindow 将我们的窗口过程挂钩到目标窗口。然后,CMacButton 实例将承担处理窗口消息的责任。

总而言之,我们使用 SetWindowsHookEx 来拦截 WM_CREATE 消息。对于我们希望改变外观/感觉的每个窗口,我们创建一个对象,并使用 SubclassWindow 将对象与窗口连接起来。

基础结构

好的,无论您是否理解以上内容,我都已完成了理论部分。但是,从理论到可执行代码还有很长的路要走。框架,或者说基础设施,是这个项目的关键部分。SkinX 框架从 ATL/WTL 库中借鉴了很多。我们先来看看 UML 模型

控件类

如上所述,我们必须为每个控件定义不同的窗口类,例如 CMacButtonCMacEdit。这些类处理不同窗口的各种消息。所有这些窗口类,我们称之为控件类,都派生自一个共同的基类:CWidgetHook,而 CWidgetHook 又派生自 CWidgetHookBase。让我们深入了解这些类。

CWidgetHookBase 仅定义接口,该接口仅包含一个方法:Install。框架调用 Install 将实例挂钩到目标窗口。InstallCWidgetHook 类中实现。CWidgetHook 还定义了一些方法,这些方法应该在派生类中被重写,它们是

 ////////////////////////////////////////////////////////////////////
 // overrides, we don't need virtual member since we use the ATL way
 void Initialize() {}; //instance initialize
 void Finalize() {}; //instance finalize
 static void InitializeClass() {}; //class initialize
 static void FinalizeClass() {}; //class finalize

前两个方法在每次创建或销毁单个实例时被调用。后两个方法在类的第一个实例创建时或类的最后一个实例销毁时被调用。定义后两个方法的原因是,对于每个类,都有一些通用的资源是必需的。例如,所有复选框都需要一些位图。为每个实例维护一份这样的资源副本效率不高。因此,最好使用静态成员来保存这些资源,并使用静态成员函数来初始化和释放它们。

CWidgetHook 还有一个静态成员:m_lRef,它是一个引用计数器,以便类知道有多少实例存在。当最后一个实例被销毁时,将调用 FinalizeClass 来清理静态成员,从而减少内存占用。CWidgitHook:OnFinalMessage 会删除实例本身,所以我们不必担心清理。

CWidgetHook 是一个 C++ 模板类,一个模板参数是派生类。这个概念是从 ATL 借鉴的。另一个参数是 CWindow 兼容的类,它可以是 WTL 包装类,这样我们就可以在派生类中使用 WTL 包装器方法。这样的设计使窗口过程的实现不那么痛苦。实际上,编写一个 CWidgetHook 派生类就像编写一个 WTL 窗口类一样简单。ATL 窗口和消息映射宏在编写和维护代码方面提供了很大的帮助。

然而,编写皮肤包中最难的事情仍然是编写这些 CWidgetHook 派生类。对于大多数这类类,必须处理 WM_PAINT 消息才能给窗口带来新的外观。还需要捕获其他一些消息,以便了解窗口的状态。附加的演示项目包含了一个 CMacButton 类的实现,它实际上为按钮、复选框和单选按钮实现了 Mac 外观/感觉,因为这三种控件都有相同的窗口类名:Button。请自行查看代码,了解如何编写控件类。

反射器挂钩

存在反射器的原因是因为许多控件窗口消息只是发送到它们的父窗口,而不是它们本身。反射器使父窗口将这些消息发送回原始控件窗口。ATL 和 MFC 都支持反射器。在这里,我们使用自己的反射器实现来避免冲突。

控件工厂

当所有控件类都准备就绪后,我们需要一种方法将这些类与控件窗口进行映射,并将它们安装到目标控件窗口。CWidgetFactory 实现了抽象工厂模式。它使用控件窗口的类名来创建一个相应的实例。CreateWidget 方法接受目标窗口的类名,并返回一个 CWidgetHookBase 接口。CreateWidget 方法是抽象的,必须在派生类中实现。

挂钩过程

有了上述基础设施,挂钩过程变得非常简单明了。它只需检查目标窗口的类名,向控件工厂请求一个 CWidgetHookBase 接口,并在接口上调用 Install 方法来安装类。

如何扩展

该框架旨在易于扩展。要编写皮肤包,首先需要从 CWidgetHook 派生一系列类,实现它们以重写默认行为,从而改变目标窗口的外观/感觉。然后,通过从 CWidgetFactory 派生一个类来创建您的控件工厂,实现 CreateWidget 方法来按窗口类创建实例。然后您就基本完成了,COM 对象代码和挂钩过程与演示项目中的相同。

就是这样

好了,关于这个框架就差不多了。它完全可扩展,并将效率作为主要考虑因素。花些时间来理解这个框架,并开始编写一些富有创意的代码,一个强大的皮肤包就能诞生。

这就是我的贡献。它并不完美。如果您发现有什么地方应该改进,请告诉我。如果您觉得它有用,那么就使用它。如果您用它创造出精彩的东西,请告诉我。我不会声称任何东西,但这会让我高兴。如果我能参与到一个精彩的项目中,那将是令人兴奋的。

© . All rights reserved.