您的位置:首页 > 编程语言 > C语言/C++

Visual C++ 中窗口子类化和超类化技术的应用

2012-09-05 15:53 169 查看
Visual C++ 中窗口子类化和超类化技术的应用

摘要: 本文介绍了窗口子类化 (SubClassing) 和超类化 (SuperClassing) 的概念、技术原理、作用以及在Visual C++ 6.0, 中的实现方法, 并给出了一个具体应用实例. 实践证明, 适当地应用窗口子类化和超类化技术, 可以大大增强应用程序的功能.

关键词: 子类化; 超类化; 窗口函数; Visual C++

引言

在我们用Visual C++开发应用程序的过程中, 有时可能需要改变原有通用 Windows 控件的行为, 使控件实现我们自己希望的特性. 使用窗口的子类化和超类化来实现以上功能, 是一条非常便捷的途径. 适当使用窗口的子类化和超类化创建出容易使用的新窗口类, 往往可以使您的程序界面更人性化.

1 窗口的子类化和超类化的基本概念

在讲述窗口的子类化和超类化之前, 我们必须先了解Windows窗口类的概念. Windows的窗口类是Windows用来创建窗口的依据之一, 每个窗口必然属于某个窗口类. 窗口类是一个窗口模板, 包含一个窗口所具有的部分窗口属性. 编写一个Windows程序时, 首先要做的工作就是注册一个窗口类, 然后基于此注册的窗口类创建一个新的窗口. 在Win32平台中, 注册窗口类的API函数是RegisterClass 和 RegisterClassEx, 其中RegisterClassEx是推荐使用的函数, 使用这个函数注册窗口类时,
需要先填写一个WNDCLASSEX结构. 这个结构实际上反映了一个窗口类的特征, 一个窗口类有本类所有窗口公用的类属性、窗口函数、类图标和小图标、类鼠标、窗口背景刷、类菜单, 当然还有类名. 除此之外, 每个类还有一定大小的类存储区, 可以用来存储该类的公共数据.

每一个创建的窗口都有一个窗口函数, 其地址由WNDCLASSEX结构的lpfnWndProc参数设定, 该窗口函数处理对应于该窗口类的所有实例的消息. 当创建一个窗口时, Windows将分配一个内存块, 用来存放与该窗口相关的信息, 并将参数lpfnWndProc从窗口类内存块拷贝到该内存块中. 当消息被分发到窗口时, Windows检查该窗口中内存块中的lpfnWndProc值, 并调用该内存块地址上的窗口函数.

一个窗口的行为主要取决于它的窗口函数, 如果能够改变一个窗口的窗口函数, 使它指向自己写的某个函数, 那就意味着发给这个窗口的各种消息将由我们自己写的这个函数来处理.

子类化一个窗口, 实际上就是改变窗口内存块中的窗口函数的地址, 使其指向用户自定义的新的窗口函数入口, 以实现用户希望的窗口特性.

超类化则是利用原来的那个窗口类的某些特征, 改变它另外的一些特征, 包括窗口函数, 重新注册一个新的窗口类.

超类化和子类化的共同之处就是, 这两种方法都是从一个已经存在的窗口类产生新的窗口或窗口类的方法, 新的窗口或窗口类具有原来的窗口类的某些特征, 也具有一些新增的特征. 但子类化是从窗口的角度出发的, 而超类化是从窗口类的角度出发的.

2 Visual C++中窗口子类化的实现

(1) 窗口子类化的优点

窗口子类化技术最大的特点就是能够截取Windows 的消息. 一旦用户自定义的窗口函数截取了传向原窗口函数的消息, 就可以对被截取的消息

进行如下处理:

将其传给原来的窗口函数. 这是对大多数消息应该采取的措施, 因为子类通常只对原来的窗口特性作少量的修改.

截取该消息, 阻止其向原窗口函数发送.

修改该消息, 修改完毕以后再向原窗口函数发送.

Windows SDK提供了一些设计好的窗口类, 如EDIT、LISTBOX、TREEVIEW等. 通过截取这些通用窗口类的消息, 用户程序可以为它们添加新的特性, 改善其外观,扩充其功能.

子类化的优点主要体现在以下两个方面: 首先, 它不需要创建新的窗口类, 不需要了解一个窗口的窗口过程. 这在原来的窗口函数是由别人编写, 而且创建过程不可见的情况下非常有用; 其次, 子类化比较容易实现, 因为所有要做的工作仅仅就是写一个窗口函数.

(2) 窗口子类化的实现

窗口子类化实现的基本步骤如下:

正常创建原始窗口, 得到窗口的句柄.

调用GetWindowLong得到原来的窗口函数OldWndProc.

调用SetWindowLong设置新的窗口函数NewWndProc.

新的窗口函数的代码如下所示:

LRESULT NewWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

if (message == WM_XXX)

{

// 截取自己感兴趣的消息, 作一些处理, 达到改变特性的目的

}

// 必要时可以调用原来的窗口函数, 使被子类化的窗口仍具有原来的很多特性

return CallWndProc(OldWndProc, hWnd, message, wParam, lParam);

}

值得注意的是, 在调用旧的窗口函数时, 不能直接用OldWndProc(...), 而必须用函数CallWndProc进行调用, 否则会出现堆栈错误.

3 Visual C++ 中窗口超类化的实现

(1) 窗口超类化的优点

