看名字( Window )就知道,窗口是 Windows 编程开发的核心,但是什么是窗口?你的大脑中可能浮现如下画面:

Windows 窗口

这种类型的窗口叫做应用程序窗口(application window)或者主窗口( main window)。典型的主窗口框架通常包括标题栏、最小化按钮和最大化按钮以及一些其它的 UI 组件。这个框架本身叫做窗口的非客户区(non-client area)。

Windows 操作系统负责管理非客户区的响应操作,例如拖拽,改变大小,最大化最小化等等。框架之外剩余的区域,叫做客户区(client area),这部分是由程序自身负责管理的。

下面是另一种类型的窗口:

Button 按钮

你可能惊讶于 UI 控件也是一种窗口。UI 控件的类型很多,包括按钮、编辑框、下拉列表等等。 UI 控件一般不能单独存在,大多都是依附于窗口之上。

当你拖拽一个窗口的时候,窗口上的 UI 控件一样会跟随移动,并保持在窗口的相对位置不变。控件和窗口之间是可以互相通讯的(例如,窗口可以接收到按钮的点击事件)。

知道上面的信息后,再次提及窗口的时候,你不应该简单的把窗口想象为典型的主窗口,而是要把它看做一种包含若干特点的数据结构:

  • 其占据屏幕的某个部分。
  • 在特定的时候可以隐藏。
  • 知道如何绘制自身。
  • 对来自用户或操作系统的事件能做成响应。

1. 父窗口(Parent Windows)和附属窗口(Owner Windows)

当存在 UI 控件的时候,控件窗口被认为是应用程序窗口的子窗口,应用程序窗口被认为是控件窗口的父窗口。通过父窗口的坐标系可以定位子窗口的位置,并且子窗口的样式等一些属性会受到父窗口的影响。例如,超出父窗口范围的子窗口默认会被裁剪掉。

除了父子关系另一种关系存在于应用程序窗口和模态窗口之间。当一个应用程序显示一个模态窗口,这个应用程序窗口被称为拥有者窗口(owner window),而这个模态窗口叫做被拥有者窗口(owned window),可以把模态窗口叫做应用程序窗口的附属窗口。

被拥有者窗口总是出现在应用窗口之前,当拥有者窗口最小化或者销毁的时候,被拥有者窗口会自动隐藏。

下图显示一个应用程序窗口和一个包含两个按钮的模态对话框窗口。

模态对话框窗口

这个应用程序窗口拥有这个对话框窗口,而对话框窗口是两个按钮的父窗口,整个关系如下图所示:

窗口关系

2. 窗口句柄

一个窗口可以看做一个对象,虽然包含数据和代码,但并不是 C++ 对象。它们使用句柄来间接的操作一个窗口,句柄的类型是不可见的。

句柄本质上就是一串数字,一串被操作系统用区分对象的数字。你可以想想 Windows 系统维护这一张巨大的表,表格的每一项代表一个窗口,而句柄就是表格项的索引,通过句柄可以查找到与之对应的项。

窗口句柄的数据类型是 HWND,窗口句柄一般通过创建窗口的函数返回:CreateWindow 或者 CreateWindowEx。

如果你想窗口执行某些操作,窗口句柄一般会作为API接口的参数传入。例如,想要在屏幕中移动某个窗口:

BOOL MoveWindow(HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint);

这里第一个参数就是要移动的窗口句柄,剩余的参数代表窗口新的位置以及是否需要重新绘制。

句柄不是指针,如果变量 hwnd 代表一个窗口句柄,传入的时候则将其直接传入,而非传入 *hwnd。

3. 屏幕坐标和窗口坐标

坐标是用像素表示的,这里的像素(pixels)一般是独立于设备的,术语用 device-independent pixels 表示,其实说简单一些就是这些像素是抽象的,独立于设备之外的,以后的文章会仔细讨论这部分内容的含义。

根据需求的不同,坐标系可以相对于屏幕建立,也可以相对于窗口(包含框架)建立,还可以相对于窗口客户区建立。对于同一个物体虽然位置不变,但对于不同的坐标系,表示的坐标位置是不同的。

如下图所示,同一个(0,0)点坐标,对于不同坐标系,表示对位置是不同的。

窗口坐标系