Kyle Marsh
Microsoft Developer Network Technology Group
MSDN
- 摘要
本文主要介绍
在本文中
-
- 简介
窗口类的风格决定了窗口的外观和风格。所有的窗口都会属于某一个窗口类。在创建一个窗口之前,必须注册
大部分开发人员认为窗口类是个麻烦的东西,他们顶多就是从例子或其他代码中拷贝一个
我们的将讨论的题目包括:
- 什么是窗口类
- 系统全局,应用程序全局,应用程序局部类的区别
- 类里包含了那些信息
- 这些信息如何影响窗口的表现
- 应用程序如何使用窗口类信息。
- 简介
一:窗口类的类型
window
- 系统全局类(
- 应用程序全局类(
- 应用程序局部类(
Application global classes)
Application local classes)
1.系统全局窗口类(
System Global Classes)windows
以及其他不那么常用的控件如TabCtrl等.
还有:
- 菜单窗口
- 桌面窗口
- 对话框窗口
- 任务条窗口
- 题头带图标的窗口
- ComboLBox
- MDIClient:
- Listbox
- ComboBox
- ScrollBar
- Button
- Static
- Edit
- winNT
- Class Styles (style)
- 如果使用
- ScrollWindow
- ScrollWindowEx
- The Window Procedure (lpfnWndProc)
- Extra Class and Window Bytes (cbClsExtra and cbWndExtra)
- Instance Handle (hInstance)
- Class Icon (hIcon)
- Class Cursor (hCursor)
- Class Background Brush (hbrBackground)
- Class Menu (lpszMenuName)
- Class Name (lpszClassName)
- GetClassLong
- SetClassLong
- GetClassWord
- GetClassName
- GetClassInfo
- 子类化:(SubClassing)
- 超类化:(Superclassing)
(下拉组合框)
(滚动条)
(按钮)
(静态标签)
(编辑框)
控件的下拉列表窗口
风格窗口的子窗口
WindowsNT
为DDEML(Dynamic Data Exchange Management Library)增加了DDEMLEvent类,因为DDEML功能已经结合到USER里去了。Windows 95/98
所有的
应用程序可以通过
在
系统全局窗口类实现
现在的
-
- 在
系统全局类在
- 如果在
- 复制一份被子类化的窗口类的信息 。将复制的新类填加到应用程序的
- 强制进程里所有的子类化过的窗口实例使用这个系统类的拷贝。但这不影响已经存在的窗口,窗口是使用事先已经拷贝到窗口实例数据区的类信息,并非直接使用进程里保存的类的信息。子类化只更新了进程的窗口类列表里的类的信息,而没有更新窗口实例里的类。
win9x和win3.1的实现使相当相象的。在系统启动时,USER模块创建了系统类。win9x和win3.1不同的是:当发现一个应用程序子类化了某个系统类的时候,win9x将进行如下工作:debug模式下运行,在debug屏幕上显示一个 warning 信息
“私有”系统类列表里。win9x系统里,系统为每个进程都保持了这样的一个列表,以供系统存放系统全局类的克隆信息。
16
winNT
服务进程管理每个
在
则有很多的不同。winNT包括了两个win32子系统:一个服务进程和一个在各win32进程里运行的动态连接库(DLL)。以edit类为例,winNT在各进程空间里,从DLL里导出和注册edit类。这样,处理EDIT控件的代码可以存在于DLL里,也即在各进程空间里。不需要系统分配局部过程调用来处理Edit控件,应用程序对控件的频繁调用所导致的系统开销也被避免了。因为EDIT 控件实例仅仅在各进程空间里操作自身数据,所以对系统鲁棒性的冲击就降低了。win32应用程序的信息,包括应用程序的公有和私有窗口类。当创建一个win32线程的过程开始(即某个线程调用USER模块或GDI模块的函数时),USER模块检查该线程是否该进程的第一个线程,如果是(一般是主线程),USER模块为该进程注册系统类。当为了任何一个进程而注册一个类(服务模块进程除外),该类就会添加到该进程的公有或私有列表里。为了提高效率,windows为每个进程都注册系统类,并且把类信息的拷贝储存在应用程序的空间里。这增加了鲁棒性,但是比起Windows95,增加了需要使用的内存。windowsNT也由此获得了更高的性能,因为当子类化一个系统类的时候,winNT不需要象window95那样重新分配内存和拷贝类信息。winNT里,16位的应用程序依然共享同一进程,也共享所有的系统全局类。16位程序总是不稳定因素的起源。2.应用程序全局类。
应用程序全局类是注册的时候指定了
CS_GLOBALCLASS标志的类(该标志还有后续叙述)。16
位系统比如win3.1的应用程序“全局”类是真正意义的“全局”的,一个DLL或应用程序注册的应用程序全局类,系统内所有的DLL和应用程序都可以使用。一个应用程序全局类在“全局”的意义上和系统全局类一致,只是它是由应用程序创建的而不是系统创建的而已。Win32
作为这个特性的扩展,
的应用系统全局类本质的不同是:应用程序全局类只是在进程内部的“全局”而已。这是什么意思呢?一个DLL或.EXE可以注册一个类,这个类可以让在相同的进程空间里其他.EXE和DLL使用。如果一个DLL注册了一个非应用程序全局类的窗口类,那么,只有该DLL可以使用该类,同样的,.EXE里注册的非应用程序全局类也适用这个规则,即该类只在该.EXE里有效。win32有一项技术,允许一个第三方窗口控件在DLL里实现,然后把这个DLL载入和初始化到每个Win32进程空间里。这项技术的细节是,把DLL的名字写入注册表的指定键值里: HKEY_LOCAL_MACHINE/Software/Microsoft/Windows NT/CurrentVersion/Windows/APPINIT_DLLS
这样当任意一个
win32应用程序加载的时候,系统也同时将该dll加载到进程空间里(这可能有点过于奢侈,因为很多win32程序不一定会使用该控件)。DLL在初始化的时候注册应用程序全局类,这样的窗口类就可以在每个进程空间的.EXE或DLL里使用了。这个技术基于win32系统的这个特性:允许在每个进程空间里自动的(也是强制的)加载特定的DLL(事实上,这也是打破进程边界,把你的代码切入到其他进程里的一种办法)。
3. 应用程序局部类
WIN32
应用程序局部类是使用最频繁的类(绝大部分的应用程序为主窗口注册的类都是应用程序局部类),仅仅在声明和注册该类的应用程序模块或DLL自身里使用。注册一个应用程序局部类和应用程序全局类的区别是,局部类不包括CS_GLOBAL CLASS标志。
二:窗口类包含的信息和作用
窗口类都包含些什么信息呢?让我们看以下窗口类结构体。
WNDCLASS
结构包含的是一般的窗口类的信息 typedef struct tagWNDCLASS { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName;} WNDCLASS;
成员 | 描述 |
style | 一组标志位的组合。定义比如窗口位置,设备上下文 (DC)分配,双击的处理等特征。 |
lpfnWndProc | 指向窗口过程的地址,该窗口过程负责处理窗口类相应的窗口消息 |
cbClsExtra | 指明需要额外分配的内存数量,单位为 byte,系统为该类分配保留指定数量的额外内存 |
cbWndExtra | 指明需要额外分配的内存数量,单位为 byte,系统为每个该类所对应的窗口实例分配保留指定数量的额外内存 |
hInstance | 标识注册该类的 DLL或应用程序实例 |
hIcon | 当一个属于该类的窗口被最小化的时候,显示的图标 . |
hCursor | 属于鼠标该类的窗口里显示的指针 |
hbrBackground | 定义当程序打开或重画某个属于该类的窗口是,填充窗口客户区的颜色和样式 |
lpszMenuName | 如果没有显性定义菜单时,窗口的默认菜单 |
lpszClassName | 字符串的类名 |
以下详细讨论各成员的具体意义:
Style
成员决定了从该类创建出来的窗口的风格,可以使用下列值的一个或几个的组合。CS_BYTEALIGNCLIENT
,CS_BYTEALIGNWINDOW如果使用这两个标志,窗口的的客户区或整个窗口都在“字节边界”上对齐,也就是说,系统调整窗口的水平位置,客户区或整个窗口的左边坐标是
看看系统如何摆放一个边框宽度为
8的倍数。win32 SDk的文档说这两个标志影响窗口的宽度,但是实际上笔者没有发现这个现象,此标志只影响窗口的水平位置(左边)。4的窗口: Original window location | Placement with CS_BYTEALIGNWINDOW | Placement with CS_BYTEALIGNCLIENT |
0,y | 0,y | 4,y |
1,y | 0,y | 4,y |
2,y | 0,y | 4,y |
3,y | 0,y | 4,y |
4,y | 8,y | 4,y |
5,y | 8,y | 4,y |
6,y | 8,y | 4,y |
7,y | 8,y | 4,y |
8,y | 8,y | 12,y |
9,y | 8,y | 12,y |
10,y | 8,y | 12,y |
11,y | 8,y | 12,y |
12,y | 16,y | 12,y |
13,y | 16,y | 12,y |
14,y | 16,y | 12,y |
15,y | 16,y | 12,y |
16,y | 16,y | 20,y |
这两个标志在以下两个情况中无效:
- 如果显示设备对每个象素使用
- 如果使用
SetWindowPos函数改变窗口的位置,此函数忽略窗口的CS_BYTEALIGNCLIENT和CS_BYTEALGNWINDOW标志指定的位置限定。在Win32 SDK文档关于WM_WINDOWPOSCHANGEING的描述让人费解,它说:“对于一个有WS_OVERLAPPED和WS_THICKFRAME风格的窗口来说,DefWindowProc函数响应WM_WINDOWPOSCHANGING消息,并向窗口发送一个WM_GETMINMAXINFO消息,此消息的处理是验证窗口的新位置和尺寸,迫使窗口接受CS_BYTEALIGNCLIENT和CS_BYTEALIGNWINDOW限定”
其实当
DefWindowProc接受到WM_WINDOWPOSCHANGING消息后,确实发送一条WM_GETMINMAXINFO消息,但是并不迫使窗口接受字节对齐的风格,为了确保字节对齐风格,应用程序必须通过计算再改变其水平位置CS_BYTEALIGNCLIENT
如果程序使用
其实对于可以显示
使用
和CS_BYTEALIGNWINDOW的存在是为了优化程序的性能,尤其在是3.0版本的windows系统以前。那时侯,所有的系统字体宽度都是固定的,系统可以通过使窗口字节对齐优化字体的显示。在3.0以后的window里,这一点已经被忽略了。BitBlt函数从一个窗口向另一个窗口,或者从窗口的某个矩形区域向另一个矩形区域拷贝象素,还是可以通过设置CS_BYTEALIGNCLIENT标志来获得更好的性能。如果客户区是字节对齐的,程序也可以尽量保证BitBlt操作发生在字节对齐的矩形里, BitBlt操作将会比发生于非字节对齐的矩形里的操作更快。当然,字节对齐仅仅是对窗口的左边界而言的,如果要进一步的提高性能,应该连宽度都进行字节对齐。256及以上颜色的视频卡或显示器,对字节对齐的需求已经微乎其微了。256色显示器已经自己实现了字节对齐,实际上,一些16色的显示器也是字节对齐的。在大部分显示器上根本看不出来字节对齐限定对窗口位置的影响。一句话,字节对齐标志已经没有什么重要的作用了。CS_BYTEALIGNWINDOW的时候也等同于包含了CS_BYTEALIGNCLIENT;对话框类本身已经默认包含了CS_BYTEALIGNWINDOW标志CS_OWNDC, CS_CLASSDC
, CS_PARENTDC这几个标志决定窗口的默认
- 如果使用了
- 如果不指定
在
出于对
DC CS_OWNDC标志,属于此窗口类的窗口实例都有自己的DC(称为私有DC),私有DC仅属于该窗口实例,所以程序只需要调用一次GetDC或BeginPaint获取DC,系统就为窗口初始化一个DC,并且保存程序对其进行的改变。ReleaseDC和EndPaint函数不再需要了,因为其他程序无法访问和改变私有DC。当选择了CS_OWNDC,程序改变影射模式(Mapping Mode)的时候必须小心,当由系统擦除窗口的背景时,系统假定和默认其影射模式是MM_TEXT。如果私有DC的影射模式不一样,窗口被擦除的地方将不再可见。
CS_OWNDC
如果使用
为
标志在WinNT和Win9x的作用也是有差别的。在WinNT,win32子系统和其他NT进程有相同的地址空间(4GB),应用程序使用此地址空间里的2GB,每个有CS_OWNDC标志窗口实例占用800个字节,在NT下,这里面没有什么问题。而Win9x为GDI保留了64K的局部堆,DC进入这个堆之后,即使其他GDI对象数据被释放,DC依然在。这意味着每个CS_OWNDC的窗口都在这个宝贵的内存空间里占用800个字节。所以,为win9x写的程序最好尽量少的使用CS_OWNDC这个标志。win9x的修正版打算解决这个问题,但效果不明显(而且win9x已经开始式微了)CS_CLASSDC标志,所有属于该类的窗口实例共享相同的DC(称为类DC).类DC有一些私有DC的优点,而更加节约内存(因为不需要为每个窗口实例都分配800字节的DC空间了)。每个窗口实例都通过GetDC或BeginPaintde得到设备上下文(DC)句柄,如果没有别的窗口需要该DC,不需要调用ReleaseDC或EndPaint释放DC。在一个窗口实例上通过GetWindowDC,GetDC,GetDCEx,BeginPaint获得 DC,并对其中的一些参数进行更改的话,所进行的更改除了剪切区域和设备本身属性(Device origin)之外对所有其他窗口实例都是有效的。和CS_OWNDC相同的是,必须确保影射模式也是MM_TEXT,否则,被系统擦除的背景将不再可见。NT编写的程序最好不要使用这个标志,因为“节约内存”的好处根本不明显。对于win9x来说,却是有用的,因为对于win9x的64K的GDI局部堆来说,节约的空间意义更重大一些。CS_PARENTDC标志,属于这个类的窗口都使用它的父窗口的句柄。和CS_CLASSDC相似的是,多个窗口共享一个DC,不同的是,这多个窗口(虽然有父子关系并且共享DC)并不要求都属于同一个窗口类。
WIN9x
下,所有的标准窗口控件都有CS_PARENTDC标志。WinNT下,除了ComBoBox之外的窗口控件都有此标志。因此,比如Edit控件和ListBox控件都共享他们的父窗口(比如对话框)的DC(
使用
注:这一段是我根据原文再加上自己的理解,不一定完全正确:) CS_PARENTDC带来的好处就一个字:速度。Win9x系统为每个线程预留了5个DC缓冲区,如果一个窗口(比如一个对话框)有多于5个的字窗口(比如有6个或以上的编辑框),而每个子窗口都有自己的DC的话,DC缓冲区就失去了效力,系统得为每个子窗口根据剪切边界和设备属性重新初始化一个DC,这多于5个的DC在DC缓冲里不停交换,不能确保在缓冲里被访问。而如果每个子窗口都和父窗口共享一个DC,在频繁访问该DC时,该DC在于DC 缓冲里被找到的几率显然相当的高,从而可以被高速的访问,显著的提高了速度,所以一般来说标准窗口控件都和父窗口共享DC。WinNT可以拥有多于5个的DC缓冲,所以它可能可以提供足够的DC缓冲 -- 但是不保证时时如此。CS_PARENTDC的另一个效果是,子窗口可以在父窗口的客户区随意做画,就象画在自己的客户区一样. 负责表现Edit控件和ListBox控件周围的3D效果的CTL3D库就是利用了这个特性。注意如果程序需要改变各子窗口的影射模式,那么最好不要用CS_PARENTDC标志,否则将很容易引起各子窗口影射模式的混乱,因为所有的子窗口都使用同一个DC。CS_OWNDC,CS_CLASSDC或CS_PARENTDC这几个标志,此类的窗口使用一个通用DC,并置于DC缓冲里以供使用。通用DC在使用前获取,使用后释放,在DC获取的时候,DC里的上下文按默认值初始化,除非当时该DC已经在窗口的DC缓冲里(比如没有调用ReleaseDC或EndPaint释放DC),这样的话DC的剪切边界和设备属性都不需要被重新初试化,可以节约一些时间。WinNT里,DC缓冲没有确定的数量。如果所有的DC缓冲都在使用中,而程序调用了GetDC和BeginPaint,NT则再分配一个缓冲。win9x的兼容考虑,win32程序最好把 DC的使用限制在5个以下,并且尽可能快的释放DC.
如果要忽略类创建时由标志位决定的窗口的默认
ScrollWindow
使用系统通用DC(这种DC使用MM_TEXT的影射模式),而忽略窗口的标志。传递给ScrollWindowEx的坐标值必须是MM_TEXT影射模式下的客户区坐标值。
CS_DBLCLKS
CS_DBLCLKS
- 如果窗口没有
- WM_LBUTTONUP
- WM_LBUTTONDOWN
- WM_LBUTTONUP.
- 如果窗口有
- WM_LBUTTONUP
- WM_LBUTTONDBLCLK
- WM_LBUTTONUP.
注意,在上述序列中间可能会插入其他的一条或一些消息,所以这两个消息序列不一定是完全连续的。
其实,在没有指定
所有的标准窗口控件,对话框,桌面窗口类都默认拥有
标志使窗口可以检测到双击事件。窗口响应双击的细节如下:CS_DBLCLKS标志,系统向窗口依次发送如下消息:
WM_LBUTTONDOWN
其实相当于两个单击。
CS_DBLCLKS标志,则系统向窗口依次发送如下消息:
WM_LBUTTONDOWN
第一种情况中的第二个
WM_LBUTTONDOWN被WM_LBUTTONDBLCLK代替了。CS_DBLCLKS标志时,程序本身也可以检测到双击事件的。参见MSDN里Dr.GUT的"Simulating Mouse Button Clicks"文章,不过要有一些技巧.一般的情况下,如果没有指定CS_DBLCLKS,在窗口的消息循环里将不会得到WM_LBUTTONDBLCLK消息。CS_DBLCLKS标志。第三方控件最好也加上此风格,以使其在对话框编辑器里可以正常工作。CS_GLOBALCLASS
CS_GLOBALCLASS
是唯一一个针对类本身起作用而不是对单个窗口起作用的标志.系统将包含这种标志的窗口类作为应用程序全局类保存,这样类可以应用于注册该类的进程内的所有EXE和DLL,当EXE或DLL退出或卸载,或对该类调用UnregisterClass后,该类则被销毁。在注册该窗口的程序退出前,所有从该类创建的窗口都必须先关闭(对于DLL来说,这是自动进行的)。CS_HREDRAW ,CS_VREDRAW
CS_HREDRAW
标志表示当窗口的水平尺寸(宽度)改变的时候,重画整个窗口。CS_VREDRAW则是在窗口垂直尺寸(高度)改变时重画整个窗口。按钮和滚动条都有这两种风格。CS_NOCLOSE
如果指定了
CS_NOCLOSE标志,则窗口上的关闭按钮和系统菜单上的关闭命令失效。CS_SAVEBITS
菜单,对话框,下拉框都拥有
CS_SAVEBITS标志。当窗口使用这个标志时,系统用位图形式保存一份被窗口遮盖(或灰隐)的屏幕图象。首先,系统要求显示驱动保存图象位数据,如果显示驱动本身的存储空间足够,保存操作成功,window系统也可以使用这些保存的位数据。如果不够,系统将位数据在全局内存里以位图的方式保存,并且在USE模块局部堆里为每个窗口分配空间,保存一些事务数据(比如位图数据缓冲的大小)的结构。当程序使遮盖屏幕的窗口消失时,系统可以很快的从内存里恢复屏幕图象。CS_SAVEBITS
以上都是style成员的可选标志。
WNDCLASS
当一个程序或
cbClsExtra
在
成员指明为每个窗口类多分配的额外数据量,cbWndExtra成员则是指明为每个窗口实例分配的额外数据量。如果程序不需要分配额外的数据,这两个成员的值都应该设置为 0,以免产生不确定的数(比如一个非常大的数)而使系统进行错误分配,如果是负数,则该窗口类将不会被注册。Win9x和NT,分配40个及以下字节是没有多大意义的,当然,开发人员可以根据需要分配额外数据大小。如果用类声明并注册一个对话框类型的窗口,cbWndExtra的值必须设置为DLGWINDOWEXTRA,系统对话框管理器需要这么多的额外数据对对话框进行管理。
WNDCLASS
WNDCLASS
WNDCLASS
WNDCLASS
里的hbrBackground成员变量表示背景颜色的,类型为HBRUSH,即GDI刷子句柄。可以对其赋值为一个刷子句柄(比如用GetStockObject获取系统内置刷子对象句柄,或者自己创建指定颜色和风格的刷子的句柄)或者颜色值。如果是选择颜色值的话,必须使用下列系统标准颜色之一。 COLOR_ACTIVEBORDER | COLOR_HIGHLIGHTTEXT |
COLOR_ACTIVECAPTION | COLOR_INACTIVEBORDER |
COLOR_APPWORKSPACE | COLOR_INACTIVECAPTION |
COLOR_BACKGROUND | COLOR_INACTIVECAPTIONTEXT |
COLOR_BTNFACE | COLOR_MENU |
COLOR_BTNSHADOW | COLOR_MENUTEXT |
COLOR_BTNTEXT | COLOR_SCROLLBAR |
COLOR_CAPTIONTEXT | COLOR_WINDOW |
COLOR_GRAYTEXT | COLOR_WINDOWFRAME |
COLOR_HIGHLIGHT | COLOR_WINDOWTEXT |
用颜色值赋值时,必须强制类型转换为
如果
WNDCLASS
WNDCLASS
里的lpszClassName结构表示类的名字。局部窗口类名在进程范围必须为唯一的,由于仅要求“在进程内唯一”,对于两个不同的程序来说,其中的窗口类名有可能相同,比如,两个程序都可能拥有各自的"Main Wnd"窗口。全局窗口类的名字必须在全局窗口类和系统窗类口范围里唯一,比如,程序可以注册一个名字为"Edit"的局部窗口类,但是无法注册同样名字的一个全局窗口类。
三
系统如何定位窗口类当程序需要根据一个类创建一个窗口,系统通过以下步骤来定位该类
1
.系统在本进程的局部窗口类列表里寻找有相同名字的类。如果找到,由于局部窗口类是属于某一个.EXE模块或DLL的,所以所找到的类的进程实例句柄(hInstance)和本模块的实例句柄应该保持一致。这样的规则是为了防止进程内一个模块或DLL里注册的局部类被进程其他及其的模块或DLL发现。2
在
- 如果系统还是无法发现同名窗口类,则继续搜索全局系统类列表。
winNT
将此过程应用于所有由程序创建的窗口,包括从主窗口再创建出的其他窗口,比如对话框和消息框。win9x
同样将此过程应用于所有的窗口,除了消息框以外。当程序弹出一个消息框,Win9x不对局部类列表进行搜索,而是直接进行后续步骤。
四 应用程序的如何使用窗口类信息。
一旦注册了一个类,一般来说除了使用该类创建窗口之外就没有什么需要作的事情了。当然,如果需要访问该类信息,子类化,或者超类化该类,介绍一些方法就是有用的。
-
- 类访问函数
如果需要获取和改变类的信息,可以使用以下函数:
具体的调用参数以及其他相关的一些类函数可以参见
- 类访问函数
对该类的某成员写入一个long类型的值。比如,写入一个新的窗口过程的地址。
从
类信息读回来一个word类型的值。比如以下调用得到类的额外数据量:
nClassExtra = GetClassWord(hwnd,GCW_CBCLSEXTRA);
SetClassWord
向类信息写入一个word类型的值。比如改变窗口类图标。 获取窗口类的名字。获取除类名和菜单以外的全部类信息。
MSDN里windows classs Functions 一节。
术语“子类化”
在
术语
关于子类化和超类化的具体描述,请参阅
"超类化"(Superclassing)指创建一个新的类,该类使用某个现存类的窗口过程,继承该类的基本功能,并可以在此基础上进行扩展。MSDN里"safe subclasing in Win32"一文
结束语
: win32的窗口类给开发人员带来了很多有用的信息,在win32平台上开发程序,最好是注意使用兼容winNT和win9x的一些特性以保证程序的兼容性。 , HBRUSH类型:cls.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);MDI:ComBoBox