说明:这是本人2008年写的一篇旧文,从未公开发表过。其中除了一小段描述Window Mobile平台的内容已过时,大部分内容对于从事Win32开发的程序员还是很有参考价值的,也是对自己从事Windows开发工作的一个总结,欢迎指正。转载请注明:http://www.cnblogs.com/dhatbj/原创。

范围(Scope

讨论Windows操作系统中窗口之间的关系(relationship between windows),除特别指明的部分之外,适用于各版本桌面平台和Windows Mobile平台。

概述(Summary

窗口(Window)是Windows操作系统中用来显示信息和接受用户输入的基本单元(Block)。负责管理窗口相关功能的操作系统部件被称为窗口管理器(Window Manager)。Windows操作系统初始化时会生成一个窗口,叫做桌面窗口(Desktop Window),调用GetDesktopWindow函数可获得它的句柄。桌面窗口会覆盖整个屏幕,所有其它窗口都在其之上显示。

 

窗口类型(Window Type

Windows中有3种类型的窗口:层叠窗口(Overlapped Window)、弹出窗口(Popup Window)、子窗口(Child Window),在生成窗口(调用CreateWindowEx)时分别以WS_OVERLAPPED、WS_POPUP或WS_CHILD窗口风格(Style)来表示。层叠窗口是窗口的缺省类型,如果不指定任何窗口类型则生成的是层叠窗口。

弹出窗口通常用于对话框。它隐含带有WS_CLIPSIBLINGS窗口风格(后面会详细描述)。

层叠窗口通常被用作应用程序的主窗口,也隐含带有WS_CLIPSIBLINGS窗口风格。在桌面平台上,层叠窗口还隐含带有WS_CAPTION窗口风格。带有标题栏的窗口都隐含带有边框(Border),至于原因嘛,想像一下“光秃秃的标题栏”+“没有边框的窗口”会是个什么样子。在Mobile平台上,层叠窗口与弹出窗口的界限已经很模糊了。

  层叠窗口和弹出窗口统称为顶层窗口(top-level windows)。

剩下的一类是子窗口,例如常见的Button,Edit Box,List Box等窗口控件。

WS_OVERLAPPED的值

在桌面平台上,WS_OVERLAPPED定义为0,这与窗口的缺省类型为层叠窗口的事实相符;而在Windows Mobile平台上,WS_OVERLAPPED被定义为WS_BORDER | WS_CAPTION,这是怎么回事呢?我想这是微软为了保持桌面平台与Mobile平台软件的外观兼容性而使用的一个技巧,因为Mobile平台上的层叠窗口缺省是不带WS_CAPTION风格的,微软的意思应该是:(WS_OVERLAPPED in PC)=(WS_OVERLAPPED in Mobile)| WS_BORDER | WS_CAPTION,在字面上就会写成:

#define WS_OVERLAPPED         WS_BORDER | WS_CAPTION

这样定义可以方便桌面平台上的代码移植到Mobile平台。但开发原生的Windows Mobile代码时就要注意了,由于Mobile上的典型窗口是不带标题栏的(Mobile界面最上方的Title Bar并不属于窗口的一部分),我们在生成层叠窗口时不应使用WS_OVERLAPPED标志(这一标志的实际意义是:PC style overlapped window)— 不指定任何窗口类型就好。

窗口层次结构(Window Hierarchy

窗口管理器以一个树状结构组织和管理系统内所有窗口,如图:

图1.树状的窗口组织图

树形结构的根是桌面窗口,其下属第一层窗口是顶层窗口(层叠窗口+弹出窗口,见上一小节)。顶层窗口之下的所有层里只包含子窗口。从桌面窗口出发,通过一系列相关API函数的调用,可以遍历系统中的所有窗口。

窗口的从属关系 

包括父/子(parent-child)关系、拥有/被拥有(owner-owned)关系及兄弟(siblings)关系。

  父/子(parent-child)关系 

类型为Child Window的窗口必须有一个父窗口,父窗口的类型可以是3种类型中的任意一种。子窗口的位置坐标都是相对于父窗口客户区的左上角(upper-left corner)计算的。子窗口会把它的notify消息发送到父窗口。父/子关系对窗口可见性的影响为:子窗口只能显示在它的父窗口的客户区中,超出父窗口客户区的部分将被裁减掉;父窗口被隐藏时,它的所有子窗口也被隐藏;最小化父窗口不影响子窗口的可见状态,子窗口会随着父窗口被最小化,但是它的WS_VISIBLE属性不变。父窗口被销毁的时候,它的所有子窗口都会被销毁。

窗口生成时通过CreateWindowEx函数的hWndParent参数可指定其父窗口,或在窗口产生后通过SetParent函数更改。通过GetParent函数可获取父窗口句柄。父窗口要查询其子窗口可使用GetWindow函数(指定GW_CHILD标志),该函数返回第一个子窗口的句柄。

桌面窗口与所有顶层窗口也是父/子关系,但又有其特殊性:对桌面窗口调用GetWindow函数(指定GW_CHILD标志)可得到第一个顶层窗口的句柄,但对某一个顶层窗口调用GetParent却会返回NULL,不会返回桌面窗口的句柄。由此可见,父/子关系中的“子”一方未必一定是子窗口(Child Window类型)。

  拥有/被拥有(owner-owned)关系

顶层窗口之间可以存在owner-owned关系。owner-owned关系对窗口可见性的影响为:owned窗口永远显示在owner窗口的前面;当owner窗口最小化的时候,它所拥有的窗口都会被隐藏;隐藏owner窗口不影响它所拥有的窗口的可见状态。根据最后这一点,如果窗口A 拥有窗口B,窗口B拥有窗口C,则当窗口A最小化的时候,窗口B被隐藏,但是窗口 C还是可见的。当owner窗口被销毁的时候,它所拥有的窗口都会被销毁。

Owner窗口在owned窗口生成时通过CreateWindowEx函数的hWndParent参数指定,如果该参数传入的是一个子窗口,窗口管理器将找到容纳该子窗口的顶层窗口,以该顶层窗口作为owner窗口。Owner窗口一旦指定不能更改。通过GetWindow函数(指定GW_OWNER标志)可获取owner窗口的句柄(如果存在的话)。

  兄弟(siblings)关系 

同一个父窗口的所有直属子窗口之间是兄弟关系,也就是相互平等,没有主从之分。窗口管理器用链表(linked list)来管理每个父窗口的直属子窗口(见图1),这个链表叫子窗口链(child window list)。

调用GetWindow函数时使用GW_HWNDPREV或GW_HWNDNEXT标志可访问子窗口链中的前一个或后一个窗口;使用GW_HWNDFIRST或GW_HWNDLAST标志可访问子窗口链中的第一个或最后一个窗口。

Z-Order 

窗口在子窗口链中的先后顺序也就是窗口在屏幕上显示时的前后顺序,在子窗口链里位置越靠前的窗口显示时也越靠前,这个前后顺序就是Z-Order。Z-Order在前的顶层窗口会遮挡Z-Order在后的顶层窗口;屏幕上的一块区域需要刷新(Update)时,同一个子窗口链中Z-Order在前的窗口先刷新,Z-Order在后的窗口后刷新。有父/子关系的窗口是父窗口先刷新,子窗口后刷新,

  顶层窗口生成时,窗口管理器会把它加到(桌面窗口的)子窗口链的最前面,也就是Z-Order的最前面,使整个窗口都可见。子窗口的Z-Order要高于它的父窗口,因此会显示在父窗口前面,但任何一个子窗口的Z-Order都不会超过其父窗口的Z-Order更靠前的兄弟窗口。改变窗口的Z-Order可使用SetWindowPos函数。

子窗口生成时,与顶层窗口的情况有所不同,窗口管理器会把它加到父窗口的子窗口链的最后面。这似乎是反直觉的,为什么会这样呢?窗口管理器这样做是有原因的,其主要目的是让后生成的窗口能显示在前面(兄弟窗口间有重叠的情况下),并且子窗口间的Tab-Order与窗口的生成顺序相同,这样的效果才是符合直觉的。子窗口大多数情况下都共用其父窗口的显示DC(Device Context),所以在刷新时是可以在其兄弟窗口的客户区上绘画(draw)的,这就造成了Z-Order在后的子窗口因为刷新顺序在后,绘画能覆盖Z-Order在前的窗口,显示效果反而在前的现象,如下图所示:

图2.子窗口相互覆盖示意图(无WS_CLIPSIBLINGS风格)

如果想使Z-Order在前的子窗口显示时也在前(覆盖Z-Order在后的子窗口),需要使用WS_CLIPSIBLINGS窗口风格(后面详述)。

Topmost窗口

也就是具有WS_EX_TOPMOST扩展风格的窗口,仅适用于顶层窗口,子窗口无法使用。根据有无WS_EX_TOPMOST风格将所有顶层窗口分成了两个级别。有WS_EX_TOPMOST风格的顶层窗口Z-Order在前,普通顶层窗口Z-Order在后。普通顶层窗口要成为Topmost窗口可以调用SetWindowPos函数指定hwndInsertAfter参数为HWND_TOPMOST,指定hwndInsertAfter参数为HWND_NOTOPMOST调用SetWindowPos则使Topmost窗口成为普通顶层窗口。

Tab-Order

兄弟窗口间的Tab-Order实际上是由Z-Order决定的(与Z-Order的顺序相同),因此如果想在程序运行过程中动态改变Tab-Order,可通过改变兄弟窗口间的Z-Order(使用SetWindowPos)来实现。反过来,在VC++的对话框编辑器里,如果控件的位置有重叠,通过调整Tab-Order也能调整控件间的遮挡关系。

WS_CLIPCHILDRENWS_CLIPSIBLINGS

       在窗口之间有重叠的情况下,这两个窗口风格会影响窗口刷新区域(Update Region)的计算方法。“Clip”一词是指从刷新区域中“剪切掉”被Z-Order在前的子窗口或兄弟窗口覆盖的区域,如图3所示:

图3. 窗口之间有重叠时的刷新区域

窗口B与窗口A是兄弟关系,B的Z-Order高于A。整个屏幕刷新时,B先刷新,A后刷新。如果A没有WS_CLIPSIBLINGS风格,则A的整个客户区都被刷新,A将重新刷新两窗口相交的C区域,造成A覆盖B的效果;但如果A有WS_CLIPSIBLINGS风格,则只有图中绿色的部分会被刷新,A就不会覆盖B。简单的说,WS_CLIPSIBLINGS可以控制Z-Order在前的窗口显示在前还是Z-Order在后的窗口显示在前。由于窗口管理器强制顶层窗口都有WS_CLIPSIBLINGS风格,所以顶层窗口总是Z-Order在前的显示在前(能覆盖Z-Order在后的窗口)。子窗口缺省不带WS_CLIPSIBLINGS风格,所以是Z-Order在后的窗口能覆盖Z-Order在前的兄弟窗口(如图2)。要想使子窗口的遮挡效果与Z-Order一致,可以把相关的子窗口都加上WS_CLIPSIBLINGS风格,之后的外观如下图:

图4.子窗口相互覆盖示意图(有WS_CLIPSIBLINGS风格)

由于父窗口总是先于子窗口执行刷新动作,所以无论是否使用WS_CLIPCHILDREN风格,父窗口都不会遮盖子窗口。WS_CLIPCHILDREN的作用主要是避免窗口重叠区域的重复刷新,有可能加快窗口显示速度以及减轻刷新时的“闪烁”问题。

窗口生成时使用缺省位置和大小 

       CreateWindowEx生成的窗口如果是层叠窗口,程序可以不指定窗口的初始位置和大小,而是由窗口管理器决定。

要让窗口管理器设置窗口的初始位置,需要使用一个特殊值CW_USEDEFAULT作为CreateWindowEx的参数“x”的值,参数“y”将被忽略,不起作用。对于桌面平台,如果窗口风格包含WS_VISIBLE,CreateWindowEx 函数内部将把“y”的值作为第二个参数(nCmdShow)传递给ShowWindow函数,这时的“y”作为一个隐藏参数使用。

要让窗口管理器设置窗口的初始大小,需要使用CW_USEDEFAULT作为CreateWindowEx的参数nWidth的值,参数nHeight将被忽略。

CW_USEDEFAULT只能用于层叠窗口,对于弹出窗口或子窗口,如果给“x”参数传递了CW_USEDEFAULT,则窗口位置不确定;如果给nWidth参数传递了CW_USEDEFAULT,则窗口大小不确定。

参考资料

1.    Win32 Window Hierarchy and Styles,Kyle Marsh,1993

http://msdn.microsoft.com/en-us/library/ms997562.aspx

窗口之间的主从关系与Z-Order的更多相关文章

  1. [转载]窗口之间的主从关系与Z-Order

    窗口之间的主从关系与Z-Order 原文地址:http://www.cnblogs.com/dhatbj/p/3288152.html说明:这是本人2008年写的一篇旧文,从未公开发表过.其中除了一小 ...

  2. 【转】窗口之间的主从关系与Z-Order

    原文链接:http://www.cnblogs.com/dhatbj/p/3288152.html 说明:这是本人2008年写的一篇旧文,从未公开发表过.其中除了一小段描述Window Mobile平 ...

  3. 自定义经纬度索引(非RTree、Morton Code[z order curve]、Geohash的方式)

    自定义经纬度索引(非RTree.Morton Code[z order curve].Geohash的方式) Custom Indexing for Latitude-Longitude Data 网 ...

  4. 简析Window、Activity、DecorView以及ViewRoot之间的错综关系

    一.职能简介 Activity Activity并不负责视图控制,它只是控制生命周期和处理事件.真正控制视图的是Window.一个Activity包含了一个Window,Window才是真正代表一个窗 ...

  5. Prism 文档 第三章 管理组件之间的依赖关系

                                                                          第3章:管理组件之间的依赖关系 基于Prism库的复合应用程 ...

  6. Python+Selenium练习篇之19-多窗口之间切换

    本文来介绍如何处理driver在多窗口之间切换,想一下这样的场景,在页面A点击一个连接,会触发在新Tab或者新窗口打开页面B,由于之前的driver实例对象在页面A,但是你接下来的脚本是操作页面B的元 ...

  7. 高强度学习训练第六天总结:Redis主从关系总结

    Redis主从复制机制 1.读写分离的好处 性能优化:主服务器专注于写操作,可以更适合写入数据的模式工作:同样,从服务器专注于读操作,可以用更适合读取数据的模式工作. 强化数据安全,避免单点故障:由于 ...

  8. Host–Parasite(主从关系): Graph LSTM-in-LSTM for Group Activity Recognition

    This article aims to tackle the problem of group activity recognition in the multiple-person scene. ...

  9. Angular2入门系列教程3-多个组件,主从关系

    上一篇 Angular2项目初体验-编写自己的第一个组件 好了,前面简单介绍了Angular2的基本开发,并且写了一个非常简单的组件,这篇文章我们将要学会编写多个组件并且有主从关系 现在,假设我们要做 ...

随机推荐

  1. 每天一个linux命令(44):top命令

    top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器.下面详细介绍它的使用方法.top是一个动态显示过程,即可以通过用户按键来不断刷新 ...

  2. java中string内存的相关知识点

    (一):区别java内存中堆和栈: 1.栈:数据可以共享,存放基本数据类型和对象的引用,其中对象存放在堆中,对象的引用存放在栈中: 当在一段代码块定义一个变量时,就在栈中 为这个变量分配内存空间,当该 ...

  3. 《Qt Quick 4小时入门》学习笔记

    http://edu.csdn.net/course/detail/1042/14804?auto_start=1   Qt Quick 4小时入门 第五章:Qt Quick里的信号与槽   QML中 ...

  4. 如何在 ETL 项目中统一管理上百个 SSIS 包的日志和包配置框架

    一直准备写这么一篇有关 SSIS 日志系统的文章,但是发现很难一次写的很完整.因为这篇文章的内容可扩展的性太强,每多扩展一部分就意味着需要更多代码,示例和理论支撑.因此,我选择我觉得比较通用的 LOG ...

  5. CSS学习目录

    前面的话 CSS是前端工程师的基本功,但好多执迷于学习javascript的人的基本功并不扎实.可能一些人从w3school网站匆匆过了一遍,只是对CSS常用概念有一些表面上的理解,就一头扎进java ...

  6. Spark入门实战系列--1.Spark及其生态圈简介

    [注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .简介 1.1 Spark简介 年6月进入Apache成为孵化项目,8个月后成为Apache ...

  7. Linux一块网卡添加多个IP地址

    环境: RHEL6.4 需求: Linux一块网卡添加多个IP地址 一.临时生效 1.1 网卡eth0添加一个IP地址 1.2 修改eth0:0的广播地址 二.永久生效 2.1 编辑ifcfg-eth ...

  8. Nodejs学习笔记(四)——支持Mongodb

    前言:回顾前面零零碎碎写的三篇挂着Nodejs学习笔记的文章,着实有点名不副实,当然,这篇可能还是要继续走着离主线越走越远的路子,从简短的介绍什么是Nodejs,到如何寻找一个可以调试的Nodejs ...

  9. Cordova webapp实战开发:(3)后面可能会学到的东西

    在<Cordova webapp实战开发:(2)认识一下Cordova>中我们了解了Cordova和Phonegap的关系,并简要介绍了一下它的架构,以及多平台性,并给大家留了一些作业.我 ...

  10. c#实现分组服务器,单一无重复生成ID

    class Program { static void Main(string[] args) { List<Thread> threads = new List<Thread> ...