在Windows 编程中, 使用窗口子类化技术, 可以方便地达到改变一个窗口的特性的目的. 但子类化也存在其局限性. 实际上, 子类化的概念是针对一个已经创建的窗口来谈的, 所以修改窗口函数是在窗口创建之后进行的, 在窗口创建期间的消息无法捕获, 也就无法处理. 另外有些窗口的特性与窗口类本身的属性有关. 比如如果一个窗口类没有CS_DBLCLKS属性的话, 那么要通过子类化这些窗口达到处理WM_LBUTTONDBLCLK消息的目的. 对于子类化的以上局限性, 可以通过窗口的超类化技术来消除. 实际上超类化可以完全实现子类化的功能.

(2) 窗口超类化的实现

超类化需要注册一个新的窗口类, 达到改变窗口类的各种特征的目的. 超类化实现的简单过程是获得一个已经存在的窗口类的特征, 然后改变这些特征, 最后重新注册一个窗口类. 具体的步骤如下:

定义一个类型为7&#’AO>>$C 的变量. 因为需要注册新的窗口类, 定义这个变量是必要的.

调用GetClassInfoEx函数得到希望超类化的那个窗口类的信息.

改变窗口类的基本特征, 显然窗口类名和模块句柄hInstance是必须改变的. 注意如果需要改变窗口类的窗口函数的话, 在改变窗口函数之前应该保存原来的窗口函数, 并且在新的窗口函数中把不需要处理的消息传递给原来的窗口函数, 以保留原窗口类的一些特征.

利用修改后的WNDCLASSEX变量, 调用RegisterClassEx函数重新注册一个新的窗口类.

创建这个新窗口类的一个窗口实例.

4 应用举例

下面笔者给出一个应用实例, 完成以下功能:

在MDI的灰色客户区中双击鼠标可以打开"New"对话框, 和从"File"菜单中点"New"的效果一样, 而如果是按住Ctrl键双击灰色客户区弹出的是"Open"对话框.

由于MDI客户窗口所属窗口类"MDIClient"没有CS_DBLCLKS属性, Windows系统不会为这个窗口产生WM_LBUTTONDBLCLK消息, 因此仅仅使用子类化是不能实现该功能的, 本例同时使用了窗口的子类化和超类化. 创建该例的具体步骤和相关代码如下:

(1) 用MFC Application Wizard新建一个MDI 程序 SuperClassing.

(2) 利用ClassWizard建一个从CWnd类派生的新类CDblClkWnd. 添加MDI客户窗口对左键双击的处理函数:

void CDblClkWnd::OnLButtonDblClk(UINT nFlags, CPoint point)

{

if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) == 0x8000)

{

// 判断CONTROL键是否被按下

AfxGetMainWnd()->SendMessage(WM_COMMAND, ID_FILE_OPEN, 0);

}

else

{

AfxGetMainWnd()->SendMessage(WM_COMMAND, ID_FILE_NEW, 0);

}

}

(3) 重新注册一个窗口类, 以进行超类化.

给CDblClkWnd类添加一个用于注册新窗口类的函数RegeisterNewMDIClass, 函数申明如下:

static BOOL RegisterNewMDIClass();

函数实现如下:

BOOL CDblClkWnd::RegisterNewMDIClass()

{

WNDCLASS wc;

if (!GetClassInfo(NULL, "MDIClient", &wc))

// 获取客户窗口类的信息

return FALSE;

wc.style = CS_DBLCLKS;

// 添加CS_DBLCLKS属性, 使窗口能够接收WM_LBUTTONDBLCLK消息

wc.lpszClassName = "DBLCLKMIDClient";

// 修改注册类名

return RegisterClass(&wc); // 注册新的窗口类

}

在CSuperClassingApp类的InitInstance函数中的创建主框架的代码之前调用上面的注册新窗口类的函数, 代码如下:

if (!CDblClkWnd::RegiserNewMDIClass())

return FALSE;

注意, 超类化不能改变已经创建的窗口的属性, 只能在创建的时候就按照新窗口类来创建. 因此, 需要在CMainFrame类中重载CreateClient函数, 函数申明如下:

virtual BOOL CreateClient(LPCREATESTRUCT lpCreateStruct, CMenu *pWindowMenu);

函数的实现部分代码可以从winmdi.cpp中的CMainFrame::CreateClient函数中拷贝过来, 作如下

修改:

修改dwExStyle的赋值为WS_EX_CLIENTEDGE, 使新建的窗口具有3D的外形.

因为在CMainFrame类中没有对afxData的定义, 所以应删除如下代码:

if (afxData.bWin4)

{

// special styles for 3d errect on Win4

dwStyle &= ~WS_BORDER;

dwExStyle = WS_EX_CLIENTEDGE;

}

在调用API函数CreateWindowEx创建MDI客户窗口时, 把原来的窗口类"MDIClient"改成我们注册的新窗口类"DBLCLKMIDClient".

给主窗口添加一个变量CDblClkWnd m_client, 然后在主窗口的OnCreate函数中对MDIClient窗口子类化, 并在OnDestroy函数中实现反子类化. 代码如下:

int CMainFrame::Oncreate(LPCREATESTRUCT lpCreateStruct)

{

...

m_client.SubclassWindow(m_hWndMDIClient);

// 子类化 MDIClient 窗口

return 0;

}

void CMainFrame::OnDestroy()

{

m_client.UnsubclassWindow(); // 反子类化

CMDIFrameWnd::OnDestroy();

}

编译执行该程序即可实现以上功能.

结语

在Windows 编程中, 适当使用子类化和超类化创建出易于使用的新窗口类, 可以使您的程序界面更人性化. 子类化和超类化各有自己的特点, 子类化简单易行, 而超类化则功能丰富, 可以弥补子类化的一些不足.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: