子类和超类是windows中两个很有用的概念,我们已经知道,所谓的windows控件是指,利用CreateWindow函数来调用系统预定义的窗口类,如“button”,“static”等等。我们知道WNDCLASS结构中有一个记录窗口过程函数地址的参数。可想而知,我们的控件所对应的窗口过程函数也是由系统帮我们完成的,所以我们才可以向控件发送一系列的控件消息。了解这些之后,我们来看看什么事子类和超类。
子类
首先,我们来考虑这么一种情况,假如我们需要一种窗口,这种窗口功能和windows中的某一个控件所提供的功能很像,只是有些细节地方不同,我们这时应该怎么去做呢?以编辑控件来说,假设我们需要实现二进制的编辑控件,但是一般的编辑框可以输入任何字符,即使我们指定ES_NUMBER风格,但也只能让编辑框接受输入数字为0-9,而不是0或1。这时,我们有两种方法,第一种是自己创建一个窗口类,然后在自己的窗口过程中完成我们所需要的所有功能,但这显然是我们不愿意看到的,因为这意味着什么我们都需要重新开始。我们几乎要重新写一遍Edit控件的所有功能,第二种方法就是使用我们下面所讲到的窗口子类化。
我们来想一下,假设我们能够获得“edit”窗口类的窗口过程函数的地址,并把这个地址指到一个我们定义的新的函数中去,然后在这个新的函数中选择的处理我们所要实现的功能,对于大量默认的功能仍然返回到原来的窗口过程函数中去,不就完成了我们所要求的功能了吗?的确是的,这也是窗口子类化的实现过程。对于上例,我们只需要在我们新的函数中重新处理一下父窗口发给编辑控件的WM_CHAR消息即可。那么如何去截获窗口的窗口过程呢?我们知道这个窗口过程地址位于WNDCLASS结构中的lpfnWndProc字段中,我们可以使用之前提到过的SetWindowLong函数来把自己定义的窗口过程来替换掉原来的地址并且返回原来的值,我们需要这个值,然后使用CallWindowProc函数来进行转到原来的窗口处理函数。
这个函数的用法如下所示:
SetWindowLong(hWnd, nIndex, dwNewLong);
其中hWnd是我们要子类化窗口的句柄,nIndex是指定需要修改的窗口的哪个属性,它可以有很多取值。不同取值指定了窗口的不同属性,比如GWL_EXSTYLE(窗口的扩展风格,这个是在WNDCLASSEX中定义)GWL_STYLE(窗口风格)GWL_WNDPROC(窗口过程地址)GWL_HINSTANCE(窗口所属模块实例句柄)等,dwNewLong即为要替换的新值。
说到这里顺便的提一下GetWindowLong函数,它的参数只有上述函数的前两个,用以获取当前窗口的属性。
简单来说,窗口子类化就是截获发送给原来窗口的消息,挑选感兴趣的消息进行处理,其余的仍然交回原来的窗口处理过程函数,有点类似过滤的功能。
对于上例,我们可以如下来实现。
#include <Windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
LRESULT CALLBACK ProcEdit (HWND, UINT, WPARAM, LPARAM);
WNDPROC lpfnOldWndProc = NULL;
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow )
{
static TCHAR szAppName[] = TEXT ("Demo") ;
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass;
wndclass.cbClsExtra = 0;
wndclass.cbSize = sizeof( WNDCLASSEX );
wndclass.cbWndExtra = 0;
wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wndclass.hCursor = LoadCursor( NULL, IDC_ARROW );
wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION );
wndclass.hIconSm = NULL;
wndclass.hInstance = hInstance;
wndclass.lpfnWndProc = WndProc;
wndclass.lpszClassName = szAppName;
wndclass.lpszMenuName = NULL;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
if ( !RegisterClassEx (&wndclass) )
{
MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow( szAppName, TEXT ("ChildCtrl"), WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
static HWND hEditWnd;
switch( msg )
{
case WM_CREATE:
hEditWnd = CreateWindowEx( WS_EX_CLIENTEDGE, TEXT("edit"), NULL, WS_CHILD | WS_VISIBLE | ES_LEFT | ES_NUMBER,
40, 30, 100, 25, hWnd, (HMENU)1, ( (LPCREATESTRUCT)lParam )->hInstance, NULL );
lpfnOldWndProc = (WNDPROC)SetWindowLong( hEditWnd, GWL_WNDPROC, (LONG)ProcEdit );
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
LRESULT CALLBACK ProcEdit (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if( msg == WM_CHAR )
{
if( wParam == TEXT('0') || wParam == TEXT('1') )
{
CallWindowProc( lpfnOldWndProc, hWnd, msg, wParam, lParam );
}
}
else
{
return CallWindowProc( lpfnOldWndProc, hWnd, msg, wParam, lParam );
}
}
超类
了解了子类,那么超类的概念就很好理解了,假设我们需要多个上述的编辑窗口,那么我们如果利用子类化来进行处理的话,那么我们需要对每一个窗口都进行子类化,这样的话重复的代码量将是庞大的,所谓超类,即使,我们利用已有的窗口类,来重新生成一个自己的窗口类,即我们不仅改变窗口类中的窗口处理函数的值,也改变窗口类名称。之后的编辑窗口都基于我们自己的窗口类来进行创建。要修改窗口类名,此时我们需要使用GetClassInfoEx函数来获得窗口类的信息,具体实现和上述代码大同小异,在这里不再实现,大家自己不妨试试。
(GetWindowLong函数不能获得窗口类的信息,正如名字所表现的那样,它只能获得窗口的属性)。