posts - 3, comments - 0, trackbacks - 0, articles - 1

2006年2月16日

引言:

       现在的应用软件都讲究个性化,因此提供一套漂亮的皮肤就必不可少。这就需要用到一些控件肤化技术。常规是重载MFC的消息列表或虚函数来实现的,但引来的问题是肤化库和应用程序的耦合性太强。经常会由于肤化代码的一处小改动而引起逻辑上并没有关联的整个应用程序大规模的编译和链接,并带来开发效率的急剧下降,这在开发大型软件时是很难忍受的。

       本文在参考了一些常用肤化技术后提供了自己的解决方案:采用替换窗体过程,皮肤库高度独立,内部完善与修改毫不影响应用程序。

 

常见肤化方法:

第一种:利用继承关系直接子类化

第二种:在一个对话框的 OnInitDialog 中逐控件子类化

第三种:在 Hook 中拦截窗口创建消息,并进行子类化

第四种 SetWindowLong替换窗口过程

第五种 Hook中替换窗口过程

各种肤化技术的原理:

第一类方法采用的是MFC子类化方法来实现(前三种)

1.                MFC 通过维护内部一张ChandleMap来记录Window控件与控件子类对象之间的关系。在MFC的全局窗口过程AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)中,通过调用CWnd::FromHandlePermanent(HWND hWnd)得到参数句柄所依附的C+控件子类对象,并调用虚函数CWnd::WindowProc将相应的消息送给相应的控件子类对象的窗口过程

2.         控件子类对象调用CWnd::SubclassWindow登记控件与控件子类对象之间的关系

第二类方法采用替换窗口过程方法来实现(第四、五种)

肤化技术分析:

MFC采用具有层次结构的控件子类设计:通过一张Map表建立控件句柄与C++对象(控件子类对象)的联系,通过控件子类对象的变量来记录和维护控件的状态信息

“肤化”的本质:

1)响应发送给该控件的Windows消息,

2)用恰当的数据表现出恰当的行为。

其中:

1)可以用替换窗体过程来实现,

2)可以用维护自定义控件子类对象和控件句柄的关系来实现。

 

本文独立肤化库采用的方法:

1.       目标:

肤化目标窗口,接口简单,并且被肤化的窗口影响小。

2.         采用方法:

替换窗口过程

3.       难点

窗口过程的设计,是否需要为每个类都设计单独的窗口过程?工作量是否过大?

维护状态变量,当有多个控件共享同一窗口过程时,如何维护它们各自的状态?

实现方法:

1.         仿照MFC,建立统一的窗体过程SkinWndProc

2.         建立一张Map表,用来登记hWnd和自定义控件子类对象之间的联系。

3.         创建一个皮肤父类,所有的肤化控件均从它派生(类似与MFCCWnd),可以在统一的窗体过程SkinWndProc中通过Map表找到相应的自定义控件子类对象,并调用其虚函数实现对Windows消息的响应

4.         重载:SubclassWindow函数,用来完成窗体过程的替换。

外部接口:

1.         提供SkinDlg(HWND hWnd)完成对对话框及其子控件的肤化

2.         提供SkinBtn(HWND hWnd, LPCTSTR lpszSection)指定采用配制文件的哪一节属性肤化按钮

3.         如果需要,提供DonotSkinCtrl(HWND hWnd)用来指定不需要肤化的控件

4.         提供ChangeSkinLPCTSTR lpszSkinName)更换皮肤

框架原理图:
框架原理图.bmp

时序图:

时序图:.bmp

posted @ 2006-02-16 22:46 VC进阶| 编辑 收藏

2006年2月13日

引言:

       在上一篇文章中,我们引入了一个组件化编程的架构,其实它是一个仿COM架构。尽管相对于COM而言它已经相当的简单了,但总的来说它仍是比较复杂。本文引入了另一种组件化编程的实现架构,相对于仿COM篇而言,它的结构更加清晰,实现更加简单。

 

组件化编程再分析:

       其实引入组件化编程的关键目的是为了将程序模块化,使各个模块之间可以单独开发,单独测试。当然,随之而来的DLL版本问题也必须要解决,防止出现不兼容的组件版本破坏程序的稳定性。只需要达到几个目的就行了,我们没必要把事情做得太过于复杂。

 

实现:

       将程序模块化,使之可以单独开发、测试而不影响其它的模块,仍然需要将每个模块分解到DLL去实现。通常因为面向对象、函数同名、函数地址偏移,使用方便等等原因,我们不希望直接使用DLL导出API,而希望能使用接口,以面向对象的方式编程。为了解决版本问题,我们给每个接口的实现版本加个标示。既我们可以要求DLL给出1.0版的接口实现。2.0版的接口实现等。还有,我们还要告诉DLL我们想获得哪个接口。于是统一起来,给每个接口加个唯一ID,因为通常我们的程序不会在全球发行,所以也没必要使用GUID,使用字符串标示就行了。

每个接口的实现都有自己的创建函数,所以我们在每个模块中建立一张表,维护接口标示与接口创建函数的关系

接口标示

全局创建函数

“VUIManager001”

__g_CreateVUIManager001interfaceNam_reg()

“VUIManager002”

__g_CreateVUIManager002interfaceNam_reg()

“VSkin001”

__g_CreateVSkin001interfaceNam_reg()

“VSkin002”

__g_CreateVSkin002interfaceNam_reg()

DLL中导出接口创建API

 DLL_EXPORT void*CreateInterface(const char *pInterfaceName, int *pReturnCode);

简化接口的创建过程.bmp
小结:

       本实现仅是组件化编程的一个简化实现,它并不能取代《仿COM篇》,它所实现的功能也不如《仿COM篇》那么强大,例如它需要实现知道接口所在的DLL,不能实现自扩展等。但对于一般的程序而言,它更简单使用。

还可以使用宏定义 把接口标示和接口创建函数与创建过程关联起来,将接口创建API也包装起来,开发中将更加方便。只需一个宏就可以搞定这些操作。

posted @ 2006-02-13 21:55 VC进阶| 编辑 收藏

2006年1月25日

     摘要: 引言:        在大型项目的开发中,随着开发进度的进行,我们经常碰到模块之间耦合度太高的问题:由于开发人员经常要在别的模块中调用自己实现的功能,经常随意在某个函数中随意添加调用代码,造成了被修改的那个函数体过长,逻辑混乱。另一个问题是随意包含头文件:开发人员在开发中经常为了要使用某些类的功能而包含引用类的头文件造成类之间的耦合度太...  阅读全文

posted @ 2006-01-25 21:07 VC进阶| 编辑 收藏