Windows窗口
窗口类
每个窗口都必须和一个窗口类(非C++意义的类)相关联,窗口类在运行时向系统注册,注册窗口类使用WNDCLASS结构
:
1 | // Register the window class. |
其中:
- lpfnWndProc 是指向应用程序定义的函数(
窗口过程
)的指针,窗口过程定义窗口大部分的行为. - hInstance 是应用程序实例的句柄.
- lpszClassName 是标识窗口类的字符串.
类名需要在进程中唯一,且不能与标准Windows控件类名(例如Button)冲突.
其他成员可以填充为0.
然后将该结构变量的地址传递给RegisterClass
函数,来注册到操作系统:
1 | RegisterClass(&wc); |
创建窗口
使用CreateWindowEx
函数创建窗口的新实例.
1 | HWND CreateWindowEx( |
- dwExStyle 制定扩展窗口样式,例如透明窗口.默认设置为0即可.
- lpClassName 窗口类名,定义要创建的窗口的类型.
- lpWindowName 窗口名称,如果窗口样式指定标题栏,则其将显示在标题栏中.
- dwStyle 窗口样式,它是一组标志,用于定义窗口的一些外观.
- X 窗口初始的水平坐标,
CW_USEDEFAULT
表示使用默认值(仅对重叠窗口有效,如果为弹出窗口或子窗口指定,则X和Y参数设置为零). - Y 同上类似.
- nWidth,nHeight 窗口的宽度,高度,以设备单位为单位,
CW_USEDEFAULT
为默认值. - hWndParent 正在创建的窗口的父窗口或所有者窗口的句柄,对于顶级窗口,设置为NULL.
- hMenu 菜单句柄,或指定子窗口标识符,具体取决于窗口样式.可以设为NULL.
- hInstance 如前所述,为
实例句柄
. - lpParam
void*
的任意数据的指针,使用此值来将数据结构传递到窗口过程.
CreateWindowEx
返回新窗口的句柄,如果失败则返回0.
如果要显示窗口,则将返回的句柄传递给ShowWindow
函数:
1 | ShowWindow(hwnd, nCmdShow); |
其中nCmdShow可用于最小化或最大化窗口,操作系统通过wWinMain
函数将此值传递给程序(?)
消息循环
事件与消息
有两种事件
:
- 来自用户的事件: 用户与程序交互的所有方式:鼠标单击,击键等.
- 来自操作系统的事件:"程序外部"的任何可能影响程序行为方式的内容,例如插入新硬件设备等.
事件在程序运行的任何时间,几乎任何顺序发生,为了处理这样无法预测的事件,Windows使用消息传递模型
,即操作系统通过向应用程序窗口传递消息来与应用程序窗口通信,消息只是指定特定事件的数字代码.
例如当按下左键
时,窗口将收到如下消息代码:
1 |
某些消息具有与之关联的数据,例如鼠标光标的x,y坐标.
要将消息传递到窗口,操作系统会调用为窗口注册的窗口过程
.
获取消息与消息循环
应用程序在运行时会收到大量消息,同时应用程序可能会有多个窗口,每个窗口都有自己的窗口过程
,应用程序需要一个循环来检索消息并将其调度到正确的窗口.
对每个线程,操作系统都会为其消息窗口创建一个队列,用于保存在该线程上创建的所有窗口的消息.该队列本身对程序隐藏,使用GetMessage函数来操作该队列:
1 | BOOL GetMessage( |
其中:
-
lpMsg 为指向MSG结构的指针,该结构用于从消息队列中接受消息信息.
-
hWnd 要检索其消息的窗口的句柄,一般为NULL(检索属于当前线程的任何窗口的消息以及当前线程的消息队列上hwnd值为NULL的任何消息)—处理窗口消息和线程消息.
如果为-1,则仅检索当前线程的消息队列上hwnd值为NULL的任何消息或PostMessage发布的线程消息.
-
wMsgFilterMin 要检索的最低消息值的整数值.
-
wMsgFilterMax 要检索的最高消息值的整数值.
-
如果wMsgFilterMin和wMsgFilterMax均为零,则GetMessage将返回所有可用消息,即不执行范围筛选.
如果检索WM_QUIT以外的消息,返回非零;检索WM_QUIT返回零;出现错误则返回-1.
GetMessage函数从队列的头部删除一条消息,若队列为空则该函数阻塞(但不会使程序无响应),因此后台处理需要创建另一个线程
.
例如:
1 | MSG msg; |
几乎不需要手动检查MSG结构,只需要将其传递给如下函数即可:
1 | TranslateMessage(&msg); // 与键盘输入相关,将击键(上,下)转换为字符.在DispatchMessage之前调用 |
需要一个循环来不断从队列中拉取消息并调度它们,如果需要退出应用程序并中断消息循环,调用PostQuitMessage函数:
1 | PostQuitMessage(0); |
该函数将WM_QUIT消息置于消息队列中,导致GetMessage
返回零,标志着消息循环结束,例如:
1 | MSG msg = { }; |
已发布消息与已发送消息
操作系统有时会绕过消息队列直接调用窗口过程.
发布消息
意味着消息进入消息队列.发送消息
意味着消息跳过队列,操作系统直接调用窗口过程.
如果应用程序在窗口之间通信,则可能有所不同,详见 消息和消息队列.
编写窗口过程
如前所述,DispatchMessage
函数调用窗口的窗口过程
,该窗口是消息的目标.
窗口过程
有以下签名:
1 | LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); |
其中:
-
hwnd 是窗口的句柄.
-
uMsg 是消息代码.
-
wParam和lParam 包含与消息相关的其他数据,具体含义依赖于消息代码(数值/指针).对于每条消息,都需要根据消息代码并将其强转为正确的数据类型.
-
LRESULT 是程序返回到Windows的整数值,包含程序对特定消息的响应,具体含义同样取决于消息代码.
例如处理WM_SIZE
消息:
1 | LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) |
LOWORD
和`HIWORD是2个宏,用于获取16位数据(参考MSDN文档).
如果不在窗口消息中处理特定消息,请将消息参数直接传递到DefWindowProc
函数,此函数对消息执行默认操作,因消息类型而异:
1 | return DefWindowProc(hwnd, uMsg, wParam, lParam); |
注意:当窗口过程执行时,它会阻止在同一线程上创建的窗口的任何其他消息.
因此,请不要在窗口过程中进行冗长处理,例如TCP连接并无限等待,这会让窗口在完成该任务前无法做出其他任何响应,甚至无法关闭!
相反,请将该工作移动到另一个线程,该操作可以使用Windows如下多任务工具
的一种来实现:
- 创建新进程
- 使用线程池
- 使用异步I/O调用
- 使用异步过程调用
绘制窗口
到此为止已经创建窗口,接下来想要在其内绘制一些东西,Windows术语叫做绘制窗口
.换句话说,窗口是一个空白的画布,它等待被绘制(填充).
有时,程序会启动绘制以更新窗口的外观。 在其他时候,操作系统会通知你必须重新绘制窗口的一部分。 发生这种情况时,操作系统会向窗口发送WM_PAINT消息。 必须绘制的窗口部分称为 更新区域。
首次显示消息时,必须绘制窗口的整个工作区,即在显示窗口时,始终会收到至少一条WM_PAINT
消息.
你只负责绘制工作区.外面的框架(包括标题栏)由操作系统自动绘制.完成绘制后,清除更新区域,这会通知操作系统,在发生更改前,其不需要发送另一条WM_PAINT
消息.(?)
假设用户移动了窗口,使其遮挡了窗口的一部分,当遮挡部分再次可见时,该部分将添加到更新区域,并且窗口会收到另一条WM_PAINT
消息:
同样,如果用户拉伸窗口,新的区域将添加到更新区域:
绘制窗口时,先使用BeginPaint函数来启动绘制操作, 此函数使用有关重画请求的信息填充PAINTSTRUCT结构:
1 | HDC BeginPaint( |
其中:
- hWnd 是要求重新绘制的窗口的句柄.
- lpPaint 是指向接收绘制信息的PAINTSTRUCT结构的指针.
- 如果函数成功,则返回指定窗口的显示设备上下文的句柄;如果函数失败,则返回值为 NULL,指示没有可用的显示设备上下文.
对 BeginPaint 的每个调用都必须具有对 EndPaint 函数的相应调用!
然后可以使用FillRect函数来绘制矩形,此函数包括矩形的左和上边框,不包括右和下边框:
1 | int FillRect( |
其中:
- hDC 是设备上下文的句柄.
- lprc 是指向RECT结构的指针,该结构包含矩形的逻辑坐标.
- hbr 是画笔句柄,其可以是逻辑画笔的句柄,也可以是颜色值.
- 如果成功,则返回非零值;否则返回零.
完成绘制后,(必须)调用EndPaint函数,该函数清除更新区域,向Windows发出窗口已完成绘制本身的信号:
1 | BOOL EndPaint( |
- hWnd 是已重新绘制的窗口的句柄.
- lpPaint 是指向PAINTSTRUCT结构的指针.
- 返回值始终为非零值.
上述使用的PAINTSTRUCT结构体包含应用程序的信息,可用于绘制该应用程序拥有的窗口的工作区:
1 | typedef struct tagPAINTSTRUCT { |
其中:
-
hdc: 要用于绘制的显示 DC 的句柄。
-
fErase: 指示是否必须擦除背景。 如果应用程序应擦除背景,则此值为非零值。 如果创建窗口类时没有背景画笔,则应用程序负责擦除背景。 有关详细信息,请参阅 WNDCLASS 结构的 hbrBackground 成员的说明。
-
rcPaint: RECT结构,指定请求绘制的矩形的左上角和右下角,以相对于工作区左上角的设备单位表示。
-
fRestore: 保留;由系统内部使用。
-
fIncUpdate: 保留;由系统内部使用。
-
rgbReserved[32]: 保留;由系统内部使用。
例如,如下例子使用纯色(用户定义的系统背景色)来填充工作区:
1 | switch (uMsg) |