视窗

视窗是在使用者界面上的一个可见的范围。视窗一般都是长方形的。它包含着各种控件，是用作输入和输出的界面。虽然视窗一般用于图形使用者界面，但又有时用于命令行界面的。而一个使用视窗为主要用户界面的系统则称为视窗系统。这个想法源于施乐帕洛阿尔托研究中心。

在视窗系统上，视窗一般被画成一些二维的物件，被安置在桌面之上。大多数的视窗都可以随使用者的意愿而更改大小、移动、隐藏、回复和关闭。当两个视窗重叠的时候，就如日常生活中一样，其中一个要位于另一个之上，而下方的则会有些地方被上方个视窗所遮盖。而视窗系统中管理这个操作的部分，则叫做视窗管理员。

视窗是数种图形使用者界面中的一种重要的widget。当中有VMS的DEC Windows、GNU/Unix-like系统的X Window系统、Microsoft Windows和Open Windows。

## 历史

<image>

这个概念是由国际斯坦福研究所（由 道格拉斯·恩格尔巴特领导）开发实现的。他们最早的系统支持多窗口，但没明显的方法显示它们的分界线（比如窗口边框、标题栏等）。

研究在Xerox公司的Palo Alto研究中心/PARC（由 艾伦·凯领导）中继续。他们使用了重叠的窗口。

在1980年代，PARC提出术语"WIMP"，分别代表了窗口、图标、菜单、指针。

那时Apple与PARC进行了简单的合作。Apple根据PARC的界面开发了自己的新界面。它首次应用是在Apple Lisa上，此后用于Macintosh电脑。与此同时，Microsoft在为"Mac"开发办公软件。之后他们在Apple系统的基础上开发了自己的窗口系统。

## 子类化与超类化

窗口子类化是对一个窗口实例通过GetWindowLong和SetWindowLong将窗口过程地址修改为一个新函数地址，新的窗口过程函数处理那些感兴趣的消息，将其它消息传递给原窗口过程。即实例子类化。在某控件已经创建的情况下，为了获得窗口消息，必须子类化它。子类化不需要创建一个完整的新窗口类，只需要拦截单个窗口。

