这几天都在家里,赶上了暑假,没啥事情可做,就做些小玩意。虽然都没什么技术含量,但自己毕竟是新手,做得不是很好,代码都得参考下别人的。今天看到一个博客上面写的是关于SDK的窗口子类化,什么是窗口子类化,其实说得简单点就是有个多功能的窗口,你对它有绝对的控制权
。举个例子好了,你自己写了一个软件,上面有个编辑框,在这里面只能输入“我是笨蛋”,(基本上没人会喜欢你的软件)客户要是想输入其他的,都输不进去。这就有一种思想,软件是我做的,我对它有绝对的控制权。
下面简单描述下原理:就拿上面那个例子来说好了,我前面的博客大概介绍了下windows是怎么处理键盘消息的。就拿‘A’键来说,你要是不处理,windows就会自己拜托DefWindowProc 自己去处理。就算你要处理了,那好,在WM_CHAR或者WM_KEYDOWN你自己处理吧,但其实这不是享有绝对的控制权。
真正要有绝对的控制权是我需要具有输入检测的能力,即每当用户输入一个字符到编辑框中时要能检测这个字符。那要怎么办呢?可以这样,把Windows的窗口过程处理函数“偷换”成自己的函数,这样你就能把所有的消息随你高兴怎么处理了,这才是真正有有绝对控制权,也就是所谓的窗口子类化。由于Windows是认定窗口过程函数的格式的,所以你自己定义的函数也要和Windows本事的窗口过程函数格式一样。这样一说其实也就很简单了。
窗口子类化之前
Windows< ==>Edit 控件的窗口处理函数。
子类化之后
Windows< ==>自定义的窗口处理函数==> Edit 控件的窗口处理函数。
注意一点子类化并不局限于控件,可以子类化任何窗口
还是先看代码吧:(代码主要实现功能:在编辑框中只能输入0或者1,可用于纯二进制输入)
#include <windows.h> #define ID_EDIT 1 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; LRESULT CALLBACK EditWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) ; WNDPROC OldWndProc; static HWND hwndEdit; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("窗口子类化") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, // window class name TEXT ("The Hello Program"), // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position CW_USEDEFAULT, // initial x size CW_USEDEFAULT, // initial y size NULL, // parent window handle NULL, // window menu handle hInstance, // program instance handle NULL) ; // creation parameters ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE : hwndEdit = CreateWindow (TEXT ("Edit"), NULL, WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL, 20, 20, 300, 25, hwnd, (HMENU) ID_EDIT, ((LPCREATESTRUCT) lParam) -> hInstance, NULL) ; OldWndProc = (WNDPROC)SetWindowLong(hwndEdit,GWL_WNDPROC,(LONG)EditWndProc) ; return 0 ; case WM_SIZE : MoveWindow (hwndEdit, 0, 0, LOWORD (lParam), HIWORD (lParam), TRUE) ; return 0 ; case WM_SETFOCUS: SetFocus(hwndEdit) ; return 0 ; case WM_DESTROY: PostQuitMessage(0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } LRESULT CALLBACK EditWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static char KeyMsg ; switch(message) { case WM_CHAR : KeyMsg = LOWORD(wParam); if(KeyMsg == '0' || KeyMsg == '1' || KeyMsg == VK_RETURN) //只能输入1或者0,这里若是不处理Enter, //则还给windows自己处理,后下面的WM_KEYDOWN又要处理,会导致两次弹框。 { return CallWindowProc(OldWndProc,hwnd,message,KeyMsg,lParam) ; } case WM_KEYDOWN : if(LOWORD(wParam) == VK_RETURN) //如果键盘输入Enter { MessageBox(NULL,"Pressed Enter in New","Edit",MB_OK|MB_ICONINFORMATION) ; SetFocus(hwndEdit); } return 0 ; case WM_DESTROY: PostQuitMessage(0) ; return 0 ; } return CallWindowProc(OldWndProc,hwnd,message,wParam,lParam) ; }在WNDCLASSEX 结构的成员 lpfnWndProc 指出了窗口函数地址。在WM_CREATE的时候创建一个编辑框。接着是
OldWndProc = (WNDPROC)SetWindowLong(hwndEdit,GWL_WNDPROC,(LONG)EditWndProc) ;
通过SetWindowLong来改变窗口的属性,第二个参赛GWL_WNDPROC 设置新的窗口处理函数地址。
1. 用参数GWL_WNDPROC调用SetWindowLong函数,如果调用成功那么返回值就是与调用功能相联系的一个32位的整数。在这里我们返回的值保存在 OldWndPro中,下面的将不感兴趣的消息返回给windows还需要这个参数。下面是SetWindowLong函数MSDN上的解释。
----------------------------------------------------------------------------
Note: This function has been superseded by the SetWindowLongPtr function. To write code that is compatible with both 32-bit and 64-bit versions of Windows, use the SetWindowLongPtr function
if you use SetWindowLong with the GWL_WNDPROC index to replace the window procedure, the window procedure must conform to the guidelines specified in the description of the WindowProc callback function
----------------------------------------------------------------------------
接着我们看看EditWndProc函数,这个是我们自己定义的函数,但是样子要和windows自己的窗口过程函数一样,不然可就识别不出的。看看这个函数,其实也和windows自己的窗口过程函数差不多,也是消息的处理,等价的,我们处理感兴趣的消息,对于不需要处理的消息我们就交还给windows自己的函数处理,我们要截获我们需要的即可,WM_CHAR中我们只处理‘0’,‘1’或者Enter然后调用
LRESULT CallWindowProc(WNDPROC lpPrevWndFunc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );
lpPrevWndFunc = 窗口原来函数的地址,剩下的四个参数就是发给自定义函数的参数。
我们可以通过第四个参数创传回去。把不感兴趣的字符消息丢了(也就输不到编辑框上),感兴趣的字符消息传回windows自己函数,就这样神不知鬼不觉的“偷换”了函数。
看下我的代码注释,
KeyMsg == VK_RETURN
如果没有这个的话就会出现弹两次框的结果,这个可不是我们想要的,为什么会出现两次呢??我调试了下,第一次是直接在消息
WM_KEYDOWN中
if(LOWORD(wParam) == VK_RETURN)一次弹框,然后程序往下走竟然到了WM_CHAR里面,Enter是字符消息,,但是我们如果没有
KeyMsg == VK_RETURN的话直接会被传回windows自己的消息过程函数。Enter在消息列队里总得处理吧?对应的消息处理存在啊
case WM_KEYDOWN 所以又弹了一次框。
上面就是一个小程序,毕竟是新手,可能有错,希望指正,不甚感激!