超类化和子类化没有具体的代码,其实是一种编程技巧,在MFC和WTL中可以有不同的实现方法。
参考 :http://www.360doc.com/content/10/1115/13/3972135_69517213.shtml
窗口子类化:
原理就是改变一个已创建窗口类的窗口过程函数。通过截获已创建窗口的消息,从而实现监视或修改已创建窗口类的行为属性。可以用来改变或者扩展一个已存在的窗口的行为,而不用重新开发。比如要获得那些预定义控件窗口类(按钮控件、编辑控件、列表控件、下 拉列表控件、静态控件和滚动条控件)的功能而又要修改它们的某些行为。
子类化的优点主要体现在以下两个方面:首先,它不需要创建新的窗口类,不需要了解一个窗口的窗口过程。这在原来的窗口函数是由别人编写,而且创建过程不可见的情况下非常有用;其次,子类化比较容易实现,因为所有要做的工作仅仅就是写一个窗口函数。
主要步骤为
- 截取该消息,阻止其向原窗口函数发送。
- 修改该消息。
- 修改完毕以后再向原窗口函数发送。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
// 保存窗口默认的消息响应函数指针
WNDPROC pSubclassOldEditProc;
// 用于替换子类化窗口的消息响应函数
LRESULT
CALLBACK JcEditProcSubClass(
HWND
hWnd,
UINT
message,
WPARAM
wParam,
LPARAM
lParam)
{
switch
(message)
{
case
WM_CHAR:
{
::MessageBox(hWnd,
"WM_CHAR响应"
,
"子类化"
, MB_OK);
return
0;
}
//使用完后,消息发回原窗体
default
:
return
::CallWindowProc(pSubclassOldEditProc, hWnd, message, wParam, lParam);
}
}
// 对创建好的窗体进行子类化代码
{
// 创建
HWND
hEdit = CreateWindowEx(NULL,
"EDIT"
,
"SubClass"
,
WS_CHILD|WS_BORDER|ES_LEFT|ES_AUTOHSCROLL, 100,120, 128, 16, hWnd, NULL, hInstance, NULL);
//修改窗口属性,改变消息响应函数
pSubclassOldEditProc = (WNDPROC)::SetWindowLong(hEdit, GWL_WNDPROC, (
DWORD
)JcEditProcSubClass);
// 显示
ShowWindow(hEdit, nCmdShow);
UpdateWindow(hWnd);
}
|
窗口超类化:
窗口超类化是在窗口类——WNDCLASS或WNDCLASSEX(非MFC类概念)级别进行的改变窗口类特征的。改变已有窗口类的行为属性。
- 通过调用 GetClassInfoEx 来获得想要进行超类化操作的窗口类的信息。函数GetClassInfoEx 需要一个指向 WNDCLASSEX 结构的指针,用于当成功返回时填入窗口类的信息。
- 按需要修改 WNDCLASSEX 结构的成员,其中有两个成员必须修改:
hInstance 存放程序的实例句柄
lpszClassName 指向一个新类名的指针
不必修改成员 lpfnWndProc,但大多数情况下还是需要的。但要记住如果要使用函数 CallWindowProc 调用老窗口的过程,那就必须保存成员 lpfnWndProc 的原值。 - 注册修改完的 WNDCLASSEX 结构,得到一个具有旧窗口类某些特性的新窗口类。
- 用新窗口类创建窗。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
WNDPROC pSuperOldEditProc;
// 保存窗口默认消息处理函数
// 用于替换的超类化消息响应函数
LRESULT
CALLBACK JcEditProcSuper(
HWND
hWnd,
UINT
message,
WPARAM
wParam,
LPARAM
lParam)
{
switch
(message)
{
case
WM_CHAR:
{
::MessageBox(hWnd,
"WM_CHAR响应"
,
"超类化"
, MB_OK);
return
0;
}
default
:
return
::CallWindowProc(pSuperOldEditProc, hWnd, message, wParam, lParam);
}
}
// 创建超类化控件代码
{
// 取得原控件信息
WNDCLASSEX myeditClass;
::GetClassInfoEx(hInstance,
"EDIT"
, &myeditClass);
// 保存原控件默认消息处理函数
pSuperOldEditProc = myeditClass.lpfnWndProc;
// 设置替换的消息处理函数
myeditClass.lpfnWndProc = JcEditProcSuper;
// 指定新的窗口类名字
myeditClass.lpszClassName =
"JcilyEdit"
;
// 设置结构体大小
myeditClass.cbSize =
sizeof
(WNDCLASSEX);
// 注册新信息
RegisterClassEx(&myeditClass);
// 创建
HWND
hEdit = CreateWindowEx(NULL, myeditClass.lpszClassName,
"SuperClass"
,
WS_CHILD|WS_BORDER|ES_LEFT|ES_AUTOHSCROLL, 100,100, 128, 16, hWnd, NULL, hInstance, NULL);
// 显示
ShowWindow(hEdit, nCmdShow);
UpdateWindow(hWnd);
}
|
窗口子类化和超类化的区别
(1) 子类化修改窗口过程函数, 超类化修改窗口类(新的窗口类名)
(2) 子类化是在窗口实例级别上的,超类化是在窗口类(WNDCLASS)级别上的。
(3) 超类化可以完成比子类化更复杂的功能,在SDK范畴上,可以认为子类化是超类化的子集。
(4) 子类化只能改变窗口创建后的性质,对于窗口创建期间无能为力(无法截获ON_CREATE 事件),而超类化可以实现;超类化不能用于Windows创建的窗口,子类化可以。
(5) 超类化可以修改包含窗体背景等属性,而子类化不能。
*********************************************************************************************************************************************
在windows平台上,使用C++实现子类化和超类化常用的API并不多,由于这些API函数的详解和使用方法,网上一大把。本文仅作为笔记,简单的记录一下。
子类化:SetWindowLong,GetWindowLong,CallWindowProc,FindWindowEx
超类化:GetClassInfoEx,RegisterClassEx,UnRegisterClass
以上函数在代码中的使用见《C++ 中超类化和子类化》
VC中基于SDK编程的窗口子类化
VC中基于SDK编程的窗口子类化的基本步骤如下:
(1)正常创建原始窗口,得到窗口的句柄。
(2)调用GetWindowLong得到原来的窗口函数OldWndProc。
(3)调用SetWindowLong设置新的窗口函数NewWndProc。
(4)在窗口函数NewWndProc中使用CallWindowProc将消息发到原来的窗口函数OldWndProc。
SetWindowLong
函数功能:该函数改变指定窗口的属性.函数也将指定的一个32位值设置在窗口的额外存储空间的指定偏移位置。
函数原型:LONG SetWindowLong(HWND hWnd,int nlndex,LONG dwNewLong);
参数:
hWnd:窗口句柄及间接给出的窗口所属的类。
nlndex:指定将设定的大于等于0的偏移值。有效值的范围从0到额外类的存储空间的字节数-4:例如若指定了12位或多于12位的额外类存储空间,则应设为第三个32位整数的索引位8。要设置其他任何值,可以指定下面值之一:
GWL_EXSTYLE:设定一个新的扩展风格。GWL_STYLE:设定一个新的窗口风格。
GWL_WNDPROC:为窗口过程设定一个新的地址。GWL_ID:设置一个新的窗口标识符。
GWL_HINSTANCE:设置一个新的应用程序事例句柄。
GWL_USERDATA:设置与窗口有关的32位值。每一个窗口均有一个由创建该窗口的应用程序使用的32位值。
GWL_ID 设置一个新的窗口标识符。
当hWnd参数标识了一个对话框时,也可使用下列值:
DWL_DLGPROC:设置对话框过程的新地址。
DWL_MSGRESULT:设置在对话框过程中处理的消息的返回值。
DWL_USER:设置的应用程序私有的新的额外信息,例如一个句柄或指针。
dwNewLong:指定的替换值。
返回值:如果函数成功,返回值是指定的32位整数的原来的值。如果函数失败,返回值为0。若想获得更多错误信息,请调用GetLastError函数。
备注:如果由hWnd参数指定的窗口与调用线程不属于同一进程,将导致SetWindowLong函数失败。
如果使用带GWL_WNDPROC索引值的SetWindowLong函数替换窗口过程,则该窗口过程必须与WindowProccallback函数说明部分指定的指导行一致。
如果使用带 DWL_MSGRESULT索引值的SetWindowLong函数来设置由一个对话框过程处理的消息的返回值,应在此后立即返回TRUE。否则,如果又 调用了其他函数而使对话框过程接收到一个窗口消息,则嵌套的窗口消息可能改写使用DWL_MSGRESULT设定的返回值。
通过使用函数RegisterClassEx将结构WNDCLASSEX中的cbWndExtra单元指定为一个非0值来保留新外窗口内存。
不能通过调用带GWL_HWNDPARENT索引值的SetWindowLong的函数来改变子窗口的父窗口,应使用SetParent函数。
GetWindowLong
函数功能:该函数获得有关指定窗口的信息,函数也获得在额外窗口内存中指定偏移位地址的32位度整型值。
函数原型:LONG GetWindowLong(HWND hWnd,int nlndex);
参数:
hWnd:窗口句柄及间接给出的窗口所属的窗口类。
nlndex:指定要检索的基于0的的偏移量。有效值的范围从0到窗口额外内存空间的字节数,最小为4。例如,若指定了12位或多于12位的窗体类的额外存储空间,则应设为第三个32位整数的索引位8。要获得任意其他值,指定下列值之一:
GWL_EXSTYLE;获得扩展窗口风格。
GWL_STYLE:获得窗口风格。
GWL_WNDPROC:获得窗口过程的地址,或代表窗口过程的地址的句柄。必须使用GWL_WNDPROC函数调用窗口过程。
GWL_HINSTANCE:获得应用事例的句柄。
GWL_HWNDPAAENT:如果父窗口存在,获得父窗口句柄。
GWL_ID:获得窗口标识。
GWL_USERDATA:获得与窗口有关的32位值。每一个窗口均有一个由创建该窗口的应用程序使用的32位值。
在hWnd参数标识了一个对话框时也可用下列值:
DWL_DLGPROC:获得对话框过程的地址,或一个代表对话框过程的地址的句柄。必须使用函数CallWindowProc来调用对话框过程。
DWL_MSGRESULT:获得在对话框过程中一个消息处理的返回值。
DWL_USER:获得应用程序私有的额外信息,例如一个句柄或指针。
返回值:如果函数成功,返回值是所需的32位值;如果函数失败,返回值是0。若想获得更多错误信息请调用 GetLastError函数。
备注:通过使用函数RegisterClassEx将结构WNDCLASSEX中的cbWndExtra单元指定为一个非0值来保留额外类的存储空间。
CallWindowProc
函数功能:将指定消息信息传送给指定的窗口过程. lpPrevWndFunc是窗口消息处理函数指针(函数名), hWnd为接受窗体句柄.
函数原型:LRESULT CallWindowProc(WNDPROC lpPrevWndFunc,HWND hWnd.UINT Msg,WPARAM wParam,LPARAMIParam);
参数:
lpPrevWndFunc:参数主要是指向你要调用的函数句柄
hWnd:指向接收消息的窗口过程的句柄。
Msg:指定消息类型。
wParam:指定其余的、消息特定的信息。该参数的内容与Msg参数值有关。
IParam:指定其余的、消息特定的信息。该参数的内容与Msg参数值有关。
返回值:返回值指定了消息处理结果,它与发送的消息有关。
备注:使用函数CallWindowsProc可进行窗口子类化。子类是一个窗口或者相同类的一套窗口,在其消息被传送到该类的窗口过程之前,这些消息是由另一个窗口过程进行解释和处理的。
FindWindowEx
函数功能:该函数获得一个窗口的句柄,该窗口的类名和窗口名与给定的字符串相匹配。这个函数查找子窗口,从排在给定的子窗口后面的下一个子窗口开始。在查找时不区分大小写。
函数原型:HWND FindWindowEx(HWND hwndParent,HWND hwndChildAfter,LPCTSTR lpszClass,LPCTSTR lpszWindow);
参数:
hwndParent:要查找子窗口的父窗口句柄。
如果hwnjParent为NULL,则函数以桌面窗口为父窗口,查找桌面窗口的所有子窗口。
Windows NT5.0 and later:如果hwndParent是HWND_MESSAGE,函数仅查找所有消息窗口。
hwndChildAfter :子窗口句柄。查找从在Z序中的下一个子窗口开始。子窗口必须为hwndPareRt窗口的直接子窗口而非后代窗口。如果HwndChildAfter为 NULL,查找从hwndParent的第一个子窗口开始。如果hwndParent 和 hwndChildAfter同时为NULL,则函数查找所有的顶层窗口及消息窗口。
lpszClass:指向一个指定了类名的 空结束字符串,或一个标识类名字符串的成员的指针。如果该参数为一个成员,则它必须为前次调用theGlobaIAddAtom函数产生的全局成员。该成 员为16位,必须位于lpClassName的低16位,高位必须为0。
lpszWindow:指向一个指定了窗口名(窗口标题)的空结束字符串。如果该参数为 NULL,则为所有窗口全匹配。返回值:如果函数成功,返回值为具有指定类名和窗口名的窗口句柄。如果函数失败,返回值为NULL。
VC中超类化常用API
窗口超类化是在窗口类——WNDCLASS或WNDCLASSEX(非MFC类概念)级别进行的改变窗口类特征的。改变已有窗口类的行为属性。
GetClassInfoEx
函数功能: 获取进程中窗体类信息
函数原型:BOOL GetClassInfo(HINSTANCE hInstance, LPCTSTR lpClassName, LPWNDCLASS lpWndClass);
参数:
hInstance:应用程序实例;
lpClassName :窗口类名;
lpWndClass :用于接收类名的结构体变量 ;
返回值:如果函数成功,返回值为非零;
备注:在超类化中,取得想要进行超类化操作的窗口类的信息
RegisterClassEx
函数功能:注册一个窗体类,以便通用CreateWindowEx, CreateWindow来实例化一个窗体。
函数原型:RegisterClassEx(CONST WNDCLASSEX *lpwcx );
参数:
Ipwcx:指向一个WNDCLASSEX结构的指针。在传递给这个函数之前,必须在结构内填充适当的类的属性。
返回值:如果函数成功,返回这个窗口类型的标识号;如果函数失败,返回值为0。
UnRegisterClass
函数功能:该函数删除一个窗口类,清空该类所需的内存。
函数原型:BOOL UnRegisterClass(LPCTSTR IpClassName; HINSTANCE hlnstance);
参数:
IpClassName:指向一个空结束字符串的指针,或是一个整型原子。如果IpClassName是一个字符串,则它指定了窗口类的类名。这个类名必须由此前调用RegisterClassEx函数来注册。系统类,如对话框控制,必须被注册。
如果这个参数是一个整型原子,它必须是由此前调用GlobalAdd原子函数创建的全局原子。这个16位整型数小于OxCOOO,必须是lpszClass的低16位,其高位宇必须为0。
hlnstance:创建类的模块的事例句柄。
返回值:如果函数成功,返回值为非零;如果未发现类或由此类创建的窗口仍然存在,则返回值为0。
若想获得更多错误信息,请调用GetLastError函数。
备注:在调用这个函数之前,应用程序必须销毁由指定类创建的所有窗口。应用程序注册的所有窗口类在应用程序中止后都为未注册的类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
/* 附窗口样式参考列表:
WS_POPUP - 弹出式窗口(不能与WS_CHILDWINDOW样式同时使用)
WS_CHILDWINDOW - 子窗口(不能与WS_POPUP合用)
WS_MINIMIZE - 最小化状态
WS_VISIBLE - 可见状态
WS_DISABLED - 不可用状态
WS_CLIPSIBLINGS - 使窗口排除子窗口之间的相对区域
WS_CLIPCHILDREN - 当在父窗口内绘图时,排除子窗口区域
WS_MAXIMIZE - 具有最大化按钮,须指定WS_SYSTEM样式
WS_CAPTION - 有标题框和边框(和WS_TILED样式相同) 窗口带有一个标题栏,经测试,实际上等于 (WS_BORDER Or WS_DLGFRAME)
WS_BORDER - 有单边框 窗口带有一个薄边框
WS_DLGFRAME - 带对话框边框样式,不带标题框 带有一般对话框的风格,但没有标题栏
WS_VSCROLL - 有垂直滚动条 窗口带有一个垂直滚动条
WS_HSCROLL - 有水平滚动条 窗口带有一个水平滚动条
WS_SYSMENU - 标题框上带有窗口菜单(须指定WS_CAPTION样式) 在窗口的标题栏上增加一个系统菜单,该窗口必须具有 WS_CAPTION 风格(即WS_BORDER和WS_DLGFRAME)
WS_SIZEBOX 和 WS_THICKFRAME - 有可调边框(与WS_SIZEBOX样式相同) 窗口带有一个可以调整窗口大小的边框(即VB里的Sizable,其他地方的边框均指不具调整大小功能的边框)
WS_GROUP - 组样式,每个组的第一个控件具有WS_TABSTOP样式
WS_TABSTOP - 可接受TAB键焦点
WS_MINIMIZEBOX - 有最小化按钮 窗口带有最小化按钮,该窗口必须具有 WS_CAPTION 风格
WS_MAXIMIZEBOX - 有最大化按钮 窗口带有最大化按钮,该窗口必须具有 WS_CAPTION 风格
WS_OVERLAPPEDWINDOW 和 WS_TILEDWINDOW:- 具有层叠,标题框,系统菜单,可调边框,系统按钮 窗口是一个交迭式窗口,并且组合了 WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU,
WS_THICKFRAME, WS_MINIMIZEBOX 以及 WS_MAXIMIZEBOX 这些风格
WS_POPUPWINDOW - 具有单边框,弹出式,系统菜单样式
WS_OVERLAPPED 和 WS_TILED:窗口是一个交迭式窗口。交迭式窗口带有一个标题栏和一个边框
*/
/* 窗口扩展样式参考列表:
WS_EX_DLGMODALFRAME - 带双层边框
WS_EX_NOPARENTNOTIFY - 创建/销毁时不通知父窗口
WS_EX_TOPMOST - 窗口置顶(停留在所有非最高层窗口的上面)
WS_EX_ACCEPTFILES - 可接受文件拖放
WS_EX_TRANSPARENT - 透明样式,在同属窗口已重画时该窗口才可重画
WS_EX_MDICHILD - MDI子窗口样式
WS_EX_TOOLWINDOW - 工具条窗口样式
WS_EX_WINDOWEDGE - 带凸起边缘的边框
WS_EX_CLIENTEDGE - 带阴影的边缘
WS_EX_CONTEXTHELP - 有上下文帮助样式,标题栏包含一个问号标志
WS_EX_RIGHT - 右对齐
WS_EX_RTLREADING - 窗口文本从右到左显示
WS_EX_LEFTSCROLLBAR - 垂直滚动条在窗口左边界
WS_EX_CONTROLPARENT - 允许用户使用TAB键在窗口的子窗口间搜索
WS_EX_STATICEDGE - 当窗口为不可用状态时创建一个三维边缘
WS_EX_APPWINDOW - 当窗口可见时将一个顶层窗口放置在任务栏上
WS_EX_OVERLAPPEDWINDOW - 带凸起边缘的边框,边缘有阴影
WS_EX_PALETTEWINDOW - 带立体边框,有工具条窗口样式,窗口在顶层
WS_EX_LAYERED - 分层或透明窗口,该样式可使用混合特效
WS_EX_NOINHERITLAYOUT - 子控件不继承窗体或控件的布局
WS_EX_LAYOUTRTL - 窗体或控件将具有从右向左的布局(因而会被镜像)
WS_EX_COMPOSITED - 用双缓冲从下到上绘制窗口的所有子孙(WinXP以上)
WS_EX_NOACTIVATE - 处于顶层但不激活
*/
/* 控件样式参考列表:
DS_ABSALIGN - 对话框的坐标为屏幕坐标(缺省为客户区坐标)
DS_SYSMODAL - 系统模式(仅支持16位程序),不能与DS_CONTROL同用
DS_LOCALEDIT - 在对话框内部为编辑框分配内存(仅支持16位程序)
DS_SETFONT - 可定制对话框字体
DS_MODALFRAME - 框架样式(不能与WS_CAPTION同用)
DS_NOIDLEMSG - 无空闲消息
DS_SETFOREGROUND - 使对话框在最前面显示
DS_3DLOOK - 四周有3维边框
DS_FIXEDSYS - 使用系统固定字体
DS_NOFAILCREATE - 忽略创建过程中的错误
DS_CONTROL - 控件模式,可作为其他对话框的子窗口
DS_CENTER - 在屏幕居中
DS_CENTERMOUSE - 在鼠标位置居中
DS_CONTEXTHELP - 有上下文帮助按钮 */
|
*****************************************************************************************************************
摘 要:本文介绍了窗口子类化(SubClassing)的概念、技术原理、作用以及在Visual C++6.0中的实现方法,并给出了一个具体应用实例。实践证明,适当地使用窗口子类化技术,可以大大增强应用程序的功能。
关键字:子类化, 窗口函数, 消息, Visual C++
一、引言
在Windows编程中,如果我们想在窗口程序执行时改变它所包含的控件(对话框中的按钮、下拉式菜单等)的某些行为,采用窗口子类化技术是一个不错的选择。可以使用对已有控件派生子类的方式定义一个子类,而控件的消息处理则在新定义的子类里定义。适当使用子类化技术创建出容易使用的新窗口类,往往可以使你的程序界面更具人性化。
二、窗口子类化技术概述[1]
Windows的窗口类是一个窗口模板,包含一个窗口所具有的部分窗口属性。编写一个Windows程序时首先要做的工作就是注册一个窗口类,然后基于此注册的窗口类创建一个新的窗口。在WIN32平台中,注册窗口类的API函数是RegisterClass和RegisterClassEX,其中RegisterClassEX是推荐使用的函数,使用这个函数注册窗口类时,需要先填写一个WNDCLASSEX结构。这个结构实际上反映了一个窗口类的特征,一个窗口类有本类所有窗口公用的类属性、窗口函数、类图标和小图标、类鼠标、窗口背景刷、类菜单,当然还有类名。除此之外,每个类还有一定大小的类存储区,可以用来存储该类的公共数据。
每一个创建的窗口都有一个窗口函数,其地址由WNDCLASSEX结构的lpfnWndProc参数设定,该窗口函数处理对应于该窗口类的所有实例的消息。当创建一个窗口时,Windows将分配一个内存块,用来存放与该窗口相关的信息,并将参数lpfnWndProc从窗口类内存块拷贝到该内存块中。当消息被分发到窗口时,Windows检查该窗口中内存块中的lpfnWndProc值,并调用该内存块地址上的窗口函数。
一个窗口的行为主要取决于它的窗口函数,如果能够改变一个窗口的窗口函数,使它指向自己写的某个函数,那就意味着发给这个窗口的各种消息将由我们自己写的这个函数来处理。
子类化一个窗口,实际上就是改变窗口内存块中的窗口函数的地址,使其指向用户自定义的新的窗口函数入口,以实现用户希望的窗口特性。
三、窗口子类化的作用
窗口子类化技术最大的特点就是能够截取Windows的消息。一旦用户自定义的窗口函数截取了传向原窗口函数的消息,就可以对被截取的消息进行如下处理[2]:
n 将其传给原来的窗口函数。这是对大多数消息应该采取的措施,因为子类通常只对原来的窗口特性作少量的修改。
n 截取该消息,阻止其向原窗口函数发送。
n 修改该消息,修改完毕以后再向原窗口函数发送。
Windows SDK提供了一些设计好的窗口类,如EDIT、LISTBOX、TREEVIEW等。通过截取这些通用窗口类的消息,用户程序可以为它们添加新的特性,改善其外观,扩充其功能。
子类化的优点主要体现在以下两个方面:首先,它不需要创建新的窗口类,不需要了解一个窗口的窗口过程。这在原来的窗口函数是由别人编写,而且创建过程不可见的情况下非常有用;其次,子类化比较容易实现,因为所有要做的工作仅仅就是写一个窗口函数。
四、在VC中实现窗口子类化
上面介绍的子类化是从Windows本身的窗口函数概念来讲的,实际上属于SDK(Software Development Kit)编程的范畴,在MFC中情况有所不同。下面将分别描述在这两种情况下窗口子类化实现的方法。
4.1 VC中基于SDK编程的窗口子类化
VC中基于SDK编程的窗口子类化的基本步骤如下:
(1) 正常创建原始窗口,得到窗口的句柄。
(2) 调用GetWindowLong得到原来的窗口函数OldWndProc。
(3) 调用SetWindowLong设置新的窗口函数NewWndProc。
新的窗口函数的代码如下所示:
LRESULT NewWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
if(message==WM_IcareIt)
{
//截取自己感兴趣的消息,作一些处理,达到改变特性的目的
}
//必要时可以调用原来的窗口函数,使被子类化的窗口仍具有原来的很多特性
return CallWndowProc(OldWndProc,hWnd,message,wParam,lParam);
}
值得注意的是,在调用旧的窗口函数时,不能直接用OldWndProc(…),而必须用函数CallWndProc进行调用,否则会出现堆栈错误。
4.2 MFC编程中的窗口子类化
MFC窗口实际上已经是被子类化的窗口。所有的MFC窗口共享同一个窗口函数,由这个窗口函数根据窗口句柄,查找这个窗口对应的CWnd派生类实例,再通过消息映射这个窗口类的消息处理函数。鉴于以上原因,在MFC中要子类化一个窗口就比较容易了,因为你的任务只是编写一个新的MFC窗口类而不需要写一个窗口函数。
假如我们现在有一个对话框,里面有一个编辑控件,我们只希望在该控件中接受非数字字符输入,我们可以拦截WM_CHAR消息,在它的处理函数中忽略任何数字的输入。MFC编程中窗口子类化的具体实现步骤在下一节笔者将用一个简单的实例来加以说明。
五、VC中窗口子类化的应用举例
MFC为广大编程者提供了很多功能丰富的窗口类,如果能在这些通用窗口类的基础上进行子类化的话,将会给编程者带来很多便利。下面举一个例子来说明MFC编程中的子类化是多么的简单易行。该例完成上面提到的在编辑控件只接受非数字字符输入的功能。实现这个子类化的基本步骤和相关代码如下:
(1)利用AppWziard创建一个基于对话框的程序SubClassing。
(2)对MFC提供的标准的对话框中的控件进行修改,删除MFC提供的静态文本控件,添加自己的一个编辑控件,设置新控件的ID为IDC_EDIT。合理布置对话框上各控件的位置,使程序界面布局合理、美观。
(3)用ClassWizard从CEdit类派生一个新的窗口类,新窗口的窗口类叫CNoNumEdit。截取CNoNumEdit类的WM_CHAR消息,在OnChar函中完成忽略任何数字的输入的处理。实现代码如下:
void CNoNumEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
TCHAR ch=nChar;
if(ch>=_T('0')&&ch<=_T('9'))
{
AfxMessageBox(("请不要输入数字!"),MB_OK);
//当输入数字字符时将被忽略,并显示警告信息
return;
}
CEdit::OnChar(nChar, nRepCnt, nFlags);//输入为非数字字符时调用原处理函数
}
(4)在对话框窗口类CSubClassingDlg的定义中添加变量CNoNumEdit ed。在CSubClassingDlg::OnInitDialog()函数中调用CWnd类的成员函数SubClassWindow进行子类化。
ed.SubclassWindow(GetDlgItem(IDC_EDIT)->m_hWnd);
(5) 在对话框窗口类CsubClassing的OnDestroy中调用ed.UnSubClassWindow()执行窗口类的反子类化。
现在可以编译执行这个程序了,当用户输入数字字符时将会忽略该输入,并显示警告信息。
六、结束语
在Windows编程中,适当使用窗口子类化技术,可以很方便地达到改变一个窗口的特性的目的。当然子类化也存在其局限性。实际上,子类化的概念是针对一个已经创建的窗口来谈的,所以修改窗口函数是在窗口创建之后进行的,在窗口创建期间的消息无法捕获,也就无法处理。另外有些窗口的特性与窗口类本身的属性有关。比如如果一个窗口类没有CS_DBLCLKS属性的话,那么要想通过子类化这些窗口达到处理WM_LBUTTONDBLCLK消息的目的是无法实现的。对于子类化的以上局限性,可以通过超类化(SuperClassing)技术消除。
*****************************************************************
在自绘窗口的时候,子类化是MFC最常用的窗体技术之一。什么是子类化?窗口子类化就是创建一个新的窗口函数代替原来的窗口函数。
Subclass(子类化)是MFC中最常用的窗体技术之一。子类化完成两个工作:一是把窗体类对象attach到一个windows窗体实体中(即把一个窗体的hwnd赋给该类)。另外就是把该类对象的消息加入到消息路由中,使得该类可以捕获消息。
而通常我们会碰到DDX_Control、SubclassWindow、SubclassDlgItem等,不同的子类化方法。首先先看下面的代码:
- void AFXAPI DDX_Control(CDataExchange* pDX, int nIDC, CWnd& rControl)
- {
- if ((rControl.m_hWnd == NULL) && (rControl.GetControlUnknown() == NULL)) // not subclassed yet
- {
- ASSERT(!pDX->m_bSaveAndValidate);
- pDX->PrepareCtrl(nIDC);
- HWND hWndCtrl;
- pDX->m_pDlgWnd->GetDlgItem(nIDC, &hWndCtrl);
- if ((hWndCtrl != NULL) && !rControl.SubclassWindow(hWndCtrl))
- {
- ASSERT(FALSE); // possibly trying to subclass twice?
- AfxThrowNotSupportedException();
- }
- #ifndef _AFX_NO_OCC_SUPPORT
- else
- {
- if (hWndCtrl == NULL)
- {
- if (pDX->m_pDlgWnd->GetOleControlSite(nIDC) != NULL)
- {
- rControl.AttachControlSite(pDX->m_pDlgWnd, nIDC);
- }
- }
- else
- {
- // If the control has reparented itself (e.g., invisible control),
- // make sure that the CWnd gets properly wired to its control site.
- if (pDX->m_pDlgWnd->m_hWnd != ::GetParent(rControl.m_hWnd))
- rControl.AttachControlSite(pDX->m_pDlgWnd);
- }
- }
- #endif //!_AFX_NO_OCC_SUPPORT
- }
- }
我们发现 DDX_Control()函数中调用了SubclassWindow(),再看SubclassWindow()里写了什么:
- // From VS Install PathVC98MFCSRCWINCORE.CPP
- BOOL CWnd::SubclassWindow(HWND hWnd)
- {
- if (!Attach(hWnd))
- return FALSE;
- // allow any other subclassing to occur
- PreSubclassWindow();
- // now hook into the AFX WndProc
- WNDPROC* lplpfn = GetSuperWndProcAddr();
- WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC,
- (DWORD)AfxGetAfxWndProc());
- ASSERT(oldWndProc != (WNDPROC)AfxGetAfxWndProc());
- if (*lplpfn == NULL)
- *lplpfn = oldWndProc; // the first control of that type created
- #ifdef _DEBUG
- else if (*lplpfn != oldWndProc)
- {
- ::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)oldWndProc);
- }
- #endif
- return TRUE;
- }
而SubclassWindow又与SubclassDlgItem有什么区别?前者用于一切具有HWND的窗体,后者只限定于对话框控件
用法:在OnInitDialog中调用SubclassDlgItem将派生类的控件对象与对话框中的基类控件相连接,则这个基类控件对象变成了派生控件对象
总而言之,比较常用的就是DDX_Control。