窗口超类化是一个已存在的窗口类（WNDCLASS或WNDCLASSEX），改变窗口类的特征。仅影响窗口类新创建的窗口实例，不会影响窗口过程地址修改前已经创建的窗口。超类化只能改变用户自创的窗口的特性，不能用于系统创建的窗口(如对话框上的标准按钮）。超类话过程示例如下：

```cpp
WNDCLASSEX wc;
wc.cbsize=sizeof(wc);
GetClassInfoEx(hinst,"XXXX",&wc); //hinst---定义窗口类XXXX的模块的句柄，如为系统定义的窗口类(如: Edit,Button)则hinst=NULL
wc.lpszClassName="YYYY";//必须改变窗口类的名字
wc.hbrBackground=CreateSolidBrush(RGB(0,0,0));
wc.lpfnWndProc=NewWndProc;//改变窗口过程
RegisterClassEx(&wc);
```

### MFC的子类化与超类化

MFC的CWindowImplBaseT::SubclassWindow用于动态地把一个窗口实例绑定到当前的CWnd对象，消息循环首先调用CWnd对象的窗口过程，传递给基类的消息将被默认的窗口过程处理。CWnd::SubclassDlgItem用于把对话框的一个控件子类化绑定到一个基于CWnd的类对象。CWindowImplBaseT::UnsubclassWindow恢复一个被子类化的窗口。

模板类CWindowImplBaseT提供一个数据成员WNDPROC m_pfnSuperWindowProc并且初始化为 ::DefWindowProc.然而在窗口超类化处理时它存储了已注册窗口类的窗口过程，在窗口子类化时它保存窗口实例句柄原有的窗口过程。

CWnd::CreateEx在创建窗口前调用SetWindowsHookEx函数安装了一个钩子函数_AfxCbtFilterHook。窗口刚创建好，钩子函数_AfxCbtFilterHook就被调用。_AfxCbtFilterHook调用SetWindowLong将窗口过程替换为AfxWndProcBase，并将SetWindowLong返回的原窗口地址保存到成员变量oldWndProc。可见，通过CWnd::CreateEx创建的所有窗口都会被子类化。在DefWindowProc函数中，消息会传给子类化时保存的原窗口地址oldWndProc。

ATL提供DECLARE_WND_SUPERCLASS来支持超类化。

## Windows窗口类型

使用Windows API函数CreateWindowEx，可以指定创建的窗口的类型：

- 层叠窗口 Overlapped Window：一种顶层（top-level)窗口，作为应用程序的主窗口。有标题条、边界与客户区，可以有菜单、最大最小化按钮、滚动条、可改变窗体尺寸的边界（sizing border）。使用CreateWindowEx函数指定WS_OVERLAPPED 或 WS_OVERLAPPEDWINDOW风格。
- 弹出窗口 Pop-up Window：另一种顶层窗口，用于对话框、MessageBox，以及其他出现在应用程序主窗口之外的临时窗口。标题条可选，除此以外与层叠窗口相同。使用CreateWindowEx函数，指定WS_POPUP风格。如果需要标题条，指出WS_CAPTION风格。使用WS_POPUPWINDOW风格有border与window菜单。弹出窗口可以拥有其他顶层窗口；顶层窗口也可以拥有弹出窗口；前述二者可以同时成立。弹出窗口总是有WS_CLIPSIBLINGS风格。弹出窗口的尺寸或定位参数不能使用CW_USEDEFAULT，否则尺寸、定位被设为0。
- 顶层窗口 top-level window：就是“非Child window”。其parent恒定是Desktop。总是有WS_CLIPSIBLINGS风格。
- 子窗口 Child Window：出现在父窗口（parent window）的客户区中，超出的部分不可见。子窗口一定有Parent，一定没有Owner。非子窗口的Parent一定是桌面，它们不一定有Owner。典型用于把父窗口的客户区分割成一些子区域。使用CreateWindowEx函数指定WS_CHILD风格。父窗口可以是Overlapped、Pop-up、其他子窗口。子窗口的唯一必要特性是有客户区；不能有菜单；其他特性可选。定位子窗口时总是用父窗口客户区左上角坐标系。Destroyed、Hidden、Moved、Shown等窗口操作与父窗口一致。父窗口如果指定了WS_CLIPCHILDREN风格，则父窗口不能在子窗口上绘制；否则重绘父窗口会覆盖子窗口的内容。兄弟子窗口（sibling window）可以在彼此重叠的客户区上绘制，除非指定了WS_CLIPSIBLINGS风格，则不论子窗口之间z-order，与其他子窗口重叠区域不被绘制。不能使用CW_USEDEFAULT，否则将没有尺寸、没有定位位置。子窗口的输入消息直接发给子窗口，并不传递给父窗口。但子窗口被函数EnableWindow 禁止时，系统把子窗口的输入消息发给父窗口。在子窗口上点击鼠标，子窗口收到WM_LMOUSEBUTTONDOWN消息；如果主窗口没有键盘输入焦点（focus），则主窗口获得输入焦点（WM_SETFOCUS）；如果兄弟子窗口有输入焦点，则当前子窗口获得输入焦点。子窗口有独一无二整数标识符。应用程序通过空间的整数标识符给它发消息。控件给父窗口发送notification消息时，用整数标识符来表示自身。
- Layered Window：带有WS_EX_LAYERED风格的窗口就是分层窗口，用于实现异形窗口和窗口整体透明。异形窗口要把控件的透明色和窗口后面的图像进行融合，需要用UpdateLayeredWindow函数来绘制。窗口整体透明，需要用SetLayeredWindowAttribute函数设置透明度。调用UpdateLayeredWindow函数后，WM_PAINT消息将失效；更新窗口需要自行调用UpdateLayeredWindow函数。子窗口无法应用WS_EX_LAYERED风格。子窗口如果需要是异形或者整体透明，只能去掉WS_CHILD风格，作为popup窗口，然后MOVE到一定位置，在父窗口移动的时候跟随移动。
- Message-Only Window，允许发送/接收Windows消息，不可见，没有z-order，不能被枚举，不接收广播消息。CreateWindowEx函数的参数hWndParent为HWND_MESSAGE常量或者一个已存在的message-only窗口的句柄。对已经存在的窗口，通过函数SetParent的参数hWndNewParent为HWND_MESSAGE，把该窗体修改为message-only。为搜到message-only窗口，调用函数FindWindowEx指定参数hwndParent为HWND_MESSAGE。此外，函数FindWindowEx搜索message-only窗口与顶层层口，如果参数hwndParent与hwndChildAfter都为NULL。
- Owner/Owned Window：Overlapped Window/Pop-up Window可以被其他Overlapped Window/Pop-up Window拥有。子窗口一定没有Owner。这种关系具有如下特性：在z-order上，Owned总是在Owner之上。当Owner被摧毁时，系统自动摧毁它的所有Owned。当Owner极小化时，Owned自动隐藏（没有了WS_VISIBLE），不显示在任务栏上；但Owned窗口自身拥有的窗口不会自动隐藏，即没有传递性。Owner隐藏（SW_HIDE），不会影响其拥有的窗口。CreateWindowEx函数创建具有WS_OVERLAPPED或WS_POPUP风格的窗口时，通过参数hwndParent指定Owner。对话框与消息框默认都是Owned。通过GetWindow函数指出GW_OWNER标志获取窗口的Owner的句柄。
- Owner Window：Owner window不能是Child window。Child window一定没有Owner，因此Owner window是对popup和Overlapped而言。popup和Overlapped不一定有Owner。Owner为NULL的非Child窗口可以在任务栏上出现它们的按钮。
- Owned Window
- top most window：具有“WS_EX_TOPMOST”属性。总是显示在其它非top most窗口的上面。同时是top most的窗口是“公平竞争”的关系。owner为top most，则其全部owned都变成了top most。child不能是top most窗口。

