Window 窗口类

窗口类 WNDCLASS 总结

总结为下面的几个问题:
1. 什么是窗口类
2. 窗口类的三种类型
3. 窗口类各字段含义
4. 窗口类的注册和注销
5. 如何使用窗口类,子类化、超类化是什么


下面分别描述:

1. 什么是窗口类?
    窗口类定义了一系列属性,系统使用这些属性作为模板来创建出一个或多个 window (窗口)。
    每个窗口类都关联了一个窗口过程函数(window procedure), 由窗口类创建出的所有窗口(window), 都共享同一个窗口过程函数。
    在进程中创建窗口之前必须先注册窗口类(RegisterClassEx)。注册窗口类时,会将窗口类与一个窗口类名(class name)关联起来。当创建窗口时,只要传入窗口类名,系统就能帮你找到对应的窗口类,从而创建出窗口。
    
    由窗口类创建出来的窗口具备什么功能、如何显示,这些绝大部分取决于窗口过程函数怎么写。
    
    窗口类和窗口的关系,可以类比为类和对象的关系。窗口类就像类定义,而窗口就像对象。
    窗口过程函数就像类的成员函数,窗口过程函数中第一个参数就是窗口的句柄,类似于类的成员函数默认有一个 this 指针。
    C++ 中的类定义会由链接器帮忙找到,Win32 没有这回事儿,所以需要进行注册窗口类的操作,这样才能根据类名找到类定义。
    
    问题:自定义了一个窗口类,比如自己实现了一个 listbox 控件,怎样为这个控件添加外部接口呢? 类似于 listbox.SetListItem() 这样的接口怎么实现?此处存疑待考

    
2. 窗口类的三种类型
    有三种类型的窗口类,它们在作用范围、注册和销毁时机上有所差别:
        1. 系统窗口类(System Classes)
            系统注册的窗口类,程序无需注册就能使用。
            系统会在程序第一次调用 GDI 函数时为其注册系统窗口类, 每个程序都会得到一份系统窗口类的拷贝。
        
            作用域:   系统内所有 Win32 进程
            销毁时机: 无法销毁
        
            以下是可以被程序直接使用的一些 System Classes:
                Button
                ComboBox
                Edit
                ListBox
                MDIClient
                ScrollBar
                Static
            有些 System Classes 是系统内部使用的:
                ComboLBox
                DDEMLEvent     
                Message
                #32768
                #32769
                #32770
                #32771
                #32772
            
        2. 程序全局窗口类(Application Global Classes)
            程序自行注册的窗口类, WNDCLASS 的 style 指定为 CS_GLOBALCLASS, 注册后当前进程内全部模块都可以使用。
            例如,在 a.dll 的 DllMain 里注册一个全局窗口类,如果 b.exe 加载了 a.dll, 那么就可以在 b.exe 里使用那个窗口类。全局指的就是这个意思。
        
            作用域:   某进程的全部模块(exe, dll)
            销毁时机: 自行 UnRegisterClass
        
            作为这个特性的扩展, win32 有一项技术,允许一个第三方控件在 dll 里实现,然后把这个 dll 载入到每个 win32 进程地址空间里。这样所有进程都可以使用这个控件,这项技术的细节是,在 dll 的 DllMain 里注册第三方控件,然后把 dll 的名字写入注册表里:
                HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\APPINIT_DLLS
            这样当任意一个 win32 进程载入时,系统也同时会把这个 dll 载入这个程序的进程地址空间(这样做可能有点儿奢侈,并不是每个进程都需要这个第三方控件)。这样窗口类就可以在任意一个 win32 进程里直接使用了。
            
        3. 程序局部窗口类(Application Local Classes)
            程序自行注册的窗口类, WNDCLASS 的 style 不指定 CS_GLOBALCLASS, 注册后仅注册模块可以使用。
            例如,在 a.dll 里注册的局部窗口类,只能在 a.dll 里使用,即使 b.exe 加载了 a.dll, 也不能使用。
        
            作用域:   仅注册窗口类的那个模块可以使用
            销毁时机: 自行 UnRegisterClass
        
            程序局部窗口类是使用最频繁的类(大多数情况下窗口类不需要给别的模块使用)
            
        问题:在 dll 里注册的全局窗口类或局部窗口类, dll 被卸载时,会自动销毁吗?如果已经创建出了窗口,窗口会自动销毁吗?此处存疑待考。
        
        
