前言:
学习笔记,随时更新。如有谬误,欢迎指正。
说明:
- 红色字体为较为重要部分。
- 绿色字体为个人理解部分。
原文链接:https://learn.microsoft.com/en-us/windows/win32/winmsg/multiple-document-interface
1 多文档接口( MDI )
1.1 MDI 概述
1.1.1 关于多文档接口
1.1.1.1 框架窗口、客户端窗口和( MDI )子窗口
- MDI 应用程序有三种类型的窗口:框架窗口、MDI 客户端窗口以及许多 MDI 子窗口。
- 框架窗口类似于应用程序的主窗口:它具有调整大小边框、标题栏、窗口菜单、最小化按钮和最大化按钮。应用程序必须为框架窗口注册一个窗口类,并提供窗口程序。MDI 应用程序不会在框架窗口的工作区中显示输出,而是显示 MDI 客户端窗口。
- MDI 客户端窗口是一种特殊类型的子窗口,属于预注册的窗口类 MDICLIENT 。客户端窗口是框架窗口的子窗口,它用作 MDI 子窗口的背景。它还提供了创建和操作 MDI 子窗口的支持。例如,MDI 应用程序可以通过向 MDI 客户端窗口发送消息来创建、激活或最大化子窗口。
- 当用户打开或创建文档时,客户端窗口将创建文档的 MDI 子窗口。客户端窗口是应用程序中所有 MDI 子窗口的父窗口。每个 MDI 子窗口都有一个大小调整边框、一个标题栏、一个窗口菜单、一个最小化按钮和一个最大化按钮。由于子窗口已剪裁,因此它仅限于客户端窗口,并且不能显示在客户端窗口的外部。
- MDI 应用程序可以支持多种类型的文档。对于它支持的每种文档类型,MDI 应用程序必须注册一个子窗口类,并提供一个窗口程序程来支持属于该类的窗口。
1.1.1.2 子窗口创建
- 创建 MDI 子窗口有多种方式(多线程应用程序中不能使用第二种方式,单线程应用程序中三种方式都可以使用):
- MDI 应用程序调用 CreateMDIWindow 函数。
- MDI 应用程序将 WM_MDICREATE 消息发送到 MDI 客户端窗口。
- MDI 应用程序调用 CreateWindowEx 函数,指定 WS_EX_MDICHILD 扩展样式(更有效方法)。
- 为了销毁子窗口,MDI 应用程序会将 WM_MDIDESTROY 消息发送到 MDI 客户端窗口。
1.1.1.3 子窗口激活
- MDI 客户端窗口中一次可以显示任意数量的 MDI 子窗口,但只能有一个 MDI 子窗口处于活动状态。激活的 MDI 子窗口位于所有其他子窗口的前面,并突出显示其边框。
- 用户可以通过单击非活动子窗口来激活该窗口。
- MDI 应用程序通过向 MDI 客户端窗口发送 WM_MDIACTIVATE 消息来激活 MDI 子窗口。当 MDI 客户端窗口处理此消息时,它会将 WM_MDIACTIVATE 消息发送到将要被激活的 MDI 子窗口的窗口程序,以及将要被反激活的 MDI 子窗口的窗口程序。
- 若要防止 MDI 子窗口被激活,则在处理 MDI 子窗口的 WM_NCACTIVATE 消息时返回 FALSE 即可。
- 系统记录每个 MDI 子窗口在重叠窗口堆栈中的位置。这种堆叠顺序被称为 Z-Order 。用户可以通过在激活状态窗口的窗口菜单中单击 next 来按 Z-Order 来激活下一个子窗口。应用程序通过向 MDI 客户端窗口发送 WM_MDINEXT 消息,以 Z-Order 来激活下一个(或上一个)子窗口。
- 为了获取激活状态的子窗口的句柄,MDI 应用程序会将 WM_MDIGETACTIVE 消息发送到 MDI 客户端窗口。
1.1.1.4 多文档菜单
- MDI 应用程序的框架窗口应包含带有窗口菜单的菜单栏。窗口菜单应包括在 MDI 客户端窗口中排列子窗口或关闭所有子窗口的项。 典型 MDI 应用程序的窗口菜单可能包含下列的项:
- Tile :以平铺的方式排列子窗口。每个子窗口全部显示在 MDI 客户端窗口中。
- Cascade :以层叠的方式排列子窗口。子窗口相互重叠,但每个窗口的标题栏是可见的。
- Arrange Icons :沿 MDI 客户端窗口底部排列最小化子窗口的图标。
- Close All :关闭所有子窗口。
- 每当创建 MDI 子窗口时,系统都会自动将新的菜单项追加到该窗口菜单中。菜单项的文本与新的 MDI 子窗口的菜单栏上的文本相同。通过单击菜单项,用户可以激活相应的 MDI 子窗口。销毁 MDI 子窗口时,系统会自动从窗口菜单中删除相应的菜单项。
- 系统最多可向窗口菜单添加十个菜单项。当创建第十个 MDI 子窗口时,系统会将 “更多窗口 ”项添加到窗口菜单。单击此项将显示 “选择窗口 ”对话框。 该对话框包含一个列表框,其中包含当前可用的所有 MDI 子窗口的标题。用户可以通过单击列表框中的标题来激活 MDI 子窗口。
- 如果 MDI 应用程序支持多种类型的子窗口,请定制菜单栏以反映与激活窗口相关联的操作。为此,请为应用程序支持的每种 MDI 子窗口类型提供单独的菜单资源。激活新类型的 MDI 子窗口时,应用程序应向 MDI 客户端窗口发送 WM_MDISETMENU 消息,并将相应菜单的句柄传递给它。
1.1.1.5 多文档加速器
- 若要接收和处理其 MDI 子窗口的快捷键,MDI 应用程序的消息循环中必须包含 TranslateMDISysAccel 函数。在调用 TranslateAccelerator 或 DispatchMessage 函数之前,循环必须调用 TranslateMDISysAccel 。
- MDI 子窗口窗口菜单上的快捷键与非 MDI 子窗口的快捷键不同。在 MDI 子窗口中,ALT + – (减号) 组合键将打开窗口菜单,CTRL + F4 组合键关闭活动子窗口,CTRL + F6 组合键激活下一个子窗口。
1.1.1.6 子窗口大小和排列方式
- MDI 应用程序通过将消息发送到 MDI 客户端窗口来控制其子窗口的大小和位置。
- 为了最大化激活状态的 MDI 子窗口,应用程序会将 WM_MDIMAXIMIZE 消息发送到 MDI 客户端窗口。当 MDI 子窗口最大化时,其工作区将完全填充 MDI 客户端窗口。此外,系统会自动隐藏 MDI 子窗口的标题栏,并将子窗口的窗口菜单图标和还原按钮添加到 MDI 应用程序的菜单栏。应用程序可以通过向 MDI 客户端窗口发送 WM_MDIRESTORE 消息,将 MDI 客户端窗口还原到其原始 ( 预先) 大小和位置。
- MDI 应用程序可以以层叠或平铺的方式排列其子窗口。
- 当子窗口层叠时,这些窗口出现在一个栈中。栈底部的窗口占据屏幕的左上角,其余窗口垂直和水平偏移,以便每个子窗口的左边框和标题栏可见。MDI 应用程序向 MDI 客户端窗口发送 WM_MDICASCADE 消息以使 MDI 子窗口以层叠方式排列。通常,当用户单击窗口菜单上的 “层叠” 时,应用程序会发送此消息。
- 当子窗口平铺时,系统将完整地显示每个子窗口,并不会重叠任何窗口。所有MDI 子窗口的大小都是能适应 MDI 客户端窗口尺寸的必要值(多个 MDI 子窗口能完全瓜分 MDI 客户端窗口的区域)。MDI 应用程序向 MDI 客户端窗口发送 WM_MDITILE 消息以使 MDI 子窗口以平铺方式排列。通常,应用程序在用户单击窗口菜单上的“平铺”时发送此消息。
- MDI 应用程序应该为它支持的每种类型的子窗口提供不同的图标。应用程序在注册子窗口类时会指定一个图标。当 MDI 子窗口被最小化时,系统会自动在 MDI 客户端窗口的下方显示 MDI 子窗口的图标。MDI应用程序通过向 MDI 客户端窗口发送 WM_MDIICONARRANGE 消息来指导系统排列 MDI 子窗口图标。通常,当用户单击窗口菜单上的“排列图标”时,应用程序会发送此消息。
1.1.1.7 图标标题窗口
- 由于MDI子窗口可以被最小化,因此在 MDI 应用程序中,要避免像操作普通的 MDI 子窗口那样操作图标标题窗口。当应用程序枚举 MDI 客户端窗口的 MDI 子窗口时,会包括图标标题窗口。然而,图标标题窗口与其他 MDI 子窗口不同,因为它们是 MDI 子窗口的一部分。简单来说,图标标题窗口是 MDI 子窗口的一种特殊类型,操作时要注意区分。要确定子窗口是否是图标标题窗口,请使用带有 GW_OWNER 索引的 GetWindow 函数。无标题窗口返回 NULL 。注意,这个测试对于顶级窗口是不够的,因为菜单和对话框是被拥有的窗口。
1.1.1.8 子窗口数据
- 由于子窗口的数量因用户打开的文档数而异,因此 MDI 应用程序必须能够将数据(例如,当前文件的名称) 与每个 MDI 子窗口关联。可通过两种方式实现此目的:
- 在窗口结构体中存储子窗口数据。
- 使用窗口属性。
1.1.1.8.1 窗口结构体
- 当 MDI 应用程序注册窗口类时,可能会在窗口结构体中为特定于此特定窗口类的应用程序数据保留额外的空间。应用程序可以使用 GetWindowLong 和 SetWindowLong 函数在此额外空间中存储和查找数据。
- 若要为 MDI 子窗口维护大量数据,应用程序可以为数据结构分配内存,然后将包含该结构的内存的句柄存储在与子窗口关联的额外空间中。
1.1.1.8.2 窗口属性
- MDI 应用程序还可以通过使用窗口属性来存储每个文档的数据。每个文档数据是特定于特定子窗口中包含的文档类型的数据。属性与窗口结构中的额外空间不同,在注册窗口类时不需要分配额外空间。一个窗口可以有任意数量的属性。此外,窗口结构中的额外空间是通过偏移量来访问的,而属性则是由字符串名称来访问。有关窗口属性的详细信息,请参见窗口属性。
1.1.2 使用多文档接口
- 示例:https://learn.microsoft.com/zh-cn/windows/win32/winmsg/using-the-multiple-document-interface。
- MDI 框架窗口的默认窗口程序是 DefFrameProc 函数,而不是 DefWindowProc 函数。
- MDI 子窗口的默认窗口程序是 DefMDIChildProc 函数,而不是 DefWindowProc 函数。