QQ截图20200908082019.bmp

什么是子类化?

窗口子类化的目的是在不修改现有代码的前提下,扩展现有窗口、控件的功能。它的思路很简单,就是将窗口过程地址修改为一个新函数地址,新的窗口过程函数处理自己感兴趣的消息,将不感兴趣的消息丢给原窗口过程处理。

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);
}