关于多文档界面

多文档界面(MDI)应用程序中的每个文档都显示在应用程序主窗口工作区内的单独子窗口中。 典型的 MDI 应用程序包括字处理应用程序,这些应用程序允许用户处理多个文本文档,以及允许用户处理多个图表和电子表格的电子表格应用程序。 有关详细信息,请参阅以下主题。

框架窗口、客户端窗口和子窗口

MDI 应用程序有三种类型的窗口:框架窗口、MDI 客户端窗口以及许多子窗口。 框架窗口类似于应用程序的主窗口:它具有大小调整边框、标题栏、窗口菜单、最小化按钮和最大化按钮。 应用程序必须注册框架窗口的窗口类,并提供一个窗口过程来支持它。

MDI 应用程序不会在框架窗口的客户端区域中显示输出。 而是显示 MDI 客户端窗口。 MDI 客户端窗口是属于预注册窗口类 MDICLIENT 的特殊子窗口类型。 客户端窗口是框架窗口的子窗口;它充当子窗口的背景。 它还支持创建和操作子窗口。 例如,MDI 应用程序可以通过将消息发送到 MDI 客户端窗口来创建、激活或最大化子窗口。

当用户打开或创建文档时,客户端窗口将为文档创建子窗口。 客户端窗口是应用程序中所有 MDI 子窗口的父窗口。 每个子窗口都有大小调整边框、标题栏、窗口菜单、最小化按钮和最大化按钮。 由于子窗口被剪裁,因此它仅限于客户端窗口,并且不能显示在该窗口外部。

MDI 应用程序可以支持多种文档。 例如,典型的电子表格应用程序使用户能够同时使用图表和电子表格。 对于它支持的每种文档类型,MDI 应用程序必须注册子窗口类,并提供一个窗口过程来支持属于该类的窗口。 有关窗口类的详细信息,请参阅 窗口类。 有关窗口过程的详细信息,请参阅 “窗口过程”。

下面是典型的 MDI 应用程序。 它名为 Multipad。

多窗口 MDI 应用程序框架窗口和客户端窗口的屏幕截图。

创建子窗口

若要创建子窗口,MDI 应用程序调用 CreateMDIWindow 函数或将 WM_MDICREATE 消息发送到 MDI 客户端窗口。 创建 MDI 子窗口的更高效方法是调用 CreateWindowEx 函数,并指定 WS_EX_MDICHILD 扩展样式。

若要销毁子窗口,MDI 应用程序会将 WM_MDIDESTROY 消息发送到 MDI 客户端窗口。

子窗口激活

任意数量的子窗口都可以在任何一次出现在客户端窗口中,但只能有一个子窗口处于活动状态。 活动子窗口位于所有其他子窗口前面,并突出显示其边框。

用户可以通过单击该窗口来激活非活动子窗口。 MDI 应用程序通过将 WM_MDIACTIVATE 消息发送到 MDI 客户端窗口来激活子窗口。 当客户端窗口处理此消息时,它会将 WM_MDIACTIVATE 消息发送到要激活的子窗口的窗口过程以及正在停用的子窗口的窗口过程。

若要防止子窗口激活,请返回 FALSE 来处理子窗口的WM_NCACTIVATE消息。

系统跟踪每个子窗口在重叠窗口堆栈中的位置。 此堆叠称为 Z 顺序。 用户可以按 Z 顺序激活下一个子窗口,方法是单击活动窗口中的“ 下一步 ”菜单。 应用程序通过向客户端窗口发送 WM_MDINEXT 消息来按 Z 顺序激活下一个(或上一个)子窗口。

若要检索活动子窗口的句柄,MDI 应用程序会将 WM_MDIGETACTIVE 消息发送到客户端窗口。

多个文档菜单

MDI 应用程序的框架窗口应包含带有窗口菜单的菜单栏。 窗口菜单应包括在客户端窗口中排列子窗口或关闭所有子窗口的项目。 典型的 MDI 应用程序的窗口菜单可能包括下表中的项。

菜单项 目的
图块 以磁贴格式排列子窗口,以便每个窗口全部显示在客户端窗口中。
级 联 将子窗口以级联格式排列。 子窗口彼此重叠,但每个窗口的标题栏可见。
排列图标 沿客户端窗口底部排列最小化子窗口的图标。
全部关闭 关闭所有子窗口。

每当创建子窗口时,系统都会自动向窗口菜单追加一个新菜单项。 菜单项的文本与新子窗口菜单栏上的文本相同。 通过单击菜单项,用户可以激活相应的子窗口。 销毁子窗口时,系统会自动从窗口菜单中删除相应的菜单项。

系统最多可向窗口菜单添加十个菜单项。 创建第十个子窗口时,系统将 “更多 Windows ”项添加到窗口菜单中。 单击此项将显示 “选择窗口 ”对话框。 该对话框包含一个列表框,其中包含当前可用的所有 MDI 子窗口的标题。 用户可以通过单击列表框中的标题来激活子窗口。

