zoukankan      html  css  js  c++  java
  • 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_MACHINESoftwareMicrosoftWindows NTCurrentVersionWindowsAPPINIT_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
  • 相关阅读:
    day38 04-Spring加载配置文件
    day38 03-Spring的IOC和DI的区别
    day38 02-Spring快速入门
    关于mysql-connector-net在C#中的用法
    SQL的四种连接用法整理
    SQL的四种连接用法整理
    SQL的四种连接用法整理
    45道CSS基础面试题
    45道CSS基础面试题
    45道CSS基础面试题
  • 原文地址:https://www.cnblogs.com/zuibunan/p/4706293.html
Copyright © 2011-2022 走看看