3. 窗口类各字段含义
    窗口类用 WNDCLASS 结构体表示, WNDCLASSEX 是扩展版本,多了一个小图标 hIconSm 成员。
    typedef struct tagWNDCLASSEX {
        UINT      cbSize;
        UINT      style;
        WNDPROC   lpfnWndProc;
        int       cbClsExtra;
        int       cbWndExtra;
        HINSTANCE hInstance;
        HICON     hIcon;
        HCURSOR   hCursor;
        HBRUSH    hbrBackground;
        LPCTSTR   lpszMenuName;
        LPCTSTR   lpszClassName;
        HICON     hIconSm;
    } WNDCLASSEX, *PWNDCLASSEX;
    
    lpszClassName:
        窗口类名,标识一个窗口类,相当于窗口类的 ID;
        在同一个模块内,不能注册同名的局部窗口类;在同一进程内,不能注册同名的全局窗口类;但是,可以注册跟全局窗口类或系统窗口类同名的局部窗口类,这是由系统查找窗口类的顺序决定的。根据窗口类名创建窗口时,系统按照如下顺序定位到窗口类:
            1. 根据窗口类名从局部窗口类列表中查找 hInstance 与当前模块的 hInstance 一致的窗口类(程序的不同模块可以用相同的窗口类名来注册局部窗口类);
            2. 如果局部窗口类列表里没有要查找的窗口类名,从全局窗口类列表里查找;
            3. 如果全局窗口类列表里没有要查找的窗口类名,从系统窗口类列表里查找;
            按照这个顺序,即使局部窗口类名跟全局窗口类或系统窗口类同名,系统依然能正确定位到局部窗口类。例如,可以注册一个 "Edit" 的局部窗口类,在模块内创建 "Edit" 窗口时,会使用局部窗口类的定义,而不是系统窗口类。
            
    hInstance:
        实例句柄,标识窗口类所在的模块,可以是进程的 hInstance, dll 的 hModule, 不能为 NULL;
        
    lpfnWndProc:
        窗口过程函数地址,窗口的功能由这个函数实现。
        
        对于从该类创建的窗口,系统会将所有消息交给此窗口过程函数处理。程序可以使用 SetWindowLong 来改变窗口类的过程函数,这个操作叫做 “子类化(SubClassing)”, 稍后将具体讲述这一操作。
        
    style:
        style 成员决定了从该类创建出来的窗口的风格,可以使用下列值的组合:
            CS_OWNDC,CS_CLASSDC,CS_PARENTDC:
                这几个标志决定窗口的默认 DC(device context):
                1. 如果使用 CS_OWNDC 标志,属于此窗口类的所有窗口实例都由自己的 DC(称为私有 DC), 所以程序只需要调用一次 GetDC 或者 BeginPaint 获取 DC, 系统就为窗口初始化一个 DC, 并且保存程序对其进行的改变。 ReleaseDC 和 EndPaint 函数不再需要了。当选择了CS_OWNDC,程序改变影射模式(Mapping Mode)的时候必须小心,当由系统擦除窗口的背景时,系统假定和默认其影射模式是 MM_TEXT. 如果私有DC的影射模式不一样,窗口被擦除的地方将不再可见。
                2. 如果使用CS_CLASSDC标志,所有属于该类的窗口实例共享相同的 DC(称为类DC). 类 DC 有一些私有 DC 的优点,而更加节约内存(因为不需要为每个窗口实例都分配800字节的DC空间了)。每个窗口实例都通过 GetDC 或 BeginPaint 得到设备上下文(DC)句柄,如果没有别的窗口需要该DC,不需要调用 ReleaseDC 或 EndPaint 释放 DC. 在一个窗口实例上通过 GetWindowDC, GetDC, GetDCEx, BeginPaint 获得 DC,并对其中的一些参数进行更改的话,所进行的更改除了剪切区域和设备本身属性(Device origin)之外对所有其他窗口实例都是有效的。和 CS_OWNDC 相同的是,必须确保影射模式也是 MM_TEXT, 否则,被系统擦除的背景将不再可见。为NT编写的程序最好不要使用这个标志,因为“节约内存”的好处根本不明显。
                3. 如果使用 CS_PARENTDC 标志, 属于这个类的窗口都使用它的父窗口的句柄。和 CS_CLASSDC 相似的是,多个窗口共享一个 DC, 不同的是,这多个窗口(虽然有父子关系并且共享 DC )并不要求都属于同一个窗口类。注意如果程序需要改变各子窗口的影射模式,那么最好不要用 CS_PARENTDC 标志,否则将很容易引起各子窗口影射模式的混乱,因为所有的子窗口都使用同一个 DC.
                4. 如果不指定 CS_OWNDC, CS_CLASSDC, CS_PARENTDC 这几个标志,此类的窗口使用一个通用 DC, 并置于 DC 缓冲里以供使用。通用 DC 在使用前获取,使用后释放,在 DC 获取的时候, DC 里的上下文按默认值初始化,除非当时该 DC 已经在窗口的 DC 缓冲里(比如没有调用 ReleaseDC 或 EndPaint 释放 DC ),这样的话 DC 的剪切边界和设备属性都不需要被重新初试化,可以节约一些时间。
                
                问题:这几个字段涉及到尚不了解的 DC 和映射模式等概念,存疑待考。
            CS_GLOBALCLASS:
                CS_GLOBALCLASS 是唯一一个针对类本身起作用而不是对某个窗口起作用的标志。系统将包含这种标志的窗口类作为应用程序全局类保存。
                
            CS_BYTEALIGNCLIENT, CS_BYTEALIGNWINDOW:
                字节对齐标志,据说没什么用了。
                
            CS_HREDRAW, CS_VREDRAW:
                CS_HREDRAW 标志表示当窗口的水平尺寸(宽度)改变的时候,重画整个窗口。 CS_VREDRAW 则是在窗口垂直尺寸(高度)改变时重画整个窗口。按钮和滚动条都有这两种风格。
                
                问题:什么情况下需要指定这两个标志?存疑待考。
            CS_NOCLOSE:
                如果指定了这个标志,则窗口上的关闭按钮和系统菜单上的关闭命令失效。
                
            CS_DBLCLKS:
                CS_DBLCLKS 标志使窗口可以检测到双击事件。窗口响应双击的细节如下:
                    WM_LBUTTONDOWN
                    WM_LBUTTONUP
                    WM_LBUTTONDOWN
                    WM_LBUTTONUP
                其实就是两个单击,而如果指定了 CS_DBLCLKS 标志,则系统想窗口依次发送如下消息:
                    WM_LBUTTONDOWN
                    WM_LBUTTONUP
                    WM_LBUTTONDBLCLK
                    WM_LBUTTONUP
                第二次的 WM_LBUTTONDOWN 被替换成了 WM_LBUTTONDBLCLK 
                注意,在上述序列中间可能会插入其他的一条或一些消息,所以这两个消息序列不一定是完全连续的。
                
                其实,在没有指定 CS_DBLCLKS 标志时,程序本身也可以检测到双击事件的。参见 MSDN 里 Dr.GUT 的 "Simulating Mouse Button Clicks" 文章,不过要有一些技巧. 一般的情况下,如果没有指定 CS_DBLCLKS, 在窗口的消息循环里将不会得到 WM_LBUTTONDBLCLK 消息。
                所有的标准窗口控件,对话框,桌面窗口类都默认拥有CS_DBLCLKS标志。第三方控件最好也加上此风格,以使其在对话框编辑器里可以正常工作。
            
            CS_SAVEBITS:
                菜单,对话框,下拉框都拥有 CS_SAVEBITS 标志。当窗口使用这个标志时,系统用位图形式保存一份被窗口遮盖(或灰隐)的屏幕图象。首先,系统要求显示驱动保存图象位数据,如果显示驱动本身的存储空间足够,保存操作成功, window 系统也可以使用这些保存的位数据。如果不够,系统将位数据在全局内存里以位图的方式保存,并且在 USE 模块局部堆里为每个窗口分配空间,保存一些事务数据(比如位图数据缓冲的大小)的结构。当程序使遮盖屏幕的窗口消失时,系统可以很快的从内存里恢复屏幕图象。
                CS_SAVEBITS 的效率本身是很难度量的。 CS_SAVEBITS 提高了“临时”窗口比如菜单,对话框,下拉框的性能。但是,存贮位信息的开销也是很明显的,尤其由系统代替显示驱动存储位信息的时候,系统承担了速度和存储开销。 使用 CS_SAVEBITS 的好处其实依赖于窗口遮盖的区域发生了什么事情,如果该区域相当复杂,需要重画很多的效果,那么,存储该区域可能比重画该区域要来的轻松,如果反之,该区域可以相当快速的重画,或者在被遮盖的时候还经常发生变化并且变化很显著,保存的方案反而影响了整体性能。
                
                问题:并不理解这部分的内容,可以结合 MSDN 来学习。
        以上 style 标记都是可选字段,视用途决定要使用哪些。如果窗口大小不改变、不响应双击操作、不被其他模块使用的话,设 style == 0 也是可以的。
    
    hIcon 和 hIconSm:
        窗口图标, hIcon 是大图标,展示在任务切换窗口(Alt+Tab), 大图标模式任务栏, explorer 中, hIconSm 是小图标,展示在窗口标题栏,小图标模式任务栏。
        窗口图标的尺寸必须符合一定大小,大小可以通过 GetSystemMetrics 函数指定 SM_CXICON, SM_CYICON, SM_CXSMICON, SM_CYSMICON 来获取大小图标的标准尺寸。
        可以通过 WM_SETICON 消息来修改图标,通过 WM_GETICON 消息来获取图标。
    
    hCursor:
        窗口默认鼠标指针。当设置了该值,当鼠标移入窗口区域时,系统将指针由系统默认形状变成所设置的指针形状。程序可以使用 LoadCursor 函数从标准系统指针库(比如 IDC_ARROW)或用户指定指针资源中获取指针句柄。程序可以通过 SetCursor 函数随时改变指针。如果 hCursor 的值未设置(设置为 NULL), 程序必须在鼠标指针移入窗口时进行设置,否则将使用系统默认的鼠标指针形状。
    
    lpszMenuName:
        窗口默认主菜单。如果创建窗口时没有显式指定菜单资源,将使用在这里指定的默认菜单。
        可以使用 MAKEINTRESOURCE 宏将资源里菜单的 ID 号转换为连续字符串赋值给该成员。
    
    hbrBackground:
        窗口背景颜色,类型为 HBRUSH, 可以将其赋值为一个画刷句柄,或者颜色值。如果是颜色值的话,必须使用下列标准系统颜色之一:
            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
        使用颜色赋值时,必须加 1 并强制转换为 HBRUSH 类型: wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        如果 hbrBackground 成员设置为 NULL, 程序必须在响应 WM_PAINT 的时候负责画背景。程序也可以响应 WM_ERASEBKGND 消息,或根据调用 BeginPaint 函数时填充的 PAINTSTRUCT 结构里的成员 fErase 的值类判断是否需要重画背景。
    
    cbClsExtra, cbWndExtra:
        cbClsExtra 指窗口类额外内存大小,这部分内存被该类创建的所有窗口共享;
        cbWndExtra 指窗口额外内存大小,该类创建的每个窗口各自拥有一段额外内存;
        额外内存的大小最多只能有 40 个字节。如果不需要的话,必须设为 0, 以防止系统错误分配内存。
        如果用类声明并注册一个对话框类型的窗口, cbWndExtra 的值必须设置为 DLGWINDOWEXTRA, 系统对话框管理器需要这么多的额外数据对对话框进行管理。
    
    