如果 MDI 应用程序支持多种类型的子窗口,请根据活动窗口关联的操作定制菜单栏。 为此,请为应用程序支持的每种子窗口类型提供单独的菜单资源。 激活新类型的子窗口时,应用程序应向客户端窗口发送 WM_MDISETMENU 消息,并将其传递给相应的菜单的句柄。

如果不存在子窗口,菜单栏应仅包含用于创建或打开文档的项目。

当用户使用游标键浏览 MDI 应用程序的菜单时,键的行为与用户浏览典型应用程序的菜单的行为不同。 在 MDI 应用程序中,控件从应用程序的窗口菜单传递到活动子窗口的窗口菜单,然后传递到菜单栏上的第一项。

多个文档加速器

若要接收和处理其子窗口的加速键,MDI 应用程序必须在其消息循环中包含 TranslateMDISysAccel 函数。 在调用 TranslateAcceleratorDispatchMessage 函数之前,循环必须调用 TranslateMDISysAccel

MDI 子窗口在窗口菜单上的加速键与非 MDI 子窗口的加速键不同。 在 MDI 子窗口中,ALT+ – (减) 组合键将打开窗口菜单,Ctrl+F4 键组合关闭活动子窗口,Ctrl+F6 键组合将激活下一个子窗口。

子窗口尺寸和布局

MDI 应用程序通过将消息发送到 MDI 客户端窗口来控制其子窗口的大小和位置。 若要最大化活动子窗口,应用程序会将 WM_MDIMAXIMIZE 消息发送到客户端窗口。 当子窗口最大化时,其工作区将完全填充 MDI 客户端窗口。 此外,系统会自动隐藏子窗口的标题栏,并将子窗口的窗口菜单图标和“还原”按钮添加到 MDI 应用程序的菜单栏。 应用程序可以通过向客户端窗口发送 一条WM_MDIRESTORE 消息,将客户端窗口还原为其原始(预初始化)大小和位置。

MDI 应用程序可以按级联或磁贴格式排列其子窗口。 当子窗口级联时,窗口会显示在堆栈中。 堆栈底部的窗口占据屏幕的左上角,其余窗口垂直和水平偏移,以便每个子窗口的左边框和标题栏可见。 若要以级联格式排列子窗口,MDI 应用程序会发送 WM_MDICASCADE 消息。 通常,当用户单击窗口菜单上的 Cascade 时,应用程序会发送此消息。

当子窗口平铺时,系统显示每个子窗口,且不会与任何窗口重叠。 所有窗口都根据需要调整为适合客户端窗口的大小。 若要以磁贴格式排列子窗口,MDI 应用程序会将 WM_MDITILE 消息发送到客户端窗口。 通常,当用户单击窗口菜单上的 “磁贴 ”时,应用程序会发送此消息。

MDI 应用程序应为它支持的每种子窗口类型提供不同的图标。 应用程序在注册子窗口类时指定图标。 当子窗口最小化时,系统会自动在客户端窗口的下半部分显示子窗口的图标。 MDI 应用程序通过向客户端窗口发送 WM_MDIICONARRANGE 消息,引导系统排列子窗口图标。 通常,当用户单击窗口菜单上的“ 排列图标 ”时,应用程序会发送此消息。

图标标题窗口

由于 MDI 子窗口可能最小化,因此 MDI 应用程序必须避免像操作普通 MDI 子窗口那样操作这些图标标题窗口。 当应用程序枚举 MDI 客户端窗口的子窗口时,将显示图标标题窗口。 但是,图标标题窗口与其他子窗口不同,因为它们由 MDI 子窗口拥有。

若要确定子窗口是否为图标标题窗口,请使用具有GW_OWNER索引的 GetWindow 函数。 非标题窗口返回 NULL。 请注意,此测试不足以用于顶级窗口,因为菜单和对话框是拥有的窗口。

子窗口数据

由于子窗口的数量因用户打开的文档数而异,因此 MDI 应用程序必须能够将数据(例如,当前文件的名称)与每个子窗口相关联。 可以通过两种方式来执行此操作:

  • 在窗口结构中存储子窗口数据。
  • 使用窗口属性。

窗口结构

当 MDI 应用程序注册窗口类时,它可能会为特定于此特定窗口类的应用程序数据保留窗口结构中的额外空间。 若要在此额外空间中存储和检索数据,应用程序使用 GetWindowLongSetWindowLong 函数。

为了维护子窗口的大量数据,应用程序可以为数据结构分配内存,然后将句柄存储在与子窗口关联的额外空间中包含结构的内存中。

窗口属性

MDI 应用程序还可以使用窗口属性存储每文档数据。 每文档数据 是特定于特定子窗口中包含的文档类型的数据。 属性不同于窗口结构中的额外空间,即注册窗口类时不需要分配额外空间。 窗口可以具有任意数量的属性。 此外,如果使用偏移量访问窗口结构中的额外空间,则属性通过字符串名称来引用。 有关窗口属性的详细信息,请参阅 “窗口属性”。