窗口类
每个窗口都是一个窗口类的成员,窗口类是一个属性集.Microsoft Windows系统在创建应用程序的窗口时用它作为一个模板。每个窗门类有一个与之相应的窗口过程,由同类窗口所共享,窗口过程为该类的所有窗口处理消息,从而控制它们的特性和外观。有关窗口过程.参见 “窗口过程”。
应用程序必须在它创建某类窗口之前注册这个窗口类,注册一个窗口类也就是把一个窗口过程、类风格及其它一些类属性与类名联系起来。如果应用程序在函数CreateWindow或 CreateWindowEx中指定一个类名.操作系统就创建一个具有与这个类名相应的窗口过程、风格及其它属性的窗口。
窗口类的种类
有三种类型的窗口类:系统全局类、应用程序全局类及应用程序局部类。这些类型的区别在于作用域以及注册和销毁的时机。
系统全局类
在windows系统启动时,它就注册一些系统全局类用于控制框.包括:按钮、组合框、列表框、滚动条、编辑控制框和静态控制框。有关控制框,参见 “控制”和 “静态控制框”。任何应用程序都可以随时使用系统全局类,因为windows系统注册的系统全局类就是用于所有应用程序的,应用程序不能销毁这些类。
应用程序全局类
应用程序全局类是一个通过动态链接库(DLL)注册的窗口类,也适用于系统中的所有应用程序。例如,dll调用RegisterClassEx把一个定义定制控制框的窗口类注册成一个应用程序全局类.那么所有的应用程序就可以创建这个定制控制框的实例。
在windows系统中,所有的窗口类都是特定于某一进程的,应用程序通过在一个DLL中创建一个窗口类来创建一个全局类,并要通过下列关键字在注册数据库中列出这个DLL的名字。
只要进程一启动,系统就在调用这个进程的函数main之前,安装最新启动的进程描述表中指定的DLL,这个DLL必须在初始化它的过程时注册这个类,还必须设置CS_GLOBALCLASS风格。
有关类风格,参见 “类风格”。在一个类被注册之后,应用程序就可以用它来创建属于该类任意数目的窗口。
windows系统销毁一个应用程序全局类,在用于注册它的DLL被卸下时,基于这个原因,所有的应用程序必须在这个DLL被卸下之前销毁所有属于应用程序全局类的窗口,函数UnregisterClass用于删除应用程序全局类,并释放与之有关的内存空间。
应用程序局部类
应用程序局部类是由应用程序注册的并由它自己专用的窗口类,尽管应用程序可以注册任意数目的局部类,但绝大多数应用程序只注册一个,这个窗口类支持应用程序主窗口的窗口过程。
注册应用程序局部类与注册一个应用程序全局类差不多,只是WNDCLASSEX结构的style成员没有设置成CS_GLOBALCLASS风格。
windows系统销毁一个局部类是在注册它的应用程序关闭时,应用程序也可用函数UnregisterClass来删除一个局部类并释放与之相关的内存空间。
Windows系统如何确定类
windows系统为三种窗口类各管理一个WNDCLASS结构列表,如果应用程序调用函数CreateWindow或 CreateWindowEx来创建一个指定类的窗口,windows系统用下列步骤确定一个类:
1.windows系统按指定类名搜索应用程序局部类列表。
2.如果名字不在应用程序局部类的列表中,windows系统再搜索应用程序全局类列
3.如果名字不在应用程序全局类的列表中,windows系统就再搜索系统全局类列
所有应用程序创建的窗口都是用的上面这个步骤,也包括windows系统代表应用程序创建的窗口,如对话框。屏蔽系统全局类又不影响其它的应用程序是可以的,这就是说,应用程序可以注册一个与一个系统全局类具有相同名字的应用程序局部类,这就替代了应用程序描述表中的系统全局类,但又不影响其它应用程序对这个系统全局类的使用。
类的所属关联
类的属主是注册类的应用程序或DLL,windows系统是根据在注册时传给函数RegisterClassEx的WNDCLASSEX结构的hInstance成员来确定类的所属关系。对windows系统的DLLs,hInstance成员必须是DLL的实例句柄。类在其属主被关闭或被卸下时被销毁,基于这个原因,应用程序必须在它关闭或DLL被卸下之前销毁所有由这个类所创建的窗口
窗口类的元素
窗口类的元素定义了属于这类窗口的默认的特性。注册窗口类的应用程序是通过设置WNDCLASSEX结构中相应的成员来给类赋元素,并把这个结构传给函数RegisterClassEx。函数GetClassInfoEx和GetClassLong用来检取有关给定窗口类的信息。函数SetClassLong用来改变由应用程序已经注册的局部或全局类的元素。
尽管一个完整的窗口类含有许多元素.但windows系统只要求应用程序提供一个类名、窗口过程的地址和实例句柄,其它的元素则用来定义这类窗口默认的属性.如光标的形状,窗口菜单的内容。窗口类的元素列表如下:
元素 用途
类名 区别于其他注册类
窗口过程地址 处理所有发生这类窗口的消息并定义窗口特性的函数的指针.
实例句柄 标志注册类的应用程序或DLL.
类光标 定义这类窗口中的光标的形状.
类图标 定义大图标.
类小图标 定义小图标 (Windows version 4.0 and higher).
类背景刷 定义窗口打开或绘制时的填充色和图案.
类菜单 为没有显示式的定义菜单的窗口指定默认的菜单
类风格 定义在一个窗口被移动或被改变大小以后如何更改它。如何处理鼠标的双击操作,如何为设备描述表及窗口的其
他方面分配空间.
类附加空间 指定windows系统为这个类保留附加空间的字节数,附加空间为所有这类窗口所共享,他也能用于任何应用程
序定义的用途。Windows系统把这部分内存定义为0.
窗口附加空间 指定windows系统为这个窗口类的每个窗口保留附加空间的字节数,附加空间用于任何应用程序定义的用途。
Windows系统把这部分内存定义为0.
类名
每一个窗口类都需要有一个类名来区别于其它的类。把WNDCLASSEX结构的lpszClassName成员设成一个以NULL作为结束的zi 字串的地址来赋予—个类名,这个字符串就是指定的名字。因为windows系统中的窗口类都是特定于进程的,对同一个进程,窗口类名应是唯—的。
函数GetClassName用来检取一个指定窗口所属类的名字。
窗口过程地址
每个类需要一个窗口过程地址来定义窗口过程的入口点,窗口过程用来处理这类窗口的所有消息。windows系统在它要求窗口完成某个任务时向窗口传递消息,如绘制它的客户区或是响应用户的输入。应用程序把窗口过程的地址复制到WNDCLASSEX结构的lpfnWndProc成员中为一个类提供窗口过程,必须在模块定义文件(.DEF)中输出窗口过程,参见 ‘窗口过程”.
实例句柄
每个窗口类都要求有一个实例句柄来标识注册窗口类的应用程序或DLL,就象个多任务系统,windows系统同时运行几个应用程序或DLL,所以就要求有实例句柄来跟踪它们。windows系统为运行应用程序或DLL的每个拷贝赋一个句柄。
同一个应用程序或DLL的多个实例共用一个代码段,但它们有各自的数据段,windows系统用一个实例句柄来标识应用程序或DLL的某一个实例相应的数据段。
windows系统在应用程序启动的时候把实例句柄传给应用程序或DLL,应用程序或DLL再通过把这个实例句柄复制到WNDCLASSEX结构的hInstance成员中把它赋给类。
类光标
类光标定义这类窗口客户区中光标的形状,在光标进入窗口的客户区时,windows系统自动把光标设置成给定的形状.并保持这个形状直到光标退出客户区。要给某个窗口类赋一个类光标,可通过函数LoadCursor安装一个预定义的光标形状,再把返回的光标句柄赋给WNDCLASSEX结构的hCursor成员。或者是提供一个定制光标资源,再用函数LoadCursor安装应用程序资源中的这个光标。
windows系统并不要求有一个类光标,如果应用程序把WNDCLASSEX结构的hCursor成员置成NULL,那么就没有定义类光标。windows系统假定每次光标移进窗口,窗口都要设置光标的形状,窗口只要一接收到WM_MOUSEMOVE消息就会调用函数SetCursor设置光标的形状。有关光标,参见 “光标”。
类图符
类图符是系统用来再现一个窗口特别类的图,程序可以有两个类图符,一个大的一个小的,当用户按alt+tab系统在切换窗口中系统显示窗口的大图符,在任务栏和资源管理器里可以显示大图标,小图标出现在窗口的标题栏和在任务栏和资源管理器里可以显示小图标.
为分配大图标和小图标给窗口类,在WNDCLASSEX结构中的hIcon 和 hIconSm成员中指定图标句柄,图标尺寸必须遵从大小图标类的尺寸要求,你可以通过在调用函数GetSystemMetrics指定SM_CXICON和 SM_CYICON的值来决定尺寸要求.,对小图标指定SM_CXSMICON 和SM_CYSMICON的值,更多信息参考,”图标”
类图符定义了给定类的窗口在最小化时图符的形状,要把一个图符赋给一个窗口类.是通过函数1朋dI咖安装应用程序资源中的图符.再把返回的句柄赋给WNDCLASSEX结构的hIconSm成员。有关图符,参见第23章“图符”。
windows系统并不要求窗口类有一个类图符,如果应用程序把WNDCLASSEX结构的hIcon和hIconSm成员置成NULL那么就没有定义类图符,在这种情况下,系统就用缺省的程序图标作为大小图标类给窗口类.如果你指定大的没有指定小的,系统就基于大的创建小的,如果指定小的没有指定大的,系统就使用缺省程序图标作为大的图标类,那指定的图标作为小的图标类.
你可以通过消息WM_SETICON为特殊窗口重载大小图标类,你可以通过消息WM_GETICON来查询当前大小图标类.
windows系统在必须绘制图符的背景时向这个类的窗口发送WM_ICONERASEBKGND消息,如果窗口不处理WM_ICONERASEBKGND消息,windows系统就在窗口最小化时,把窗口存户区的内容画进图符。
背景刷类
类背景刷为应用程序曲绘制准备客户区,windows系统用这个画刷把客户区画成同一的颜色或图案,所以也就删除了这个位置上的所有图案,而不管它是否属于该窗口。windows系统通过向这个窗口发送WM_ERASEBKGND消息来通知它要绘制背景。有关画刷,参见 “画刷”。
要把一个背景刷赋给一个类,先要用图形设备接口(GDI)中适当的函数创建一个画刷,再把返回的画刷句柄赋给WNDCLASSEX结构的hbrBackground成员。
应用程序也可以不创建一个画刷,而是把hbrBackground成员设置成一个标准的系统颜色值,SetSysColors.中,有一个标准系统颜色值的列表。
要使用标准的系统颜色,应用程序必须把背景色的值加1,例如,COLOR_BACKGROUND + 1是系统背景颜色。
windows系统并不要求窗口类有一个类背景刷,如果这个参数设置成NULL,窗口就必须在接收到WM_ERASEBKGND消息时绘制它的背景。
类菜单
如果在创建窗口时没有显式地给出菜单,类菜单就为这个类中的窗口定义一个默认的菜单。菜单是一个命令列表,用户可通过它选择某个活动让应用程序来完成。
要给一个类赋一个菜单,需要把WNDCLASSEX结构的lpszMenuName成员设置成—个以NULL结束的字符串的地址,这个字符串指定菜单的资源名字,这个菜单被假设成是应用程序中的一个资源.在需要的时候,windows系统能够自动安装这个菜单。注意,如果菜单是由一个整数而不是名字来标识的,那么应用程序需要在赋值之前,通过宏MAKEINTRESOURCE把lpszMenuName成员设置成为该整数。
windows系统并不要求一个类菜单,如果应用程序把WNDCLASSEX结构的lpszMenuName成员设置成NULL,windows系统就假定这个类中的窗口没有菜单栏。即使没有给出类菜单,应用程序还是能够在创建窗口时为窗口定义一个菜单栏。
windows系统不允许子窗口有菜单栏,如果给窗口类赋予了一个菜单,那么创建这种类的子窗口时,菜单被忽略。有关菜单,参见 “菜单”。
类风格
类风格为窗口类定义另外的元素,两个或更多的风格可通过按位或OR(|)操作组合起来使用。把一个风格给一个窗口类,也就是把这个风格赋给WNDCLASSEX结构的style成员。类风格列表如下:
风 格
活 动
CS_BYTEALIGNCLIENT
CS_BYTEALIGNWINDOWCS_CLASSDC
CS_DBLCLKS
CS_GLOBALCLASS
CS_HREDRAW
CS_NOCLOSE
CS_OWNDC
CS_PARENTDC
CS_SAVEBITS
CS_VREDRAW
把窗口的客户区按位(x方向)对齐以增强绘画操作的性能,这个风格影响窗口的宽度及水平位置。
把窗口按位(x方向)对齐以增强移动或改变窗口大小操作的性能,这个风格影响窗口的宽度及水平位置。
分配一个由某宙u类中的所有窗口共享的设备描述表。参见 “类和私有设备描述表”及 “设备描述表。
使得windows系统在用户双击某窗口类的窗口中的鼠标时,向窗口过程发送一个双击消息。见 “鼠标输入”。
指定窗口类为应用程序全局类。有关信息,参见 “应用程序全局类”。
指定在移动或调整大小改变了客户区的宽度时整个窗口都要重画。
禁止系统菜单中的close命令。
为某窗口类的每一个窗口分配一个独立的设备描述表。参见设备描述表,类和私有设备描述表描述表。
把父窗口的设备描述表给其子窗口。
把一个窗口所遮掩住的屏幕映象部分用位图保留下来,windows系统用保留住的位图在窗口移走后重建这部分屏幕映象,windows系统把这个位图显示在原来的位置,但如果没有其它的屏幕活动使所保留的位图失效,就不向由这个由口遮掩住的窗口发送WM_PAINT消息。这个风格用于短暂显示的小窗口,在发生其它屏幕活动时被删除掉(如,菜单或对话框)。这种风格增加了显示一个窗口的时间.因为操作系统首先必须为保留的位图分配内存。
指定在移动或调整大小改变了客户区的高度时整个窗口都要重画。
类附加内存空间
windows为系统中的每一个窗口类管理一个WNDCLASSEX结构,在应用程序注册一个窗口类时.它可以让windows系统为WNDCLASSEX结构分配和追加一定字节数的附加内存空间,这部分内存空间称之为类附加内存,由属于这种窗口类的所有窗口所共享,类附加内存空间用于存储类的附加信息。
因为附加的内存是从系统局部堆里分配的,所以程序要节约使用附加内存。如果类附加内存字节超过40字节,那么函数RegisterClassEx调用失败。如果要这么做,就要分配自己的内存并把指向类附加内存的内存指针保存起来。
函数SetClassWord和SetClassLong用来把某—个值复制到类附加内存,要检取类附内存中的值,则用函数GetClassWord和GetClassLong。WNDCLASSEX路结构的cbClsExtra成员指定了所分配的类附加内存的总数,如果应用程序不需要类附加内存.则必须把cbClsExtra贝成员初始化为0。
窗口的附加空间.
windows系统为每一个窗口管理一个内部数据结构,在注册一个窗口类的时候,应用程序能够指定一定字节数的附加内存空间,称之为窗口附加内存。在创建这类窗口时,windows系统就为窗口的结构分配和追加指定数目的窗口附加内存空间,应用程序可用这部分内存存储窗口特有的数据。
因为附加的内存是从系统局部堆里分配的,所以程序要节约使用附加内存。如果附加窗口内存字节超过40字节,那么函数RegisterClassEx调用失败。如果要这么做,就要分配自己的内存并把指向附加窗口内存的内存指针保存起来。
函数SetWindowWord和SetWindowLong把某个值复制到附加内存,函数GetWindowWord和GetWindowLong 从窗口附加内存检取一个值。结构WNDCLASSEX的cbWndExtra成员指定所分配的窗口附加内存总数,如果应用程序不用窗口附加内存,则必须把cbWndExtra成员置成0。
类和私有设备描述表
设备描述表是特殊的数据集,应用程序用来在它们窗口的客户区中进行绘画。windows系统为显示每一个窗口要求有一个设备描述表,但允许操作系统在如何存储和对待这个设备描述表方面有一些灵活性。
如果设备描述表没有显式地给出,windows系统假定每个窗口使用一个从windows系统管理的描述表池中检取的设备描述表,在这种情况下,每个窗口必须在绘制之前检取和初始化这个设备描述表,并在绘制后释放它。
为避免每次对窗口内部进行绘制时都要检取设备描述表,应用程序可为窗口类设置CS_OWNDC风格,这个类风格指导windows系统创建一个私有的设备描述表——也就是,为这个窗口类中的每一个窗口分配独立的设备描述表。应用程序仅需检取一次设备描述表,即可用于后面的所有绘制操作。尽管CS_OWNDC风格是很方便的,但也需要小心使用,因为每一个设备描述表都占用系统资源的一个重要部分。
通过指定S_CLASSDC风格,应用程序能够创建一个类设备描述表,类设备描述表是个难得使用的特性,它允许进程中的同一个窗口类所创建的多个窗口使用完全相同的设备描述表进行绘画。
应用程序能够设置CS_PARENTDC风格创建继承其父窗口设备描述表的子窗口.这种风格允许子窗口在其父窗口的客户区中进行绘画。
使用窗口类
在windows系统中,每个应用程序必须注册它自己的窗口类,应用程序可使用函数RegisterClassEx随时注册一个应用程序局部类,必须在应用径序中定义窗口过程.填充WNDCLASSEX结构的成员,再把结构的指针传给函数RegisterClassEx。
下面的范例说明如何注册一个局部窗口类以及怎样使用它来创建应用程序的主窗
#include <windows.h>
// Global variable
HINSTANCE hinst;
// Function prototypes.
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
InitApplication(HINSTANCE);
InitInstance(HINSTANCE, int);
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
// Application entry point.
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
if (!InitApplication(hinstance))
return FALSE;
if (!InitInstance(hinstance, nCmdShow))
return FALSE;
while (GetMessage(&msg, (HWND) NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
UNREFERENCED_PARAMETER(lpCmdLine);
}
BOOL InitApplication(HINSTANCE hinstance)
{
WNDCLASSEX wcx;
// Fill in the window class structure with parameters
// that describe the main window.
wcx.cbSize = sizeof(wcx); // size of structure
wcx.style = CS_HREDRAW | CS_VREDRAW; // redraw if size changes
wcx.lpfnWndProc = MainWndProc; // points to window procedure
wcx.cbClsExtra = 0; // no extra class memory
wcx.cbWndExtra = 0; // no extra window memory
wcx.hInstance = hinstance; // handle of instance
wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION); // predefined app. icon
wcx.hCursor = LoadCursor(NULL, IDC_ARROW); // predefined arrow
wcx.hbrBackground = GetStockObject( WHITE_BRUSH); // white background brush
wcx.lpszMenuName = "MainMenu"; // name of menu resource
wcx.lpszClassName = "MainWClass"; // name of window class
wcx.hIconSm = LoadImage(hinstance, // small class icon
MAKEINTRESOURCE(5), GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
LR_DEFAULTCOLOR);
// Register the window class.
return RegisterClassEx(&wcx);
}
BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
{ HWND hwnd;
// Save the application-instance handle.
hinst = hinstance;
// Create the main window.
hwnd = CreateWindow(
"MainWClass", // name of window class
"Sample", // title-bar string
WS_OVERLAPPEDWINDOW, // top-level window
CW_USEDEFAULT, // default horizontal position
CW_USEDEFAULT, // default vertical position
CW_USEDEFAULT, // default width
CW_USEDEFAULT, // default height
(HWND) NULL, // no owner window
(HMENU) NULL, // use class menu
hinstance, // handle of application instance
(LPVOID) NULL); // no window-creation data
if (!hwnd)
return FALSE;
// Show the window and send a WM_PAINT message to the window
// procedure.
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
return TRUE;
}
注册应用程序全局类与注册应用程序局部类差不多,只是有如下一些不同:
·结构WNDCLASSEX的 style成员必须置成CS_GLOBALCLASS风格o
‘类可在应用程序或DLL的描述表中注册。
·应用程序或DLL不需要在注册类之前检查应用程序或DLL的前一个实例。
GetClassInfoEx
GetClassLong
GetClassName
GetClassWord
GetWindowLong
GetWindowWord
RegisterClassEx
SetClassLong
SetClassWord
SetWindowLong
SetWindowWord
UnregisterClass
Obsolete Functions
GetClassInfo
RegisterClass
WNDCLASS
WNDCLASSEX
窗口过程
Microsoft Windows系统中的每个窗口都有一个相应的窗口过程,用于处理发送或投递给该类窗口的所有消息的函数,窗口外观和特性的所有方面取决于:窗口过程对这些消息的响应。
每个窗口都是某个窗口类的成员,窗口类决定了窗口用来处理它的消息的窗口过程,属于同一种窗口类的所有窗口使用相同的窗口过程,例如,系统为组合框类(COMBOBOX)定义了一个窗口过程,那么所有的组合框都可使用这个窗口过程。
应用程序通常至少要注册一个新的窗口类及相应的窗口过程,注册了一个类之后,应用程序可创建许多这种类的窗口.而且所有窗门都使用相同的窗口过程。这就意味着几个窗口能够同时调用同—段代码,因此开发人员在修改共享的窗口过程资源时必须很小心。有关窗口类,参见 “窗口类”。
对话框过程与窗口过程具有相同的结构和功能,这一节中涉及到窗口过程的所有方面都可用十对话框过程。有关对话框参见 “对话框”。
窗口过程的结构
窗口过程是一个带有四个参数的函数。它返回个32位有符号数,参数包括窗口句柄、UNIT消息标识以及两个 用WPARAM和LPARAM数据类型说明的参数,操作系统把WPARAM定义成一个32位无符号整数,LPARAM被定义成一个32位有符号整数。
下表是对窗口过程参数的描述。
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle of window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
通常消息参数的低位字和高位字都用来存储信息,win32应用程序编程接门(APl)有几个应用程序可用来从消息参数中抽取信息的宏。例如,宏LOWORD从消息参数中抽取低位字(0—15位),其它宏还音HIWORD, LOBYTE, 和 HIBYTE。
窗口函数返回的值被说明成LRESULT数据类型,它是一个32位有符号数,对返回值的解释取决于不同的消息,应根据每条消息的种类以确定适当的返回值。
因为有可能发生对窗口过程的递归调用,所以减少对局部变量的使用显得尤为重要。在处理某条消息时,应用程序应在窗口过程的外面调用函数以避免无节制的使用局部变量,由于深递归而造成的堆溢出。
默认的窗口过程
默认窗口过程函数DefWindowProc定义了一些所有窗口都有的基本特性。默认窗口过程为窗口提供了最基本的功能,应用程序的窗口过程应把它不处理的任何消息传给函数DefWindowProc进行默认处理。
窗口过程子类
如果应用程序创建了一个窗口.操作系统就得分配一块内存来存贮窗口特有的信息,如用于处理消息的窗口过程的地址。如果windows系统需要把一条消息传给窗口,它就搜寻窗口特有信息来得到窗口过程的地址,以便把消息传给过程。
建子类是—种技术,它使得应用程序在窗口能够处理消息之前截取和处理发送或投递给某个窗口的消息。通过给一个窗口建子类,应用程序可增加、修改或监视窗口的特性。应用程序可以为任何窗口建子类,包括属于系统全局类的窗口,如编辑控制框或列表框。例如,应用程序可以通过了类编辑控制框来禁止控制框接收某些字符。
应用程序通过把窗口原窗口过程的地址换成一个叫做子类过程的新窗门过程的地址来为一个窗口建子类,从此,就由此类过程接收任何发送或投递给窗口的消息。
子类过程在它接收到一条消息时可能有三种情况:把消息传给原来的窗口过程;修改这条消息后再传给原来的窗口过程;再一个就是处理这条消息而不传给原来的窗口。如果子类过程处理某条消息,它可在把这条消息传给原来的窗口过程之前、之后或是在这前后都处理这条消息。
windows系统提供两种类型的子类:实例子类和全局子类。对于实例子类,应用程序取代的是一个窗口的某个实例的窗口过程地址.应用程序必须用实例子类来子类一个存在的窗口。对于全局子类,应用程序取代的是窗口类的WNDCLASS结构中窗口过程的地址,后面所有由这个窗口类创建的窗口用的就是子类过程的地址,但由这个窗口类创建的已存在的窗口是不受影响的。
实例子类
应用程序通过函数SetWindowLong来为一个窗口的某个实例建子类.应用程序把GWL_WNDPROC标志、要建子类的窗口的句柄以及子类过程的地址传给SetWindowLong。子类过程可驻留在应用程序的模块小或是一个动态链接库(DLL)中。应用程序必须在应用程序的或DLL的模块定义文件(.DEF)的EXPORTS语句中列出中子类过程的名字。
SetWindowLong返回窗口的原窗口过程的地址,应用程序必须保留这个地址,用在后面对CallWindowProc的调用中,以便把截取的消息传给原窗口过程。应用程序从窗口删除子类时也需要原窗口过程的地址。这就需要再一次调用SetWindowLong,把原窗口过程的地址以及GWL_WNDPROC标志和窗口的句柄传给它。
应用程序能够为系统中的任何一个窗口建子类,但是在子类并不属于它的窗口时,应用程序必须保证子类过程不要破坏窗口的原有特性,因为应用程序并不控制这个窗口,它就不能使用有可能被窗口属主改变的窗口信息。
应用程序在没有完全搞清窗口或类附加字节确切的意思,以及原窗口过程如何使用这些字节之前是不能使用它们的。即使搞情了,如果不是它自己的窗口也不应使用它们。如果应用程序使用属于另一个应用程序的窗口的窗口附加字节,而某些东西被其属主改变了,子类过程就可能失败。基于这个理由,应用程序最好不要为一个属于系统全局控制类的窗口建子类。windows系统管理系统全局类.控制的某些方面往往会随windows系统版本的不同而有所改变。如果应用程序必须给一个系统全局类窗口建子类,开发人员就可能需要在新的windows版本发行后更新应用程序。
因为实例子类是发生在窗口创建之后,应用程序建窗口子类时就不能为窗口追加任何附加字节。子类窗口的应用程序应该使用窗口的属性列表来存储子类窗口的实例所需的任何数据。有关窗口属性,参见 “窗口性质”。
如果应用程序为一个子类窗口建子类,就必须按相反的次序删除前面的子类,否则就会发生不可恢复的系统错误。
全局子类
要为一个窗口类建全局子类,应用程序必须使用这个窗口类的某一个窗口的句柄。应用程序也需要用这个句柄来删除子类。要得到句柄,应用程序通常用要建子类的窗口类创建一个隐藏的窗门,有了句柄之后,应用程序调用函数SetClassLong时指定这个句柄、GCL_WNDPROC标志以及子类过程的地址,SetClassLong返回这个窗口类的原窗口过程的地址。
全局子类中原窗口过程地址的用法与实例子类中相同,子类过程调用函数CallWindowProc把消息传给原窗口过程,应用程序从窗口类中删除子类也是调用SetClassLong,向它传送原窗口过程的地址、GCL_WNDPROC标志以及要建子类的窗口类的某一个窗口句柄。为一个控制类建全局子类的应用程序在应用程序结束时必须删除子类.否则也有可能发生不可恢复的系统错误。
全局子类除了具有与实例子类同样的限制,还要加上另外一些限制。应用程序在没有搞清原窗口过程是如何使用类或窗口实例的附加字节之前是不能使用的。如果应用程序必须与窗口的数据发生联系.它就得使用窗口属性列表。
应用程序不能为一个系统全局类建全局子类,如果有不止一个应用程序为一个控制类建全局子类,就会发生不可恢复的系统错误。如果应用程序需要为一个控制类建全局子类.那么就得采用下面章节中所讲的超类技术。
窗口过程超类
超类也是一个技术,它允许应用程序建一个新的窗口类,既具有原窗口类的基本功能,又增加一些由应用程序提供的增强功能。用于产生超类的窗口类称之为基类,通常基类是系统全局窗口类如:编辑控制框,当然它也可以是其它窗口类。
注:应用程序不能为系统全局类SCROLLBAR建超类。
超类有它自己的窗口过程,叫做超类过程。超类过程处理一条消息也有三种情况:把消息传给原窗口过程;修改消息后再传给原窗口过程;再—个就是直接处理这条消息,不把它传给原窗口过程。如果超类过程处理一条消息,它也可以在把它传给原窗口过程之前、之后或者是在这前后都对一条消息进行处理。
与子类过程不同,超类过程是能够处理窗口创建消息的(WM_NCCREATE, WM_CREATE,等等),但它必须还要把这些消息传给原基类窗口过程,以便基类窗口过程能够完成它的初始化过程。
要超类一个窗口类,应用程序首先调用函数GetClassInfo来检取有关基类的信息,GetClassInfo用基类的WNDCLASS结构的值填充一个WNDCLASS结构。下一步是把它自己的实例句柄复制到WNDCLASS结构的hInstance成员中.把超类的名字复制到lpszClassName成员中。如果基类有一个菜单,应用程序还必须用同一个菜单标识提供一个新的菜单,再把菜单名复制到lpszMenuName成员中。如果超类处理WM_COMMAND消息.并且不再把它传给基类的窗口过程,那么菜革中就无需有相应的标识。函数GetClassInfo并不返回WNDCLASS路结构的lpszMenuName, lpszClassName, 或 hInstance成员。
应用程序必须也能设置WNDCLASS结构的lpfnWndProc成员,函数GetClassInfo用这个类的原窗口过程的地址填充这个成员,应用程序必须保存这个地址.以便向原窗口过程传递消息.然后再把超类过程的地址复制到lpfnWndProc成员中。如果需要的话,应用程序是能够修改WNDCLASS结构的其它成员的.在它填充了WNDCLASS结构之后,应用程序把结构的地址传给函数RegisterClass注册这个超类,这个超类就可用来创建窗口。
因为超类注册了新的窗口类.应用程序就能够添加类附加字节和窗口附加字节。超类是不能使用基类或窗口的原附加字节的.理由与实例子类或全局子类不能使用它们是一样的。而且,如果应用程序要为类或窗口实例添加它自己使用的附加字节,它就必须是在原基类使用的附加字节的数目基础上添加附加字节。出为用于基类附加字节的数对不同版本的基类有所不同,超类附加字节的起始偏移值也会随着基类版本的不同而不同。
使用窗口过程
这一节讲解怎样完成下列工作
·设计窗口过程
·联系窗口过程和窗口类
·为窗口建子类
设计窗口过程
下面的范例说明了一个典型的窗口过程的结构,这个窗口过程把消息参数用在switch语句中,通过case语句来处理每一条消息。注意每个case为每一条消息返回一个指定值.对于它不处理的消息,这个窗口过程调用函数DefWindowProc
LRESULT CALLBACK MainWndProc(
HWND hwnd, // handle of window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam) // second message parameter
{
switch (uMsg)
{
case WM_CREATE:
// Initialize the window.
return 0;
case WM_PAINT:
// Paint the window's client area.
return 0;
case WM_SIZE:
// Set the size and position of the window.
return 0;
case WM_DESTROY:
// Clean up window-specific data objects.
return 0;
//
// Process other messages.
//
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
WM_NCCREATE 当某个窗口第一次被创建时,此消息在WM_CREATE消息发送前发送, 若返回FALSE, 函数CreateWindowEx 调用失败. WM_DESTROY窗口销毁. WM_NCDESTROY 消息在销毁前发送.
至少,窗口过程应该处理WM_PAINT消息画它自己的窗口,通常它还应该处理鼠标和键盘消息,根据每条消息的描述以确定窗口过程是否需要处理它们。
应用程序可以在处理消息时调用函数DefWindowProc,这种情况下,应用程序能够在把消息传给函数DefWindowProc之前修改消息.或在完成它自己的操作之后再进行默认处理。
对话框过程接收的是WM_INITDIALOG消息而不是WM_CREATE消息,也不把没有处理的消息传给函数DefDlgProc,除此以外,对话框过程与窗口过程完全相同。有关对话框过程, “对话框”。
联系窗口过程和窗口类
在注册类时联系窗口过程和窗口类,必须用有关类的信息填充WNDCLASS结构,lpfnWndProc成员必须指定为窗口过程的地址。要进行类注册,也就是把WNDCLASS结构传给函数RegisterClass。一旦窗口类被注册,窗门过程就会自动地与用这个类所创建的窗口联系起来。
下面的范例说明了如何把前一个例子中的窗口过程与一个窗口类联合起来。
int APIENTRY WinMain(
HINSTANCE hinstance, // handle of current instance
HINSTANCE hinstPrev, // handle of previous instance
LPSTR lpCmdLine, // address of command-line string
int nCmdShow) // show-window type
{
WNDCLASS wc;
// Register the main window class.
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC) MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = "MainMenu";
wc.lpszClassName = "MainWindowClass";
if (!RegisterClass(&wc))
return FALSE;
//
// Process other messages.
//
}
为窗口建子类
要为一个窗口的实例建子类,需要调用函数SetWindowLong,并指定GWL_WNDPROC标志、要建子类的窗口的句柄和子类过程的指针。函数SetWindowLong返回原窗口过程的指针;通过这个指针向原窗口过程传递消息。
下面的范例说明了怎样为对话框中一个编辑控制框建子类的实例.在控制框有输入焦点时,这个子类窗口过程允许编辑控制框接收所有理盘输入.包括ENTER和TAB
WNDPROC wpOrigEditProc;
LRESULT APIENTRY EditBoxProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HWND hwndEdit;
switch(uMsg)
{
case WM_INITDIALOG:
// Retrieve the handle of the edit control.
hwndEdit = GetDlgItem(hwndDlg, ID_EDIT);
// Subclass the edit control.
wpOrigEditProc = (WNDPROC) SetWindowLong(hwndEdit,
GWL_WNDPROC, (LONG) EditSubclassProc);
//
// Continue the initialization procedure.
//
return TRUE;
case WM_DESTROY:
// Remove the subclass from the edit control.
SetWindowLong(hwndEdit, GWL_WNDPROC,
(LONG) wpOrigEditProc);
//
// Continue the cleanup procedure.
//
break;
}
return FALSE;
UNREFERENCED_PARAMETER(lParam);
}
// Subclass procedure
LRESULT APIENTRY EditSubclassProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_GETDLGCODE)
return DLGC_WANTALLKEYS;
return CallWindowProc(wpOrigEditProc, hwnd, uMsg, wParam, lParam);
};
CallWindowProc
DefWindowProc
WindowProc