4. 窗口类的注册和注销
    注册窗口类使用 RegisterClass 或 RegisterClassEx 函数;
        参数:  WNDCLASS 或 WNDCLASSEX 结构体;
        返回值:ATOM 类型的原子值,据说这个值是跟每个窗口类一一对应的。
        注意:  RegisterClass 和 RegisterClassEx 都有 ANSI 和 Unicode 两种版本。如果使用 ANSI 版本如 RegisterClassA, 消息中的文本信息将是 ANSI 格式;如果使用 Unicode 版本如 RegisterClassExW, 消息中的文本信息将是 Unicode 格式。 
        
    注销窗口类使用 UnregisterClass 函数;
        参数:  lpszClassName, hInstance
        返回值:如果定位不到窗口类,或者仍然有窗口没有被销毁,将失败并返回非 0 值。
        注意:  调用 UnRegisterClass 之前,必须先销毁由该类创建出的所有窗口。
                dll 被卸载后, 这个 dll 所注册过的窗口类并不会被注销。
    
    
5. 如何使用窗口类,子类化、超类化是什么
    一旦注册了一个类,一般来说除了使用该类创建窗口之外就没有什么需要做的事情了。当然,如果需要访问该类信息,子类化,或者超类化该类,介绍一些方法就是有用的。
    类访问函数:
        GetClassInfoEx         获取窗口类信息,输入 hInstance 和 lpszClassName, 返回窗口类对应的 WNDCLASSEX 结构体。 也可以输入 atom
        GetClassLong           从 WNDCLASSEX 结构体中获取一个 long 类型数值, 输入 hWnd, nIndex, 返回窗口对应窗口类的 WNDCLASSEX 结构体中的某个字段的信息。 比如 GetClassLong(hWnd, GCL_STYLE) 可以获取窗口类的 styles
        GetClassLongPtr        跟上面那个函数的功能类似,只是以指针方式返回结果。比如 GetClassLongPtr(hWnd, GCLP_WNDPROC) 可以获取窗口类的过程函数地址
        GetClassName           获取某窗口所属窗口类的窗口类名, 输入 hWnd, 返回 lpClassName
        GetWindowLong          获取某个窗口的指定信息,以 long 类型返回,输入 hWnd, nIndex, 返回窗口的某个字段的信息,比如 GetWindowLong(hWnd, GWL_STYLE) 可以获取窗口的 styles
        GetWindowLongPtr       跟上面那个函数的功能类似,只是以指针方式返回结果。比如 GetWindowLongPtr(hWnd, GWLP_WNDPROC) 可以获取窗口对应窗口类的窗口过程函数
        SetClassLongPtr        替换窗口类 WNDCLASSEX 的某个字段
        SetClassWord           跟上面函数功能类似,不过貌似是给 16 位系统用的
        SetWindowLong          替换窗口的某个字段,输入 hWnd, nIndex, dwNewLong 可以替换指定字段
        SetWindowLongPtr       跟上面函数功能类似
    
    子类化:
        术语"子类化"(subclassing)描述的是用一个新的窗口过程代替原窗口过程。
        术语"实例子类化"(即子类化单个窗口)是指使用 SetWindowLong 函数改变某一个窗口实例的窗口过程。
        术语"全局子类化"(子类化整个窗口类)则是指使用SetClassLong改变整个类的默认窗口过程函数。
        在 32 位 windows  系统里,可能难于子类化另一个进程里的窗口或窗口类,一般来说,子类化都是发生于同一个进程里的("打破进程边界"的相关的主题本文没有涉及).
    
    超类化:
        术语"超类化"(Superclassing)指创建一个新的类,该类使用某个现存类的窗口过程,继承该类的基本功能,并可以在此基础上进行扩展。
    
    关于子类化和超类化的具体描述,请参阅 MSDN 里 "safe subclasing in Win32" 一文
        http://jdearden.gotdns.org/programming_windows_notebook/safe_subclassing_in_win32.html
    

参考链接:
https://msdn.microsoft.com/en-us/library/ms633574(v=vs.85).aspx
http://blog.csdn.net/vcbear/article/details/5988

 

posted on 2015-08-06 02:18  zuibunan  阅读(1829)  评论(0编辑  收藏  举报

导航