在这一讲,我们将学习什么是窗口子类化和怎样按你所想要的方式方便地使用它。
理论:
如果你曾经在 Windows 环境下编过程序,有时候就会发现:有一个现成的窗口,几乎有你所需要的全部功能,
但还不完全一样(否则就没有必要讲这一节了)。你曾遇到过这样的处境吗,如果你需要一个具有过滤特殊字符功能的
Edit 控件。
当然最直接的方法就是自己用代码来实现,但这的确是一个费时又很困难的任务,而窗口子类化就可以用来做
这种事情。窗口子类化允许你接管被子类化的窗口,使你对它有绝对的控制权。举个例子了来阐明一下:例如你需要一
个只接受十六进制数字输入的文本编辑框,如果使用一个简单的 Edit控件,当用户输入十六进制以外的字符时,你既不
知道也无计可施。也就是说,当用户进文本框中输入字符串 "zb+q*" 时,如果除了拒绝接受整个字符串以外几乎什么也
不能做,至少这显得特别不专业。重要的是,你需要具有输入检测的能力,即每当用户输入一个字符到编辑框中时要能检
测这个字符。
现在来解释实现细节:当用户往文本框中输入字符时,Windows 会给Edit控件的窗口函数发送 WM_CHAR 消息。
这个窗口函数本身寄生于 Windows 中,因此不能直接修改它。但是我们可以重定向这个消息使之发送到我们自己编写
的窗口处理函数。如果自定义窗口要处理这个消息那就可以处理它,如果不处理就可以把这个消息转发到它原来窗口处
理函数。通过这种方式,自定义的窗口处理函数就把它自己插入到 Windows 系统和 Edit 控件之间。
看下面的流程:
窗口子类化之前
Windows ==>Edit 控件的窗口处理函数。
子类化之后
Windows ==>自定义的窗口处理函数==> Edit 控件的窗口处理函数。
注意子类化并不局限于控件,可以子类化任何窗口,现在我们要把精力集中到怎样实现子类化一个窗口上。让我
们想想Windows怎样知道 Edit 控件的窗口处理函数放在什么地方。
猜的?…肯定不是。原来 WNDCLASSEX 结构的成员 lpfnWndProc 指出了窗口函数地址。如果能用自己编写的窗口
函数的地址来替换这个成员变量,那 Windows 不就把消息发到自定义的窗口函数了吗! 我们通过调用函数SetWindowLong
来实现这个任务,此函数的原型为:
LONG SetWindowLong( HWND hWnd, int nIndex, LONG dwNewLong );
hWnd = 将要实施子类化的窗口的句柄
nIndex = 函数了功能索引
GWL_EXSTYLE 设置窗口的扩展风格.
GWL_STYLE 设置新的窗口风格
GWL_WNDPROC 设置新的窗口处理函数地址
GWL_HINSTANCE 设置新的应用程序句柄
GWL_ID 设置新的窗口标识
GWL_USERDATA 设置一个与这个窗口相关的给用户使用的32位的数据
dwNewLong = 用来更新的数据
我们的工作还是比较简单的:
写一个窗口函数用于处理发给Edit控件的消息。 用参数GWL_WNDPROC调用SetWindowLong 函数,如果调用成功那么
返回值就是与调用功能相联系的一个32位的整数
在我们的程序中,返回值就是原先窗口函数的地址。我们要保存这个值以便以后使用。 记住:有一些我们不处理的
消息,需要把它们派遣给原来的窗口函数来处理,这就用到另外一个函数 CallWindowProc,函数原型为:
LRESULT CallWindowProc(
WNDPROC lpPrevWndFunc, // pointer to previous procedure
HWND hWnd, // handle to window
UINT Msg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
lpPrevWndFunc = 窗口原来函数的地址. 剩下的四个参数就是发给自定义函数的参数,直接把它们传给函数 CallWindowProc 就行了。
例子:见光盘FirstWindow18
#include "Windows.h"
#include "tchar.h"
#include "commctrl.h"
#pragma comment(lib,"comctl32.lib")
TCHAR ClassName[] = _T("SubclassWinClass");
TCHAR AppName[] = _T("Subclassing Demo");
TCHAR EditClass[] = _T("EDIT");
TCHAR Message[] = _T("You pressed the Enter key in the text box!");
HINSTANCE g_hInstance;
HWND hwndEdit;
WNDPROC OldWndProc;
INT_PTR CALLBACK EditWndProc(HWND hWnd, UINT Msg, PARAM wParam, LPARAM lParam )
{
switch(Msg)
{
case WM_CHAR:
{
BYTE bTmp = LOBYTE(LOWORD(wParam));
if((bTmp >= '0' && bTmp <= '9') || (bTmp >= 'A' && bTmp <= 'F') || (bTmp >= 'a' && bTmp <= 'f') || bTmp == VK_BACK)
{
if(bTmp >= 'a' && bTmp <= 'f')
{
bTmp -= 0x20; //小写转换成大写
}
DWORD newWparam = wParam;
newWparam = newWparam >> 8;
newWparam = newWparam << 8;
newWparam += bTmp;
return CallWindowProc(OldWndProc,hWnd,Msg,newWparam,lParam);
}
}
break;
case WM_KEYDOWN:
if(LOWORD(wParam) == VK_RETURN)
{
MessageBox(hWnd,Message,AppName,MB_OK|MB_ICONINFORMATION);
SetFocus(hWnd);
}
break;
default:
return CallWindowProc(OldWndProc,hWnd,Msg,wParam,lParam);
}
return 0;
}
INT_PTR CALLBACK ProcWinMain(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam )
{
switch(Msg)
{
case WM_CREATE:
hwndEdit = CreateWindowEx(WS_EX_CLIENTEDGE ,EditClass, NULL, WS_CHILD | WS_VISIBLE| WS_BORDER,
20, 20, 300, 25, hWnd, NULL, g_hInstance, NULL);
SetFocus(hwndEdit);
OldWndProc = (WNDPROC)SetWindowLong(hwndEdit,GWL_WNDPROC,(LONG)EditWndProc);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd,Msg,wParam,lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
MSG msg;
HWND hWnd;
g_hInstance = hInstance;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = ProcWinMain;
wc.cbClsExtra = NULL;
wc.cbWndExtra = NULL;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE);
wc.lpszMenuName = NULL;
wc.lpszClassName = ClassName;
wc.hIcon = wc.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL,IDC_ARROW);
RegisterClassEx(&wc);
hWnd = CreateWindowEx(WS_EX_CLIENTEDGE, ClassName, AppName, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,350,200,NULL,NULL,hInstance,NULL);
ShowWindow(hWnd,SW_SHOWNORMAL);
UpdateWindow(hWnd);
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
InitCommonControls();
}
分析:
OldWndProc = (WNDPROC)SetWindowLong(hwndEdit,GWL_WNDPROC,(LONG)EditWndProc);
在创建 Edit 控件后,通过调用 SetWindowLong 把原来的窗口函数地址替换为自定义函数的地址,
从而对它实施了窗口子类化,要注意 为了调用函数 CallWindowProc,我们存储了原窗口函数地址,自已编
写的EditWndProc 仅仅是个普普通通的窗口函数。当然也可以再调用一次 SetWindowLong 函数来存储这个32位的值,
SetWindowLong(hwndEdit,GWL_USERDATA,OldWndProc);
当然用的时候就要调用GetWindowLong 来取回这个值。
case WM_CHAR:
{
BYTE bTmp = LOBYTE(LOWORD(wParam));
if((bTmp >= '0' && bTmp <= '9') ||
(bTmp >= 'A' && bTmp <= 'F') ||
(bTmp >= 'a' && bTmp <= 'f') ||
bTmp == VK_BACK)
{
if(bTmp >= 'a' && bTmp <= 'f')
{
bTmp -= 0x20; //小写转换成大写
}
DWORD newWparam = wParam;
newWparam = newWparam >> 8;
newWparam = newWparam << 8;
newWparam += bTmp;
return CallWindowProc(OldWndProc,hWnd,Msg,newWparam,lParam);
}
} break;
在函数 EditWndProc 中,我们自己处理了WM_CHAR消息:
如果输入的字符是'0'--'9'、'A'-'F'或者是'a'--'f'就接受,并且把此消息转发给原窗口函数,
其中若输入的是小写的'a'--'f'就把它变为大写。如果输入的不是十六进制字符,就丢掉它,并且也不转发此消息。因此当输入是非十六进制字符时,这个字符就不会显示在 Edit 控件中。
case WM_KEYDOWN:
if(LOWORD(wParam) == VK_RETURN)
{
MessageBox(hWnd, Message, AppName, MB_OK|MB_ICONINFORMATION);
SetFocus(hWnd);
}
break;
在这里我们通过处理 回车(Enter) 键进一步示范了子类化的能力。
EditWndProc通过检查 WM_KEYDONW 消息来判断是否是 回车键,若是显示提示消息框,否则转发此消息。
你可以用窗口子类化来控制另外的窗口,这是必须掌握的十分有用的技术之一。