前言:
学习笔记,随时更新。如有谬误,欢迎指正。
说明:
- 红色字体为较为重要部分。
- 绿色字体为个人理解部分。
原文链接:https://learn.microsoft.com/en-us/windows/win32/winmsg/windows
1 窗口
- (所有进程)各窗口共享一个屏幕,同一时刻只有一个窗户口能接收用户输入。
1.1 窗口概述
1.1.1 关于窗口
1.1.1.1 桌面窗口
- 操作系统启动时自动创建桌面窗口,用于展示所有应用程序的所有窗口的背景。
- 默认情况下,桌面窗口使用在注册表中指定的 .bmp 文件(称为壁纸)来作为背景绘制在屏幕上。
- GetDesktopWindow 函数返回桌面窗口句柄。
- 系统配置软件,如控制面板,使用 SystemParametersInfo 函数来更改壁纸( wAction 参数设置为 SPI_SETDESKWALLPAPER ,lpvParam 用来指定bmp文件名称)。 SystemParametersInfo 函数将会加载并使用此图片,并在注册表中记录。
1.1.1.2 应用程序窗口
- 每一个基于图形窗口的应用程序都会有一个主窗口(可能还有其他窗口),每个窗口都会在在显示输出和接收来自用户的输入方面发挥作用。
- 应用程序启动时,任务栏会穿线一个与之相关的按钮(该按钮会使用相关程序的图标和标题)。当应用程序处于激活状态时,任务栏相关按钮会展示出“按下”的状态。
- 通常一个应用程序窗口可能包含如下组件:标题栏、菜单栏、窗口菜单(以前称为系统菜单)、最小化/最大化/还原关闭按钮、一个边框尺寸调整、一个客户区、一个横向/纵向滚动条。示意图详见此处
1.1.1.2.1 客户区(或称为工作区)
- 工作区是指窗口中用于展示应用程序输出的部分。
1.1.1.2.2 非客户区(或称为非工作区)
- 标题栏、菜单栏、窗口菜单、最小化和最大化按钮、边框尺寸调整和滚动条统称为窗口的非工作区。
- (操作)系统管理非工作区的大多数方面,应用程序管理工作区的外观和行为。
- 标题栏展示应用程序创建时指定的图标和标题,且用户可以拖拽标题栏来移动程序窗口。
- 菜单栏列举了应用程序所支持的命令。
- 窗口菜单由(操作)系统创建和管理。 它包含一组标准菜单项,当用户选择这些菜单项时,可设置窗口的大小或位置、关闭应用程序或执行任务。
- 调整大小边框是窗口周边的一个区域,使用户能够使用鼠标或其他指向设备调整窗口大小。
1.1.1.3 控件和对话框
- 对话框大多数都有标题栏、窗口菜单、边框 (非大小调整) 和工作区,但通常没有菜单栏、最小化和最大化按钮或滚动条。
1.1.1.4 窗口属性
- 创建窗口时必须提供以下信息:类名、窗口名称、窗口样式、扩展窗口样式、位置、大小、父窗口或所有者窗口句柄、菜单句柄或Child-Window标识符、应用程序实例句柄、创建数据,窗口创建函数将返回窗口句柄作为该窗口的唯一标识。
1.1.1.4.1 类名
- 每个窗口都属于一个窗口类。 应用程序必须先注册窗口类,然后才能创建该类的任何窗口**。 窗口类定义窗口外观和行为的大多数方面。
- 窗口类的主要组件是窗口程序,是一个用于接收和处理发送到窗口的所有输入和请求的函数。
- 系统以消息的形式提供输入和请求。
1.1.1.4.2 窗口名称
- 若窗口名称不为空,则通常会显示在主窗口、对话框或消息框标题栏。
- 若要在创建窗口后更改窗口名称,请使用 SetWindowText 函数。若要获取窗口名称相关信息,可以使用 GetWindowTextLength 和 GetWindowText 函数。
1.1.1.4.3 窗口样式
- 窗口样式是一个具名常量,它定义了窗口的外观和行为。应用程序通常在创建窗口时设置窗口样式。 它还可以在创建窗口后使用 SetWindowLong 函数设置样式。
- 可以在窗口类中指定窗口类样式,以 CS_ 前缀开头,改样式会作用于该类中所有的窗口实例。
- 可以在创建窗口实例时对该窗口实例指定样式,有一些样式是对所有窗口通用的,在这些通用样式中常规窗口样式以 WS_ 前缀开头。还有一些样式针对特定控件,如 SCROLLBAR 是预定义的滚动条类字符串(用于创建滚动条控件),而 SBS_HORZ 和 SBS_VERT 样式确定是创建水平滚动条控件还是垂直滚动条控件。
1.1.1.4.4 扩展窗口样式
- 每个窗口可以选择具有一个或多个扩展窗口样式。
- 应用程序通常在创建窗口时设置扩展窗口样式。 它还可以在使用 SetWindowLong 函数创建窗口后设置样式。
1.1.1.4.5 位置
- 窗口的位置即其左上角的坐标。
- 对于普通窗口,该坐标(有时称为窗口坐标)始终相对于屏幕的左上角。对于子窗口,该坐标相对于父窗口工作区的左上角。
- WindowFromPoint 函数可以查找占据屏幕上特定点的窗口的句柄。 ChildWindowFromPoint 和 ChildWindowFromPointEx 函数可以查找占据父窗口工作区中特定点的子窗口的句柄。 不同的是 ChildWindowFromPointEx 可以忽略不可见、禁用和透明的子窗口,但 ChildWindowFromPoint 不能。
1.1.1.4.6 大小
- 窗口的大小 (宽度和高度) 以像素为单位。
- 窗口的宽度或高度可以为零。 如果应用程序将窗口的宽度和高度设置为零,则系统会将窗口大小设置为默认的最小窗口大小。
- 窗口的最小大小可以通过向 GetSystemMetrics 函数传入 SM_CXMIN 和 SM_CYMIN 参数来获取。
- 如果要创建特定大小的窗口,可以先调用 AdjustWindowRect 和 AdjustWindowRectEx 函数,他们会根据所需工作区的大小计算窗口所需的大小,然后将得到的值传递给 CreateWindowEx 函数。
- 应用程序的窗口大小可调整大,但不应超过屏幕大小,设置前可以通过向 GetSystemMetrics 函数传递 SM_CXSCREEN 和 SM_CYSCREEN 参数来查询屏幕的宽度和高度。
1.1.1.4.7 父窗口或所有者窗口句柄
- 一个窗口可以有一个父窗口。 具有父窗口的窗口称为子窗口。
- 父窗口提供了用于定位子窗口的坐标系。
- 父窗口会对子窗口的外观和行为产生一定影响,如剪裁子窗口,使子窗口的任何部分都不能出现在其父窗口的边框之外。
- 没有父窗口或其父窗口为桌面窗口的窗口称为顶级窗口。可以使用 EnumWindows 函数获取屏幕上每个顶级窗口的句柄。
- 顶级窗口可以拥有另一个窗口,也可以被另一个窗口拥有。 被拥有的窗口始终出现在其所有者窗口的前面,当其所有者窗口最小化时隐藏,当其所有者窗口被销毁时会销毁。
1.1.1.4.8 菜单句柄或子窗口标识符
- 子窗口可以具有子窗口标识符,即与子窗口关联的应用程序定义的唯一值。创建子窗口时可以指定其标识符(通过 CreateWindow 传参)。创建窗口后可以使用 SetWindowLong 函数更改标识符,也可以使用 GetWindowLong 函数获取标识符。
- 每个窗口(子窗口除外)都可以有一个菜单。 应用程序可以通过在注册窗口的类或创建窗口时提供菜单句柄来设置菜单。
1.1.1.4.9 应用程序实例句柄
- 每个应用程序实例都有一个与之关联的实例句柄,该句柄由操作系统生成,通过WinMain提供。操作系统就是通过不同的用用程序实例句柄来分别不同应用程序实例的。
- 窗口在创建时通常要指定应用程序实例句柄,样可以确保窗口与应用程序实例关联起来,正确处理窗口消息,并在应用程序退出时进行资源清理。
1.1.1.4.10 创建数据
- 每个窗口都可以有与之关联的创建数据。创建窗口时,系统会将创建数据的指针传递到正在创建的窗口的窗口程序(创建数据指针即 WM_CREATE 事件中的 lParam 参数所代表的 CREATESTRUCT 结构体中的 lpCreateParams 指针)。
1.1.1.4.11 窗口句柄
- 创建窗口成功后会返回窗口的唯一标识:窗口句柄 。 在应用程序中都通过句柄来操作指定窗口。
- FindWindow 函数查找是否存在具有指定类名或窗口名称的窗口。 如果存在此窗口将返回该窗口的句柄。 FindWindowEx 函数可以查找相应子窗口。
- IsWindow 函数用来查询窗口句柄当前是否还标识的是有效窗口。
- 某些函数中的窗口句柄参数可以换位一些特殊值,例如在 SendMessage 和 SendMessageTimeout 函数中使用 HWND_BROADCAST ,或在 MapWindowPoints 函数中使用 HWND_DESKTOP 。
1.1.1.5 窗口创建
- 创建窗口使用 CreateWindow 或 CreateWindowEx 函数。两函数只是 dwExStyle 参数的区别,实际上 CreateWindow 函数只是将 dwExStyle 参数设置为零的 CreateWindowEx 函数。
1.1.1.5.1 主窗口创建
- 每个基于 Windows 的窗口应用程序都必须使用 WinMain 作为其入口点函数。
- WinMain 需要执行许多任务,包括注册主窗口的窗口类和创建主窗口。 WinMain 通过调用 RegisterClass 函数注册主窗口类,并通过调用 CreateWindowEx 函数创建主窗口。 WinMain 中还可以使用 CreateMutex 函数创建命名互斥体来限制程序单一实例运行。
- 主窗口创建后不会自动显示,必须使用 ShowWindow 函数来显示主窗口,函数中需要传递窗口句柄和一个用于指定主窗口在首次显示时的显示模式(最小化还是最大化)的标志。正常情况下该显示模式可以是 WS_ 开头的任何值,但当 ShowWindow 用来显示程序主窗口时,此标记必须为 SW_SHOWDEFAULT ,表示按照启动此应用程序的程序(父进程)的指示(父进程调用 CreateProcess 中的 STARTUPINFO 参数)来显示窗口。
- 如果使用了 Unicode 版本的 RegisterClass 注册了窗口类,则窗口仅接收 Unicode 消息。可以使用 IsWindowUnicode 函数来查询窗口是否使用 Unicode 字符集。
1.1.1.5.2 窗口创建消息
- 创建窗口时操作系统会将创建消息发送到窗口的窗口程序。
- 创建窗口的非工作区后发送 WM_NCCREATE 消息,创建窗口的工作区后发送 WM_CREATE 消息。 窗口程序会在显示窗口前收到这两条消息。这两条消息都会携带 CREATESTRUCT 结构体的指针,该结构包含 CreateWindowEx 函数中指定的所有信息。
- 创建子窗口时操作系统会在发送 WM_NCCREATE 和 WM_CREATE 消息后,将 WM_PARENTNOTIFY 消息发送到父窗口。 它还会在创建窗口时发送其他消息,这些消息的数量和顺序取决于窗口类和样式,以及用于创建窗口的函数。
1.1.1.5.3 多线程应用程序
- Windows 界面应用程序可以有多个执行线程,每个线程都可以创建窗口。 创建窗口的线程必须包含其窗口程序的代码。
- EnumThreadWindows 函数可以枚举由特定线程创建的窗口。
- GetWindowThreadProcessId 函数可以获取创建特定窗口的线程 id 。
- ShowWindowAsync 函数可以在一个线程中设置由另一个线程创建的窗口的显示状态。
1.1.2 窗口功能
1.1.2.1 窗口类型
1.1.2.1.1 重叠窗口
- 重叠窗口是顶级窗口。(所有顶层窗口都是层叠窗口,都具有 WS_OVERLAPPED 样式)
- 在 CreateWindowEx 函数中指定 WS_OVERLAPPED 或 WS_OVERLAPPEDWINDOW 样式将创建重叠窗口。
- WS_OVERLAPPED 样式:窗口具有标题栏和边框。
- WS_OVERLAPPEDWINDOW 样式:窗口具有标题栏、调整边框大小、窗口菜单以及最小化和最大化按钮。
1.1.2.1.2 弹出窗口
- 弹出窗口是一种特殊类型的重叠窗口,它的标题栏是可选的。
- 在 CreateWindowEx 中指定 WS_POPUP 样式或 WS_POPUPWINDOW 样式来创建弹出窗口。
- WS_POPUP 样式:弹出窗口。若要包含标题栏,还需指定 WS_CAPTION 样式。
- WS_POPUPWINDOW 样式:具有边框和窗口菜单的弹出窗口。 WS_CAPTION 样式必须与 WS_POPUPWINDOW 样式结合使用才能使窗口菜单可见。
1.1.2.1.3 子窗口
- 在 CreateWindowEx 函数中指定 WS_CHILD 样式来创建子窗口。子窗口的区域仅限于其父窗口的工作区。
- 子窗口必须有父窗口,其父窗口可以是重叠窗口、弹出窗口甚至另一个子窗口。如果在 CreateWindowEx 中指定 WS_CHILD 样式,但没有指定父窗口,则系统不会创建该窗口。
- 子窗口就默认只有工作区。但是可以指定其拥有标题栏、窗口菜单、最小化和最大化子窗口的按钮、边框和滚动条(通过样式指定)。
- 子窗口不能有菜单。如果注册子窗口类或创建子窗口时指定菜单句柄,则忽略菜单句柄。
- 如果未指定边框样式,系统会创建无边框窗口。
1.1.2.1.3.1 定位
- 子窗口位置坐标始终相对于其父窗口工作区的左上角。
- 子窗口的任何超出父窗口的边框之外部分将会被裁剪掉。
- 子窗口在销毁父窗口之前被销毁。
- 子窗口在隐藏父窗口之前被隐藏。子窗口仅在父窗口可见时才可见。
- 子窗口随父窗口的工作区移动。子窗口负责在移动后绘制其工作区。
- 子窗口在父窗口显示之后才显示。
1.1.2.1.3.2 剪裁
- 操作系统不会自动从父窗口的工作区剪裁子窗口(这里说的裁剪应该是绘制时的裁剪,上节中诉说的裁剪时指位置上的裁剪),也就是说如果父、子窗口在同一位置进行绘图,默认情况下是不会出现子窗口绘制的像素的,子窗口的所有像素都是由父窗口绘制。但如果父窗口具有 WS_CLIPCHILDREN 样式,操作系统会从父窗口的工作区的绘制中剪裁掉子窗口的位置,而不绘制子窗口部分。
- 多个拥有同一个父窗口的子窗口称为同级窗口,同级窗口之间何以互相重叠,如果后一个窗口没有设置 WS_CLIPSIBLINGS 样式,则对于重叠部分来说,在其前面的同级窗口无法遮住后面同级窗口的像素。如果后一个窗口设置了 WS_CLIPSIBLINGS 样式,则在绘制后一个窗口时,其重叠部分将会被裁剪掉而不绘制(这样效果)。
- 如果窗口有 WS_CLIPCHILDREN 或 WS_CLIPSIBLINGS 样式,则性能将略有下降,谨慎使用。
1.1.2.1.3.3 与父窗口的关系
- SetParent 函数可以更改父窗口。调用此函数后,系统会从旧父窗口的工作区中删除子窗口,并将其移动到新父窗口的工作区。
- GetParent 函数可以获取子窗口的父窗口的句柄。
- 父窗口将工作区的一部分分给子窗口,子窗口接受该区域的素有输入。
- 每个控件都是一个子窗口。
- 子窗口只有一个父窗口,但父窗口可以有多个子窗口。 子窗口也可以有子窗口。窗口链中,每个子窗口是原始父窗口的后代窗口。 IsChild 函数可以校验给定窗口是否是给定父窗口的子窗口(或后代窗口)。
- EnumChildWindows 函数可以枚举给定父窗口的子窗口。
1.1.2.1.3.4 消息
- 操作系统会将子窗口的输入消息直接传递到子窗口,消息不会通过父窗口传递。唯一例外是当调用 EnableWindow 函数禁用了子窗口时,操作系统会将本应传递给子窗口的任何输入消息传递到父窗口。
- 应用程序通过向控件发送消息来指导控件的活动。应用程序使用控件的子窗口标识符将消息定向到控件。此外,控件会将一些通知消息发送到它的父窗口(比如 WM_PARENTNOTIFY 消息)。通知消息中包含了控件的子窗口标识符,父窗口使用该标识符辨别发送消息的控件。如何指定子窗口标识符见1.1.1.4.8节。
1.1.2.1.4 分层窗口
- 使用分层窗口可以显著提高具有复杂形状、对其形状进行动画处理或希望使用 alpha 混合效果的窗口的性能和视觉效果。
- 系统会自动合成和重绘分层窗口和底层应用程序的窗口。因此,分层窗口渲染平滑,不会有复杂窗口区域的闪烁问题。
- CreateWindowEx 函数中指定 WS_EX_LAYERED 扩展窗口样式可以创建分层窗口,也可以在创建窗口后调用 SetWindowLong 函数设置 WS_EX_LAYERED 来创建分层窗口。 调用 CreateWindowEx 后分层窗口将不会显示,直到为此窗口调用 SetLayeredWindowAttributes 或 UpdateLayeredWindow 函数。
- 从 Windows 8 开始, WS_EX_LAYERED 可用于子窗口和顶级窗口。以前的Windows版本仅支持顶级窗口 WS_EX_LAYERED 。
- 分层窗口的命中测试基于窗口的形状和透明度。也就是说窗口中特定颜色或alpha值为零的区域鼠标消息将直接穿过(即鼠标点到此窗口就的这些区域将不会有鼠标消息发送到此窗口中)。但如果分层窗口具有 WS_EX_TRANSPARENT 扩展窗口样式,则分层窗口的形状将被全部忽略(即鼠标点到此窗口任何区域区域都不会有鼠标消息发送到此窗口中)。
1.1.2.1.5 Message-Only窗口
- Message-Only 窗口不可见,没有 Z-Order ,无法枚举,不接收广播消息,仅用来处理消息。
- 在 CreateWindowEx 函数的 hWndParent 参数中指定 HWND_MESSAGE 常量或现有的 Message-Only 窗口句柄,可以创建 Message-Only 窗口。还可以通过在 SetParent 函数的 hWndNewParent 参数中指定HWND_MESSAGE,将现有窗口更改为Message-Only窗口。
- FindWindowEx 函数的 hwndParent 参数中指定 HWND_MESSAGE 可以查找 Message-Only 窗口。
1.1.2.2 窗口关系
1.1.2.2.1 前台和后台窗口
- 每个进程可以有多个执行线程,每个线程都可以创建窗口。被用户当前正在使用(接收用户输入)的窗口称为前台窗口,其他的窗口都是后台窗口,创建该窗口的线程称为前台线程,其他的线程都是后台线程。
- 也就是说前台线程和前台窗口在随着用户操作而不断地切换,同一时刻前台线程和前台窗口都只会有一个。另外前台窗口和激活窗口不一定相同(大部分情况下相同),前台窗口是指当前接收用户输入的窗口,而激活窗口是当前有焦点的窗口。比如一个 notepad++ 界面(有焦点)在前, vscode 界面在后,两者不完全遮挡,此时鼠标移到 vscode 界面,用滚轮能翻动 vscode 界面内容,此时 vscode 界面就是前台窗口, notepad++ 界面就是激活窗口。
- 每个线程都有一个优先级,用于确定线程接收的 CPU 时间量,应用程序可以设置其线程的优先级。一般前台线程(正常为9)的优先级略高于后台线程(正常为7)。
- 用户通过单击窗口或使用 Alt+TAB 或 ALT+ESC 组合键来设置前台窗口。
- GetForegroundWindow 函数可以获得前台窗口句柄。
- SetForegroundWindow 函数可以设置窗口为前台窗口。但系统对哪些进程可以设置前台窗口做了限制。当且仅当进程满足以下条件,才能设置前台窗口:
- 以下所有条件均需满足:
- 调用 SetForegroundWindow 地进程为桌面程序,不是 UWP 或者 Windows AppStore 应用。
- 前台进程尚未使用 LockSetForegroundWindow 函数来禁用前台窗口设置。
- 前台锁超时已过期。(见 SystemParametersInfo 中的 SPI_GETFOREGROUNDLOCKTIMEOUT )
- 没有处于激活的菜单。
- 以下所有条件需满足至少一个:
- 调用进程是前台进程。
- 调用进程由前台进程启动。
- 当前没有前台窗口(也就没有前台进程)。
- 调用进程收到了最后一个输入事件。
- 正在调试前台进程或调用进程。
- 以下所有条件均需满足:
- 在某些情况下,即使一个进程满足这些条件,也有可能设置前台窗口失败。如:
- 安全性限制:操作系统可能通过安全策略限制某些进程设置前台窗口,以防止恶意软件或未经授权的进程干扰用户的操作。
- 用户交互限制:如果用户正在与其他应用程序进行交互(如编辑文档、播放媒体等),操作系统可能会阻止其他进程设置前景窗口,以避免打断用户的工作流程。
- 系统级别限制:一些进程可能具有更高的权限级别,例如管理员或系统服务进程,它们可以优先于其他进程设置前景窗口。在这种情况下,其他进程可能会被拒绝设置前景窗口的权限。
- 可以设置前台窗口的进程可以通过 AllowSetForegroundWindow 函数或在 BroadcastSystemMessage 函数中传入BSF_ALLOWSFW,使另一个进程能够设置前台窗口。 前台进程可以通过调用 LockSetForegroundWindow 函数来禁用对 SetForegroundWindow 调用(也就是说前台进程可以调用此函数来禁止其他进程设置前台窗口,且加锁和解锁最好成对,即上锁之后前台进程无法被修改,执行完相关逻辑后应该解除该限制)。
1.1.2.2.2 被拥有的窗口
- 重叠或弹出窗口可由另一个 重叠或弹出窗口拥有。产生“拥有”关系的窗口之间会有一些约束:
- 被拥有的窗口 Z-Order 始终高于其所有者窗口。
- 当所有者窗口被销毁时,操作系统会自动销毁其所拥有的窗口。
- 当所有者窗口最小化时,其所拥有的窗口会被隐藏(注意:Windows 中隐藏和最小化不同的,最小化是指窗口缩小到任务栏状态,隐藏是任务栏也看不到窗口图标。如果主窗口被隐藏了意味着需要任务管理器结束程序,然后重启该程序。本条说的是所有者最小化的效果,而所有者窗口隐藏不会对其他窗口产生影响)。
- 只有重叠或弹出窗口可以是所有者窗口,子窗口不能是所有者窗口( 也就是说子窗口可以是被拥有者,子窗口的父窗口和拥有者窗口可能不一样)。
- 调用CreateWindowEx创建具有 WS_OVERLAPPED 或 WS_POPUP 样式的窗口时, hwndParent 参数指定的是该窗口的所有者(不是父窗口)。hwndParent参数必须有 WS_OVERLAPPED 或 WS_POPUP 样式。 如果 hwndParent 是子窗口(即有 WS_CHILD 样式),则系统会将窗口的所有者设置为 hwndParent 所指定的子窗口的顶层父窗口。
- 子窗口一定有父窗口,但不能有所有者窗口(因为父窗口和所有者窗口都管理子窗口的生命周期,如果两者不一致则生命周期归属权无法清晰)。
- 层叠窗口和弹出窗口(顶层)没有父窗口,他们不一定有所有者。没有所有者的顶层窗口可以在任务栏显示标题。
- 默认情况下,对话框和消息框是被拥有的窗口(因为从一般逻辑上讲这两者都是依附于其他窗口的)。
- GetWindow 函数传入 GW_OWNER 标记可以查找指定窗口的所有者窗口句柄。
1.1.2.2.3 Z-Order
- Z-Order 表示一个窗口在层叠窗口栈中的位置(距离屏幕的前后位置)。重叠窗口栈沿着虚拟的z轴方向向屏幕外延申。 Z-Order 值越大越靠近屏幕,反之越远离屏幕(越容易被遮挡)。
- 系统使用单链表来维护 Z-Order 。最顶层窗口会有 WS_EX_TOPMOST 样式,不管其是不是激活或前台窗口,会总是显示在最前面。
- 有父子关系时父窗口先刷新,子窗口后刷新。同为子窗口时, Z-Order 越大越先绘制。
- 窗口链表中的先后顺序就是显示的前后顺序,在链表中越靠前显示位置就越靠前(越靠近屏幕)。
- Z-Order 值越大,在屏幕显示越靠近用户,越先绘制。
- 创建顶层窗口时,窗口管理器会把它加到顶层窗口链的最前面,使整个窗口显示在最前面。其任何子窗口的 Z-Order 都会比该顶层窗口更靠前,这样才能将子窗口显示在父窗口之上。但顶层窗口的任何子窗口 Z-Order 都不会比在该顶层窗口更靠前的的兄弟们更靠前(也就是说相邻的顶层窗口中,处于下层的顶层窗口的子窗口将不会比处于上层的顶层窗口更靠前,这很好理解)。
- 创建子窗口时,窗口管理器会把子窗口放在该层级窗口链表的最后面,这样做的目的是使后加入的子窗口在子窗口有重叠时显示在最前面(显示效果,不是 Z-Order 逻辑上的在最前面,这是两回事),因为子窗口大多数情况会使用父窗口的 DC ,这样一来绘制某一个子窗口时实可以绘制到其他兄弟窗口的工作区的,由于默认情况下子窗口不带有 WS_CLIPSIBLING 样式,这样就会使后绘制的窗口像素显示在最前面。
- 设置 WS_CLIPSIBLING 样式可以使绘制 Z-Order 低的窗口时裁剪掉其窗口中被比其 Z-Order 更高的窗口所占用的区域,因此可以达到 Z-Order 越大,越先绘制,但看起来却越接近用户的效果。因此可以分析上文第6、7条中的逻辑:因为顶层窗口在创建时窗口管理器会让其强制携带 WS_CLIPSIBLING 样式,且无法去除,所以新添加的顶层窗口添加在该层级窗口链表的最前面,就达到了新窗口显示在最前面的效果;对于子窗口默认是不带 WS_CLIPSIBLING 样式的,所以新添加的子窗口添加在该层级窗口链表的最后面,同样达到了新窗口显示在最前面的效果。因此可以看出, Z-Order 和 WS_CLIPSIBLING 配合使用才能够随意控制窗口遮挡关系。
- 用户通过几乎不同的窗口来改变 Z-Order 。操作系统将激活窗口置于最前。 GetTopWindow 函数可以获取某窗口的最顶层的子窗口,然后用 GetNextWindow 函数可以根据Z-Order层次向上或向下获取相应窗口。
1.1.2.3 窗口显示状态
1.1.2.3.1 激活的窗口
- 用户正在使用的顶层窗口称为激活窗口,只有顶层窗口才能称为激活窗口。当使用子窗口时会自动激活其所属的顶层窗口。同一时刻只有一个顶层窗口处于激活状态。
- 单击顶级窗口 (或其子窗口) ,或使用 Alt+ESC 或 ALT+TAB 组合键来激活顶级窗口。 SetActiveWindow 函数可以激活顶级窗口。其他函数如 SetWindowPos 、 DeferWindowPos 、 SetWindowPlacement 和 DestroyWindow 也可能导致不同的顶层窗口被激活。
- GetActiveWindow 函数可以获取激活窗口的句柄。
- 当激活窗口从一个应用程序的顶级窗口变为另一个应用程序的顶级窗口时,系统会向这两个应用程序发送 WM_ACTIVATEAPP 消息。当激活窗口更改为同一应用程序中的其他顶级窗口时,系统会向两个窗口发送 WM_ACTIVATE 消息。
1.1.2.3.2 被禁用的窗口
- 窗口可以被禁用,被禁用的窗口无法接收鼠标和键盘事件,但它可以从其他窗口、其他应用程序和系统接收消息(禁用只是不接受交互事件,其他事件不受影响)。
- 默认情况下创建窗口时窗口时未被禁用的。但在创建时可以指定 WS_DISABLED 样式来禁用新窗口。 EnableWindow 函数可以启用或禁用窗口。 当窗口的启用状态即将更改时,系统会向窗口发送 WM_ENABLE 消息。 IsWindowEnabled 函数可以查询窗口是否被禁用。
- 禁用子窗口时,系统会将子窗口的鼠标输入消息传递到父窗口。
- 一次仅能有一个窗口接收键盘输入, 该窗口被称为具有键盘焦点。如果使用 EnableWindow 函数禁用有键盘焦点窗口,该窗口除了被禁用外,还会失去键盘焦点。如果子窗口具有键盘焦点,则父窗口在被禁用时子窗口会失去焦点。
1.1.2.3.3 窗口可见性
- 隐藏的窗口可以处理来自系统或其他窗口的消息,但它不能处理用户的输入,也不能显示。
- 窗口设置 WS_VISIBLE 样式时是可见的。 默认情况下,如未指定 WS_VISIBLE 样式, CreateWindowEx 函数会创建隐藏窗口。通常情况下应用程序通过在创建窗口之后再设置 WS_VISIBLE 样式,这样可以向用户隐藏创建过程的详细信息。如果在调用 CreateWindowEx 时指定了 WS_VISIBLE 样式,则系统会在创建窗口之后、显示窗口之前将 WM_SHOWWINDOW 消息发送到窗口。
- IsWindowVisible函数可以查询窗口是否可见。还可以使用 ShowWindow 、 SetWindowPos 、 DeferWindowPos 、 SetWindowPlacement 或 SetWindowLong 函数来显示或隐藏窗口。这些函数都是通过设置或删除窗口的 WS_VISIBLE 样式来显示或隐藏窗口的。在显示或隐藏消息之前,还将 WM_SHOWWINDOW 消息发送到窗口。
- 当所有者窗口最小化时,操作系统自动隐藏其所拥有的窗口。同样,还原所有者窗口时,系统会自动显示其所拥有的窗口。这两种情况下系统会先将 WM_SHOWWINDOW 消息发送到其所拥有的窗口,然后再隐藏或显示它们。 ShowOwnedPopups 函数可以显示或隐藏一个所有者窗口所拥有的所有窗口。此函数设置或删除被有拥窗口的 WS_VISIBLE 样式,并将 WM_SHOWWINDOW 消息发送到被有拥窗口,然后再隐藏或显示它们。隐藏所有者窗口不会影响所拥有窗口的可见性状态(即是否有 WS_VISIBLE 样式)。
- 父窗口显示时其子窗口也可见,父窗口隐藏时其子窗口也隐藏。最小化父窗口会最小化子窗口,但是父子窗口的可见性状态不改变。
- 即使窗口具有 WS_VISIBLE 样式也不一定能被看到。(也就是说,有 WS_VISIBLE 样式不等于用户一定能看到该窗口,用户能不能看到该窗口还要取决于一些其他因素,如父窗口裁剪、窗口位置等)
1.1.2.3.4 最小化、最大化和还原的窗口
- 最大化的窗口具有 WS_MAXIMIZE 样式。默认情况下,系统会放大最大化窗口,使其填充整个屏幕。虽然可以通过设置窗口尺寸是窗口充满整个屏幕,但这还是与最大化状态不一样。最大化状态时,系统会自动将窗口的标题栏移动到屏幕顶部或父窗口工作区的顶部。 另外,系统会禁用窗口的通过边框调整窗口大小功能和通过拖拽标题栏来移动窗口功能。
- 最小化的窗口具有 WS_MINIMIZE 样式。默认情况下,系统会将最小化窗口缩小到任务栏按钮的大小并将最小化窗口移至任务栏上(这就是最小化不等于隐藏的原因)。
- 还原的窗口是指从最小化或最大化状态回到到其最小化或最大化状态之前的大小和位置的窗口。
- 如果在 CreateWindowEx 函数中指定 WS_MAXIMIZE 或 WS_MINIMIZE 样式,则窗口最初将最大化或最小化。 CloseWindow 函数用来最小化窗口。 ArrangeIconicWindows 函数可以排列桌面上的图标,或在父窗口中排列最小化的子窗口。 OpenIcon 函数将最小化窗口还原到其以前的大小和位置。
- IsZoomed 函数用来查询给定窗口是否是最大化状态。
- IsIconic 函数用来查询给定窗口是否是最小化状态。
- GetWindowPlacement 函数可以查询窗口的显示状态,以及各种显示状态(最小化、最大化、还原)下窗口的位置。
- 当系统收到最大化或还原一个最小化窗口的命令时,它会向窗口发送 WM_QUERYOPEN 消息。 如果窗口过程返回 FALSE ,则系统将忽略最大化或还原命令。
- 系统自动将最大化窗口的大小和位置设置为系统定义的默认值。若要覆盖这些默认值,可以调用 SetWindowPlacement 函数;或处理即将被最大化的窗口所收到的 WM_GETMINMAXINFO 消息, WM_GETMINMAXINFO 包含一个指向 MINMAXINFO 结构的指针,该结构体中包含了操作系统用来设置最大化位置和大小的数据,修改这些数据就能覆盖默认值。
1.1.2.4 窗口大小和位置
- 窗口的大小和位置用一个矩形来表示。该矩形的坐标相对于屏幕或父窗口,顶层窗口的坐标相对于屏幕的左上角,子窗口的坐标相对于父窗口的左上角。
1.1.2.4.1 默认大小和位置
- CreateWindowEx 中指定 CW_USEDEFAULT 可以使系统自己计算顶级窗口的初始大小或位置。
- 如果窗口的坐标参数设置为 CW_USEDEFAULT ,并且未创建其他顶级窗口,则系统相对于屏幕左上角会设置新窗口的位置;否则,它将相对于应用程序最近创建的顶级窗口位置来该设置的位置。
- 如果窗口的宽度和高度参数设置为 CW_USEDEFAULT ,系统将计算自己新窗口的大小。如果应用程序已创建其他顶级窗口,系统将基于应用程序最近创建的顶级窗口的大小来计算新窗口的大小。在创建子窗口或弹出窗口时指定 CW_USEDEFAULT 会导致系统将窗口的大小设置为默认的窗口最小大小。
1.1.2.4.2 可调大小
- 具有 WS_THICKFRAME 样式的窗口具有大小调整边框,系统为该样式的窗口维护了一个最小和最大的可调大小。最小可调大小是通过拖动窗口的大小边框能够达到的最小窗口大小。最大可调大小是可以通过拖动大小调整边框可以达到的最大窗口大小。
- 窗口在被创建时的最小和最大可调大小将设置为系统定义的默认值。可以通过处理 WM_GETMINMAXINFO 消息来获取并覆盖其默认值。详见1.1.2.4.5节。
1.1.2.4.3 系统命令
- 具有窗口菜单的应用程序可以通过发送系统命令来更改该窗口的大小和位置。当用户从窗口菜单中选择命令时,将生成系统命令。应用程序可以通过向窗口发送 WM_SYSCOMMAND 消息来模拟用户操作。影响窗口的大小和位置的系统命令如下:
- SC_CLOSE:关闭窗口。 此命令将 WM_CLOSE 消息发送到窗口。 该窗口执行清理和销毁自身所需的所有步骤。
- SC_MAXIMIZE:最大化窗口。
- SC_MINIMIZE:最小化窗口。
- SC_RESTORE:将最小化或最大化的窗口还原到其以前的大小和位置。
- SC_MOVE:移动窗口。
- SC_SIZE:调整窗口大小。
1.1.2.4.4 大小和位置函数
- 设置窗口的大小或位置的函数包括: SetWindowPlacement 、 MoveWindow 、 SetWindowPos 和 DeferWindowPos 。
- SetWindowPlacement 设置窗口的最小化位置、最大化的位置、还原的大小和位置以及显示状态。
- MoveWindow 和 SetWindowPos 函数都设窗口的大小或位置,但 SetWindowPos 函数包含一组影响窗口显示状态的标志,而 MoveWindow 不包括这些标志。
- 使用 BeginDeferWindowPos 、 DeferWindowPos 和 EndDeferWindowPos 函数可同时设置多个窗口的位置,包括大小、位置、 Z-Order 和显示状态。
- GetWindowRect 函数可以获取窗口边界矩形的坐标,它使用窗口左上角和右下角的坐标填充 RECT 结构,坐标相对于屏幕左上角。
- ScreenToClient 函数将屏幕坐标系下的点转换为工作区坐标系坐标。
- MapWindowPoints 函数将 (映射) 一组点从相对于一个窗口的坐标系转换为相对于另一个窗口的坐标系。
- GetClientRect 函数可以获取窗口工作区矩形的坐标。它使用工作区左上角和右下角的坐标填充 RECT 结构,左上角的坐标始终 (0,0) ,右下角的坐标是工作区的宽度和高度。
- CascadeWindows 函数层叠排列桌面上的窗口或指定父窗口的各指定子窗口。
- TileWindows 函数平铺桌面上的窗口或指定父窗口的子窗口。
1.1.2.4.5 大小和位置消息
- 系统将 WM_GETMINMAXINFO 消息发送到大小或位置即将更改的窗口。 WM_GETMINMAXINFO 参数中包含指向 MINMAXINFO 结构的指针,该结构包含窗口的默认最大化大小和位置,以及默认的最小和最大可调大小。应用程序可以通过修改 MINMAXINFO 的相应成员变量来覆盖这些默认值。窗口必须具有 WS_THICKFRAME 或 WS_CAPTION 样式才能接收 WM_GETMINMAXINFO 。 具有 WS_THICKFRAM 样式的窗口在创建过程中以及移动或调整其大小时都会接收到此消息。
- 系统将 WM_WINDOWPOSCHANGING 消息发送到大小、位置、 Z-Order 或显示状态即将更改的窗口。 此消息包含指向 WINDOWPOS 结构的指针,该结构指定窗口新的大小、位置、 Z-Order 和显示状态。修改 WINDOWPOS 中的变量可以影响窗口的新的大小、位置和外观。
- 更改窗口的大小、位置、 Z-Order 或显示状态后,系统会将 WM_WINDOWPOSCHANGED 消息发送到窗口。此消息包含指向 WINDOWPOS 的指针,该指针携带了窗口新的大小、位置、 Z-Order 和显示状态。修改随 WM_WINDOWPOSCHANGED 消息传递过来的 WINDOWPOS 结构体中的值对窗口不会产生任何影响。想要处理 WM_SIZE 和 WM_MOVE 消息的窗口必须将 WM_WINDOWPOSCHANGED 传递给 DefWindowProc 函数,否则系统不会向窗口发送 WM_SIZE 和 WM_MOVE 消息。
- 创建窗口或调整窗口大小时,系统会将 WM_NCCALCSIZE 消息发送到窗口。系统使用该消息来计算窗口工作区的大小以及工作区相对于窗口左上角的位置。 窗口通常将此消息传递给默认的窗口程序,但当应用程序需要自定义窗口的非工作区或在调整窗口大小时保留工作区部分时,此消息可能很有用。
1.1.2.5 窗口动画
- AnimateWindow 函数可以在显示或隐藏窗口时生成特殊效果,具体效果取决于在调用 AnimateWindow 时指定的标志。
- 默认情况下,系统使用滚动动画。
1.1.2.6 窗口布局和镜像
- 窗口布局定义了文本和 Windows 图形设备接口( GDI ) 对象如何在窗口或设备上下文( DC )中布局。
- 某些语言(如英语、法语和德语)需要从左到右 ( LTR ) 布局。其他语言(如阿拉伯语和希伯来语)需要从右到左 ( RTL ) 布局 (也称为镜像 ) 。
- 窗口布局适用于文本,但也会影响窗口的其他 GDI 元素,包括位图、图标、原点的位置以及水平坐标坐标增加方向等。
- 并非所有对象都受窗口布局的影响,如对话框、消息框和不与窗口关联的设备上下文(如图元文件和打印机 DC )的布局必须单独处理。
- 对于具有 CS_OWNDC 样式的窗口或具有 GM_ADVANCED 图形模式的 DC ,不支持更改为 RTL 布局。
- 默认情况下,窗口布局为LTR。若要设置 RTL 窗口布局,请在调用 CreateWindowEx 时使用 WS_EX_LAYOUTRTL 扩展样式。
- 默认情况下,子窗口(即使用 WS_CHILD 样式和有效的父窗口句柄创建的窗口)具有与其父窗口相同的布局。若要禁止子窗口对父窗口镜像布局的继承,请在调用 CreateWindowEx 时指定 WS_EX_NOINHERITLAYOUT 扩展样式。
- 被拥有的窗口(没有 WS_CHILD 样式)父窗口句柄为空的窗口无法继承镜像样式。
- 若要禁用单个窗口的镜像布局的继承,请处理 WM_NCCREATE 消息时使用 SetWindowLong 来移除 WS_EX_LAYOUTRTL 扩展样式。
- 调用 SetProcessDefaultLayout(LAYOUT_RTL) 可以将默认布局设置为 RTL 。调用后创建的所有窗口都将镜像,但现有的窗口不受影响。若要关闭默认镜像,调用 SetProcessDefaultLayout(0) 。 SetProcessDefaultLayout 仅镜像需镜像窗口的 DC ,若要镜像任意一个 DC ,请调用 SetLayout(hdc, LAYOUT_RTL) ,详见1.1.2.6.2节。
- 默认情况下,镜像窗口中的位图和图标也会被镜像。但有些内容如商标、模拟时钟等就不应该被镜像。在 SetLayout 函数的 dwLayout 参数中加上 LAYOUT_BITMAPORIENTATIONPRESERVED 标记可以禁用对位图的镜像。
- GetProcessDefaultLayout 函数可以查询当前当前默认布局,该函数会返回 LAYOUT_RTL 或0。 GetLayout 可以查询设备上下文的布局设置。
- 创建窗口后,可以使用 SetWindowLong 函数更改布局。且更改现有窗口的布局时,必须使窗口失效并更新(调用 InvalidateRect ),以确保窗口的内容都在同一布局上绘制。
- 在将一个坐标系下的点映射到另一个处于镜像布局中的坐标系时,请使用 MapWindowPoints 函数,而不能使用如 ScreenToClient 等函数,因为在支持镜像的平台上, MapWindowPoints 返回值会较换左右点坐标以保证左侧不大于右侧。
1.1.2.6.1 镜像对话框和消息框
- 对话框和消息框不继承布局,因此必须显式设置布局。
- MessageBox 或 MessageBoxEx 函数中传入 MB_RTLREADING 选项可以镜像消息框布局。
- 在对话框模板结构 DLGTEMPLATEEX 中使用 WS_EX_LAYOUTRTL 扩展样式可以镜像对话框布局。属性表是对话框中的特例,每个选项卡被视为单独的对话框,因此要为每一个需要被镜像的选项卡设置 WS_EX_LAYOUTRTL 扩展样式。
1.1.2.6.2 镜像不与窗口关联的设备上下文
- 未与窗口关联的 DC(如图元文件或打印机 DC)不会继承布局,因此必须显式设置布局,对 DC 设置布局使用 SetLayout 函数。
- DC 的初始布局都是根据窗口的 WS_EX_LAYOUTRTL 标志由 BeginPaint 或 GetDC 来设置的。
- SetLayout 不会影响 GetWindowOrgEx 、 GetWindowExtEx 、 GetViewportOrgEx 和 GetViewportExtEx 返回的值。
- 当布局为 RTL 时, GetMapMode 将返回 MM_ANISOTROPIC 而不是 MM_TEXT ,但仅 GetMapMode 的返回值受到影响,使用SetMapMode传入 MM_TEXT 时功能是正常的。当在 MM_TEXT 映射模式下调用 SetLayout(hdc, LAYOUT_RTL) 映射模式更改为 MM_ANISOTROPIC 。
1.1.2.7 窗口销毁
- 一般情况下应用程序必须调用 DestroyWindow 函数销毁它创建的所有窗口。销毁窗口时如果窗口可见,系统会隐藏该窗口,然后删除与该窗口关联的任何内部数据。销毁窗口后窗口句柄就会失效,此后该窗口句柄就不能再被使用。
- 在销毁窗口之前应保存或删除与窗口关联的任何数据,并释放为该窗口分配的任何系统资源。如果不主动释放资源,操纵系统将会释放应用程序未释放的任何资源。
- 销毁窗口实例不影响该窗口的窗口类。
- 销毁窗口也会销毁窗口的后代窗口。 DestroyWindow 函数首先将 WM_DESTROY 消息发送到窗口,然后发送到其子窗口和后代窗口。 这样所有相关窗口都会被销毁。
- 当点击“关闭”时,具有窗口菜单的窗口会收到WM_CLOSE消息。可以在此消息处理逻辑中提示用户进行确认。如果用户确认应销毁窗口,可以调用 DestroyWindow 函数来销毁窗口。
- 如果正在销毁的窗口是激活窗口,则激活状态和焦点状态都会转移到另一个窗口。将要被激活的窗口由 ALT+ESC 组合键决定。
1.1.3 使用窗口
1.1.3.1 创建主窗口
- 大多使用 WS_OVERLAPPEDWINDOW 样式来创建主窗口。
- 设置 CW_USEDEFAULT 标记可以设置使用系统默认的窗口位置和尺寸。
- 系统在创建主窗口后不会自动显示它,所以在创建main窗口后需要调用ShowWindow函数,传入 SW_SHOWDEFAULT 标志,以允许启动应用程序的程序来设置主窗口的初始显示状态。 UpdateWindow 函数会向窗口发送其第一个 WM_PAINT 消息。
1.1.3.2 创建、枚举子窗口并调整其大小
- 可以使用子窗口将窗口的工作区划分为不同的功能区域。
- 子窗口必须指定 WS_CHILD 样式,并在创建时指定父窗口。
1.1.3.3 销毁窗口
- 应用程序在销毁窗口之前发送 WM_CLOSE 消息,使窗口有机会在销毁窗口之前提示用户进行确认。用户确认后需要调用 DestroyWindow 函数来销毁窗口。
- 系统从屏幕中删除窗口后,会发送 WM_DESTROY 消息给该窗口。了响应 WM_DESTROY ,窗口会保存其数据并释放其分配的任何资源。主窗口最后通过调用 PostQuitMessage 函数退出应用程序。
1.1.3.4 使用分层窗口
- 若要使对话框出现半透明效果,先正常创建对话框,然后在 WM_INITDIALOG 消息处理中设置 WS_EX_LAYERED 分层扩展样式,并调用 SetLayeredWindowAttributes 传入透明度值来设置透明度。透明度值范围为0到255,其中0表示完全透明,255表示完全不透明。
- 若要使此窗口再次完全不透明,需要调用 SetWindowLong 删除 WS_EX_LAYERED 扩展样式,并调用 RedrawWindow 函数来重新绘制该窗口及其所有子窗口。
1.2 窗口参考目录
1.2.1 窗口常量
1.2.2 窗口函数
- 窗口函数。
1.2.3 窗口宏
- 窗口宏。
1.2.4 窗口消息
- 窗口消息。
1.2.5 窗口通知
- 窗口通知。