windows程序设计之窗口子类化

这几天都在家里,赶上了暑假,没啥事情可做,就做些小玩意。虽然都没什么技术含量,但自己毕竟是新手,做得不是很好,代码都得参考下别人的。委屈今天看到一个博客上面写的是关于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 所以又弹了一次框。


上面就是一个小程序,毕竟是新手,可能有错,希望指正,不甚感激!

 


参考资料《windows+sdk编程系列文章》

标签:SDK