超类化和子类化没有具体的代码,其实是一种编程技巧,在MFC和WTL中可以有不同的实现方法。

窗口子类化:
原理就是改变一个已创建窗口类的窗口过程函数。通过截获已创建窗口的消息,从而实现监视或修改已创建窗口类的行为属性。可以用来改变或者扩展一个已存在的窗口的行为,而不用重新开发。比如要获得那些预定义控件窗口类(按钮控件、编辑控件、列表控件、下 拉列表控件、静态控件和滚动条控件)的功能而又要修改它们的某些行为。

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

主要步骤为

  1. 截取该消息,阻止其向原窗口函数发送。
  2. 修改该消息。
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 结构的成员,其中有两个成员必须修改:

  1. hInstance 存放程序的实例句柄
  2. lpszClassName 指向一个新类名的指针
  3. 不必修改成员 lpfnWndProc,但大多数情况下还是需要的。但要记住如果要使用函数 CallWindowProc 调用老窗口的过程,那就必须保存成员 lpfnWndProc 的原值。
  4. 注册修改完的 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. 超类化可以修改包含窗体背景等属性,而子类化不能。

C++ 中超类化和子类化常用API:
在windows平台上,使用C++实现子类化和超类化常用的API并不多,由于这些API函数的详解和使用方法,网上一大把。本文仅作为笔记,简单的记录一下;

  1. 子类化:SetWindowLong,GetWindowLong,CallWindowProc,FindWindowEx
  2. 超类化:GetClassInfoEx,RegisterClassEx,UnRegisterClass

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
/* 附窗口样式参考列表: 
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 - 有上下文帮助按钮 */