什么是子类化?
窗口子类化的目的是在不修改现有代码的前提下,扩展现有窗口、控件的功能。它的思路很简单,就是将窗口过程地址修改为一个新函数地址,新的窗口过程函数处理自己感兴趣的消息,将不感兴趣的消息丢给原窗口过程处理。
MFC框架将子类化方法应用得淋漓尽致,MFC将所有的窗口处理函数都注册成DefWndProc,那
么,是不是MFC将所有的消息都发送到DefWndProc中去了呢?答案是“不是”,而是都发送到
AfxWndProc函数中去了(您可以回想一下前面我们查看MSDN是提到的AfxWndProc)
窗口子类化的步骤如下:
(1)正常创建系统控件/窗口,得到控件/窗口的句柄。
(2)调用GetWindowLong()得到原来的系统的窗口函数OldWndProc。
(3)调用SetWindowLong()设置控件新的窗口函数为我们的NewWndProc。
(4)在NewWndProc处理感兴趣的消息
(5)不感兴趣的消息调用CallWindowProc()传递给原来的OldWndProc处理
注意:在调用旧的窗口函数时,不能直接调用OldWndProc(..),
而必须用函数CallWndProc调用,否则会出现堆栈错误。
相关函数解析
Long GetWindowLong(
HWND hWnd, //目标窗口句柄
int nlndex // 要检索的值的基于零的偏移量
);
如果函数成功,返回值是所需的32位整型值
LONG SetWindowLong(
HWND hWnd, // 目标窗口句柄
int nlndex, // 要设置的值的基于零的偏移量
LONG dwNewLong // 新的值
);
如果函数成功,返回值是指定的32位整数的原来的值。如果函数失败,返回值为0。若想获得更多错误信息,请调用GetLastError函数。
nlndex可以为以下的值
GWL_EXSTYLE | 检索扩展窗口样式。 |
GWL_HINSTANCE | 检索应用程序实例的句柄。 |
GWL_HWNDPARENT | 检索父窗口的句柄(如果有的话)。 |
GWL_ID | 检索窗口的标识符。 |
GWL_STYLE | 检索窗口样式。 |
GWL_USERDATA | 检索与窗口关联的用户数据。 |
GWL_WNDPROC | 检索窗口过程的地址或表示窗口过程地址的句柄。您必须使用CallWindowProc函数调用窗口过程。 |
DWL_DLGPROC | 检索对话框过程的地址句柄,您必须使用CallWindowProc函数来调用对话框过程。 |
DWL_MSGRESULT | 检索在对话框过程中处理的消息的返回值。 |
DWL_USER | 检索应用程序专用的额外信息,例如句柄或指针。 |
//将消息信息传递给指定的窗口过程。
LRESULT CallWindowProc(
WNDPROC lpPrevWndFunc, //窗口过程
HWND hWnd, //窗口句柄
UINT Msg, //消息
WPARAM wParam, //额外的消息特定信息
LPARAM IParam //额外的消息特定信息
);
返回类型:LRESULT
返回值指定了消息处理的结果并取决于发送的消息。
备注
使用CallWindowProc函数进行窗口子类化,应用程序必须通过调用CallWindowProc将任何未由新窗口过程处理的消息传递给前一个窗口过程
实例代码
在主窗口过程中WM_CREATE消息中,创建控件时,将窗口过程改为我们自己的
case WM_CREATE: { LPCREATESTRUCT pcs = (LPCREATESTRUCT)LParam; HWND h3 = CreateWindow(L"edit", L"这是一个文本框", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_MULTILINE, 5, 100, 100, 80, hwnd, (HMENU)10003, pcs->hInstance, NULL); //将控件的窗口过程处理函数改为自定义的,从而捕获控件消息 //由于SetWindowLong会返回旧的过程,所以这里就不用GetWindowLong了 编辑框旧过程 = (WNDPROC)SetWindowLong(h3, GWL_WNDPROC, (LONG)编辑框的窗口过程); break; }
然后为编辑框写一个窗口过程
//创建一个全局变量 储存旧的过程 WNDPROC 编辑框旧过程 = NULL; //为编辑框写一个窗口过程 LRESULT CALLBACK 编辑框的窗口过程(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM LParam){ //消息所处的窗口句柄,具体消息名称 WM_xxxx消息 switch (uMsg) { //处理了我们感兴趣的消息 case WM_LBUTTONDOWN: { //MessageBox(hwnd, L"我们捕获了编辑框的左键消息", L"提示", MB_OK); SetWindowText(hwnd, L"我们捕获了编辑框的左键消息"); //return 0; 返回0代表我们自己处理了 系统默认的点击事件就没有了 所以无法置焦点 break; } } //其他的消息交给原来的处理过程函数去处理,保证控件原来的功能 return CallWindowProc(编辑框旧过程, hwnd, uMsg, wParam, LParam); }