GetParent函数，对于子窗口返回其父窗口句柄；对于弹出窗口，返回拥有者窗口句柄；对于层叠窗口，返回0.

GetWindow函数，指出GW_OWNER参数，则可以返回弹出窗口或层叠窗口的拥有者窗口句柄。

FindWindowEx函数，用于找到指定父窗口、指定子窗口的下一个子窗口。

## Windows窗口类与窗口实例的额外存储空间

WNDCLASS中cbClsExtra和cbWndExtra，分别用于指定附加于窗口类与窗口实例的额外存储空间字节大小，不能超过40字节。分别类似于C++类中的static变量与类似于C++类中的成员变量。

GetClassLong(hwnd,GCL_CBCLSEXTRA)获取的是类附加空间的大小，即cbClsExtra的值。GetClassLong(hwnd,GCL_CBWNDEXTRA)获取的是窗口附加空间的大小，即cbWndExtra的值。

使用方法：

- 访问类附加空间的数据GetClassLong(hwnd,index)，index表示访问附加空间以0开始的索引。
- 设置类附加空间的数据SetClassLong(hwnd,index,value)，index表示访问附加空间以0开始的索引，value表示要设置的值。
- 访问窗口附加空间的数据GetWindowLong(hwnd,index)，index表示访问附加空间以0开始的索引。
- 设置窗口附加空间的数据SetWindowLong(hwnd,index,value)，index表示访问附加空间以0开始的索引，value表示要设置的值。

其它相关Windows API有：GetWindowWord、GetWindowLongPtr、SetWindowWord、SetWindowLongPtr。

## 窗口管理器

视窗系统中管理视窗的部分被成为"窗口管理器"

常见的窗口系统：

- X Window系统
- Microsoft Windows