窗口相关概念和对应关系
目录
三个角度窗口含义
- 从应用程序开发员的角度
窗口就是图形用户界面句柄,以该句柄作为系统API的参数,就可以对窗口进行各种操作 - 从MFC程序员角度
窗口就是一个CWnd对象 - 从操作系统角度
窗口是一块普通内存块.该内存中保存了一个数据结构的信息(WNDCLASS).操作系统利用这个数据结构维护窗口状态
窗口类型
- 可重叠窗口
这种窗口是有标题栏,边框,客户区域的顶层窗口,它可以充当应用程序的主窗口,这类窗口还可以有窗口菜单,最大和最小化按钮及滚动条 - 弹出窗口
这类窗口具有可重叠窗口的全部特性,但其标题栏是可选的,这类窗口是在应用程序的主窗口中出现的临时窗口.对话框,消息提示框(也是一种对话框)是最定型的弹出窗口 - 子窗口
子窗口具有WS_CHILD风格.并限制在父窗口的客户区绘制.应用程序会经常用子窗口将父窗口划分为若干个功能区,多视图就是一个典型的例子,创建子窗口的方法是调用CreateWindowEx时,指定WS_CHILD作为窗口的风格,父窗口参数指向父窗口句柄(必须有)除了特殊指定,子窗口仅有一个客户区而没有其他特性
父窗口的某些行为会影响到子窗口.
- 父窗口销毁时候,会自动销毁它所有的子窗口,如果子窗口又有子窗口会有连锁反应,导致所有的子窗口都被销毁
- 子窗口总是相对于父窗口定位的.因此移动父窗口也会导致它的子窗口跟着移动
- 消息窗口
这类窗口只能发送或接收消息,™没有边框,Z次序,不能被枚举,不能接收广播消息.调用CreateWindowEx时将父窗口句柄设置为常量HWND_MESSAGE,就会创建一个消息窗口
此外,通过对已有的的窗口调用SetParent,并将HWND_MESSAGE传入hWndNewParent参数,也会把一个窗口转换为消息窗口.因为消息窗口不能被枚举,所以查找消息窗口的过程比较麻烦,这时要将FindWindowEx的hwndParent参数设置为HWND_MESSAGE - 兄弟窗口
共享同一个父窗口的多个子窗口叫兄弟窗口 - 活动窗口
活动窗口是应用程序的顶层窗口,也就是当前使用的窗口.只有一个顶层窗口可以是活动窗口,如果用户使用的是一个子窗口,Windows系统就激活与这个子窗口相应的顶层窗口
任何时候系统中只能有一个顶层窗口是活动的.用户通过单击窗口(或其中的一个子窗口)、使用ALT+TAB或ALT+ESC组合键来激活一个顶层窗口,应用程序则调用函数SetActiveWindow来激活一个顶层窗口 - 前台窗口/后台窗口
在Windows系统中,每一个进程可运行多个线程,每个线程都能创建窗口.创建正在使用窗口的线程称之为前台线程,这个窗口就称之为前台窗口.所有其它的线程都是后台线程,由后台线程所创建的窗口叫后台窗口
用户通过单击一个窗口、使用ALT+TAB或ALT+ESC组合键来设置前台窗口,应用程序则用函数SetForegroundWindow设置前台窗口.如果新的前台窗口是一个顶层窗口,那么Windows系统就激活它,换句话说,Windows系统激活相应的顶层窗口
窗口关系
父子关系
一个窗口由另一个窗口派生出来,那么被派生出来的窗口为子窗口,而派生窗口的窗口为父窗口
相关函数
- 通过调用SetParent可以应用程序可以再运行时,对一个已有的窗口改变其父窗口
(如果SetParent中传入NULL,那么桌面将成为新窗口的父窗口.HWND SetParent( HWND hWndChild, // handle to window HWND hWndNewParent // new parent window );
- 通过调用GetParent获得父窗口
HWND GetParent( HWND hWnd // handle to child window );
- 通过调用IsChild可以判断一个给定的窗口是否为某个窗口的后代窗口
BOOL IsChild( HWND hWndParent, // handle to parent window HWND hWnd // handle to window to test );
- 通过EnumChildWindows提供了枚举起始窗口所有后代窗口
BOOL EnumChildWindows( HWND hWndParent, // handle to parent window WNDENUMPROC lpEnumFunc, // callback function LPARAM lParam // application-defined value );
BOOL CALLBACK EnumChildProc( HWND hwnd, // handle to child window LPARAM lParam // application-defined value );
Z次序关系
窗口的Z次序表明了重叠窗口堆中窗口的位置,这个窗口堆是按一个假想的轴定位的,这个轴就是从屏幕向外伸展的Z轴.Z次序最上面的窗口覆盖所有其它的窗口,Z次序最底层的窗口被所有其它的窗口覆盖.应用程序设置窗口在Z次序中的位置是通过把它放在一个给定窗口的后面,或是放在窗口堆的顶部或底部.
Windows系统管理三个独立的Z次序——一个用于顶层窗口、一个用于兄弟窗口,还有一个是用于最顶层窗口.最顶层窗口覆盖所有其它非最顶层窗口,而不管它是不是活动窗口或是前台窗口.应用程序通过设置WS_EX_TOPMOST风格创建最顶层窗口.
一般情况下,Windows系统把刚刚创建的窗口放在Z次序的顶部,用户可通过激活另外一个窗口来改变Z次序;Windows系统总是把活动的窗口放在Z次序的顶部,应用程序可用函数BringWindowToTop把一个窗口放置到Z次序的顶部.函数SetWindowPos和DeferWindowPos用来重排Z次序.
系统假定用户由Z轴从上往下看窗口,这样Z次序较低的窗口就可能被其他Z次序较低的窗口覆盖,Z次序最低的窗口是桌面,Z次序最高的窗口,我们把它称为顶层窗口.顶层窗口总是可见的
Windows通过一个全局队列维持窗口的Z次序.添加窗口时,它按照下面的规则设定窗口的Z次序:如果待添加的窗口是WS_EX_TOPMOST风格,那么它将会变为顶层窗口,也就是位于所有现有窗口的顶端.这一点跟待添加的窗口是否为活动或后台窗口无关:如果待添加的窗口为子窗口,则它拥有和父窗口相同的Z次序,对于其他窗口则放在同类型窗口的顶部,当一个窗口位于顶部时,它所拥有的全部子窗口也位于Z次序的顶部
父窗口拥有的每个子窗口的Z次序不同的,这些子窗口按照Z次序从上到下排列.
示例
一个程序
多个程序
相关函数
- 通过调用BringWindowToTop可将一个窗口带到相同窗口的顶端
BOOL BringWindowToTop( HWND hWnd // handle to window );
么拥有它的父窗口会被激活,注意该函数只能改变同种类型也就是同一个CLASS衍生的窗口的Z次序 - 通过GetTopWindow可以获得Z次序最高的子窗体
HWND GetTopWindow( HWND hWnd // handle to parent window );
- 通过调用GetNextWindow获得当前窗口的上一个或下一个子窗口
HWND GetNextWindow( HWND hWnd, // handle to current window UINT wCmd // direction );
前后关系
前后就是前台和后台的简称.当前正在执行的线程被称为前台线程,该线程创建的窗口被称为前台窗口;所有其他线程被称为后台线程,而这个线程创建的窗口就是后台窗口
相关函数
- 通过调用GetForegroundWindow可以获得当前的前台窗口
HWND GetForegroundWindow(VOID);
- 通过调用SetForegroundWindow设置前台窗口
BOOL SetForegroundWindow( HWND hWnd // handle to window );
拥有关系
拥有关系不同于父子关系,它的关系在于WS_OVERLAPPED和WS_POPUP风格的窗口之间.也就是说一个重叠或者弹出串口能被另一个重叠或者弹出窗口所拥有,这种关系一旦建立起来就不能更改
特点
- 它总是位于它的拥有窗口的Z次序之上
- 当窗口被销毁时,它所拥有的窗口也将被自动销毁
- 当窗口最小化时,它所拥有的窗口被隐藏
- 并且在创建它时候CreateWindow中的父窗口句柄为拥有窗口
相关函数
通过GetWindow获得该窗口的拥有窗口
HWND GetWindow( HWND hWnd, // handle to original window UINT uCmd // relationship );
线程和窗口
Windows操作系统将一个应用程序的一次运行定义为一个进程.线程表示一条执行路径,Windows操作系统是一种所谓的分时操作系统,它将CPU资源划分为若干个时间片,在不同的时间片可以执行不同的线程.当前线程拥有独立的堆栈和寄存器状态,并可以和同一进程的其他线程共享进程中的代码和数据,一个进程只少拥有一个主线程,此外还可以有多个辅助线程 .而窗口只不过是窗口结构的一个实例,它是一个普通的GUI资源,窗口本身是由线程创建,窗口不会拥有线程,线程则拥有它所有创建的所有窗口.窗口和线程的联系是窗口包含了负责处理消息的窗口过程的代码,而线程执行这些代码.所以在窗体程序中线程对创建的窗口拥有所有权,这样,如果一个线程建立一个窗口后立即结束,操作系统会自动销毁线程拥有的所有窗口.
建立窗口的线程必须是为窗口处理所有的消息的线程,如果线程已经结束,不可能被用来使用窗口接收和处理消息.这也意味着每个线程,如果它至少建立了一个窗口,都应由系统对它分配一个容纳处理的消息队列.这个队列用于窗口消息发送.这个队列就是线程的消息队列,为了使窗口接收这些消息,线程必须有它自己的消息循环
Windows设计目标之一是提供一个强健的应用程序应用环境.为实现这个目标,要保证每个线程运行在一个相对独立的环境中,在这个环境中每个线程都相信自己是唯一运行的线程.说的更具体些,就是每个线程必须有完全不受其他线程影响的消息对列.而且每个线程必须有一个虚拟环境,使线程维持它自己的键盘焦点,窗口激活,鼠标捕获等
为了减少线程对系统资源的要求,当一个线程第一次被建立时,系统假定线程不会用于任何与用户相关的任务,这样就不会创建消息队列,但是,一旦这个线程调用一个与图形用户界面有关的函数,系统就会为该线程奉陪一个THREADINFO结构,并将这个数据结构域线程联系起来,THREADINFO结构中包含一组成员变量,特别地,该结构中包含了该线程的消息队列集合.利用这组成员,线程可以认为它是在自己独占的环境中运行
除了为每个线程维持一个消息队列外,系统还维持一个全局的消息队列,用于容纳各种硬件输入消息的系统硬件输入队列,当系统初始化时,要建立一个特殊的线程,即原始输入线程(Raw Input Thread,RIT)同时建立系统硬件输入队列(System Hardware Input Queue,SHIQ)
原始输入线程按照下面的方法确定往哪个线程的的消息队列里增加硬件输入消息.对鼠标消息,原始输入线程只是确定哪一个窗口在鼠标光标之下.利用这个窗口,原始输入线程调用GetWindowThreadProc essId来确定哪个线程建立了这个窗口.返回的线程ID指出哪个线程应该得到这个鼠标消息
对按键事件事件稍微不同,在任何给定的时间,只有一个线程同原始输入线程"连接".这个线程就是前台线程.因为它建立了正在于用户交互的窗口,当切换前台窗口时,原始输入线程将自动切换和当前的前台线程的连接