尽管Windows系统提供了丰富的通用控件(如按钮,编辑框,滑动条等),但仍不可能满足我们实际应用中千差万别的需求,笔者在某项目的开发工作中就遇见了这样的问题。项目需要一个供用户输入表格数据的接口界面,要求只接收用户输入的数据信息,并可以利用键盘上的光标键移动输入位置以避免用户在键盘和鼠标之间的频繁切换。简单地使用Windows的编辑框控件不仅不能对输入字符进行有效过滤(如果给编辑框控件加上ES_NUMBER风格则只能接收0~9之间的数字而不能接收小数点正负号等需要的字符),而且无法移动控件。如果重起炉灶自己编程来实现,其工作量是相当可观的。为此,笔者经多次尝试,终于通过采用窗口子类化方法,很好地解决了上述问题。
窗口处理原理:
应用程序为了登记一个窗口类,首先要填写好一个WNDCLASS结构,其中的结构参数lpfnWndProc就是该类窗口的窗口过程函数(回调函数),接着调用RegisterClass()函数向Windows系统申请登记这个窗口类。这时Windows会为其分配一块内存来存放该类的全部信息,这个内存块称为窗口类内存块。
当应用程序要创建一个属于某一已登记窗口类的窗口时,Windows便为这个窗口分配一块内存,即窗口内存块,用来存放与该窗口有关的专用信息。这些信息一部分来自传递给窗口创建函数CreateWindow()或CreateWindowEx()的参数信息,另一部分则来自所属窗口类的窗口类内存块,其中参数lpfnWndProc便被Windows从窗口类内存块复制到为新创建窗口分配的窗口内存块中。当有消息被发送到这个窗口时,Windows检查该窗口内存块中的窗口过程函数(回调函数)(lpfnWndProc),并调用该地址上的函数来处理这些消息。
子类化原理:
所谓窗口子类化,实际上就是改变窗口内存块中的有关参数。由于这种修改只涉及到一个窗口的窗口内存块,因此它不会影响到属于同一窗口类的其它窗口的功能和表现。窗口子类化中最常见的是修改窗口内存块中的窗口过程函数(回调函数)(lpfnWndProc),使其指向一个新的窗口过程函数(回调函数),从而改变原窗口函数的处理方法,改进其功能。
子类化方法:
方法一:
(1)编写子类化窗口函数。该函数必须为标准的窗口函数格式即:
LRESULT CALLBACK SubClassWndProc ( HWND , UINT , WPARAM , LPARAM ) ;
在这个函数中对感兴趣的消息进行处理,而把未处理或者需要原窗口函数进一步处理的消息传送给原窗口函数;
(2)利用待子类化窗口的句柄hWnd,调用GetWindowLong ( hWnd , GWL_WNDPROC ) 函数获得原窗口函数的地址并保存起来;
(3)调用SetWIndowLong ( hWnd , GWL_WNDPROC , SubClassWndProc )(其返回值为原始的窗口过程函数,直接保存,可以替代第(2)步) 把窗口函数设置成子类化窗口函数,完成窗口子类化。
方法二:
子类化的方法还有另外一种:
使用 SubclassWindow()或是SubclassDlgItem(),方法很简单,直接MSDN。
方法三:
但是通常我们都不用以上的两种方法,如果是用MFC + vc的话,我们可以直接在ClassWizard里创建一个类,这个类的子类是MFC的控件类。然后对需要特殊照顾的控件(例如为按钮贴图, 改变标签控件的标签头,背景等)关联一个控件变量,变量的类型是我们所创建的类就可以了。这时候就不能再用Subclasswindow()了。
实例:
点击一个按钮,在按钮本身的消息(按钮的消息回调函数)中弹出对话框,而不是在COMMAND(父类的消息回调函数)消息中弹出对话框。
// Windows程序设计 Day003 上机题1.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "上机题1.h"
// 全局变量:
HINSTANCE hInst; // 当前实例
WNDPROC OriginalProc; // 用于保存原始的窗口过程函数(回调函数)
// 此代码模块中包含的函数的前向声明:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ButtonProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
{
MSG msg;
HWND hWnd = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)WndProc);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// 获取按钮句柄
HWND hButton = GetDlgItem(hWnd, IDC_BUTTON1);
// 保存原有回调函数,并修改成新的回调函数
OriginalProc = (WNDPROC)SetWindowLong(hButton, GWL_WNDPROC, (LONG)ButtonProc);
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
//自己添加的按钮回调函数
LRESULT CALLBACK ButtonProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONUP:
MessageBox(hWnd, _T("此窗口由Button的回调函数弹出!"), _T("提示"), MB_OK | MB_ICONASTERISK);
break;
}
//调用按钮原有的回调函数
return CallWindowProc(OriginalProc, hWnd, message, wParam, lParam);
}
//父窗口的窗口回调函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;
case WM_CLOSE:
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return FALSE;
}
return TRUE;
}