使用多文档接口

本部分介绍如何执行以下任务:

为了说明这些任务,本部分包括 Multipad 的示例,Multipad 是 MDI) 应用程序 (典型的多文档接口。

注册子级和框架窗口类

典型的 MDI 应用程序必须注册两个窗口类:一个用于其框架窗口,一个用于其子窗口。 如果应用程序支持多种类型的文档 (例如电子表格和图表) ,则必须为每个类型注册一个窗口类。

框架窗口的类结构类似于非 MDI 应用程序中main窗口的类结构。 MDI 子窗口的类结构与非 MDI 应用程序中子窗口的结构略有不同,如下所示:

  • 类结构应具有图标,因为用户可以最小化 MDI 子窗口,就像它是普通应用程序窗口一样。
  • 菜单名称应为 NULL,因为 MDI 子窗口不能有自己的菜单。
  • 类结构应在窗口结构中保留额外的空间。 使用此空间,应用程序可以将数据(如文件名)与特定的子窗口相关联。

以下示例演示 Multipad 如何注册其框架和子窗口类。

BOOL WINAPI InitializeApplication() 
{ 
    WNDCLASS wc; 
 
    // Register the frame window class. 
 
    wc.style         = 0; 
    wc.lpfnWndProc   = (WNDPROC) MPFrameWndProc; 
    wc.cbClsExtra    = 0; 
    wc.cbWndExtra    = 0; 
    wc.hInstance     = hInst; 
    wc.hIcon         = LoadIcon(hInst, IDMULTIPAD); 
    wc.hCursor       = LoadCursor((HANDLE) NULL, IDC_ARROW); 
    wc.hbrBackground = (HBRUSH) (COLOR_APPWORKSPACE + 1); 
    wc.lpszMenuName  = IDMULTIPAD; 
    wc.lpszClassName = szFrame; 
 
    if (!RegisterClass (&wc) ) 
        return FALSE; 
 
    // Register the MDI child window class. 
 
    wc.lpfnWndProc   = (WNDPROC) MPMDIChildWndProc; 
    wc.hIcon         = LoadIcon(hInst, IDNOTE); 
    wc.lpszMenuName  = (LPCTSTR) NULL; 
    wc.cbWndExtra    = CBWNDEXTRA; 
    wc.lpszClassName = szChild; 
 
    if (!RegisterClass(&wc)) 
        return FALSE; 
 
    return TRUE; 
} 

创建框架和子窗口

注册其窗口类后,MDI 应用程序可以创建其窗口。 首先,它使用 CreateWindow 或 CreateWindowEx 函数创建其框架窗口。 创建其框架窗口后,应用程序再次使用 CreateWindowCreateWindowEx 创建其客户端窗口。 应用程序应将 MDICLIENT 指定为客户端窗口的类名称; MDICLIENT 是由系统定义的预注册窗口类。 CreateWindow 或 CreateWindowExlpvParam 参数应指向 CLIENTCREATESTRUCT 结构。 此结构包含下表中所述的成员:

成员 说明
hWindowMenu 用于控制 MDI 子窗口的窗口菜单的句柄。 创建子窗口时,应用程序将其标题作为菜单项添加到窗口菜单中。 然后,用户可以通过单击子窗口菜单上的标题来激活子窗口。
idFirstChild 指定第一个 MDI 子窗口的标识符。 为创建的第一个 MDI 子窗口分配此标识符。 使用递增的窗口标识符创建其他窗口。 当子窗口被销毁时,系统会立即重新分配窗口标识符,使其范围保持连续。

 

将子窗口的标题添加到窗口菜单时,系统会为子窗口分配标识符。 当用户单击子窗口的标题时,框架窗口会收到一条 WM_COMMAND 消息,其中包含 wParam 参数中的标识符。 应为 idFirstChild 成员指定一个值,该值与框架窗口菜单中的菜单项标识符不冲突。

多板的框架窗口过程在处理 WM_CREATE 消息时创建 MDI 客户端窗口。 以下示例演示如何创建客户端窗口。

case WM_CREATE: 
    { 
        CLIENTCREATESTRUCT ccs; 
 
        // Retrieve the handle to the window menu and assign the 
        // first child window identifier. 
 
        ccs.hWindowMenu = GetSubMenu(GetMenu(hwnd), WINDOWMENU); 
        ccs.idFirstChild = IDM_WINDOWCHILD; 
 
        // Create the MDI client window. 
 
        hwndMDIClient = CreateWindow( "MDICLIENT", (LPCTSTR) NULL, 
            WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL, 
            0, 0, 0, 0, hwnd, (HMENU) 0xCAC, hInst, (LPSTR) &ccs); 
 
        ShowWindow(hwndMDIClient, SW_SHOW); 
    } 
    break; 

子窗口的标题将添加到窗口菜单的底部。 如果应用程序使用 AppendMenu 函数将字符串添加到窗口菜单,则每当) 创建或销毁子窗口时, (重新绘制窗口菜单时,子窗口的标题都可以覆盖这些字符串。 向其窗口菜单添加字符串的 MDI 应用程序应使用 InsertMenu 函数并验证子窗口的标题是否未覆盖这些新字符串。

使用 WS_CLIPCHILDREN 样式创建 MDI 客户端窗口,以防止窗口在其子窗口上绘制。

编写主消息循环

MDI 应用程序的main消息循环类似于处理加速键的非 MDI 应用程序。 区别在于,MDI 消息循环在检查应用程序定义的加速键之前或在调度消息之前调用 TranslateMDISysAccel 函数。

以下示例演示典型 MDI 应用程序的消息循环。 请注意,如果出现错误, GetMessage 可以返回 -1。

MSG msg;
BOOL bRet;

while ((bRet = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0)
{
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else 
    { 
        if (!TranslateMDISysAccel(hwndMDIClient, &msg) && 
                !TranslateAccelerator(hwndFrame, hAccel, &msg))
        { 
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        } 
    } 
}

TranslateMDISysAccel 函数将WM_KEYDOWN消息转换为WM_SYSCOMMAND消息,并将其发送到活动的 MDI 子窗口。 如果消息不是 MDI 加速器消息,则函数返回 FALSE,在这种情况下,应用程序使用 TranslateAccelerator 函数确定是否按下了任何应用程序定义的快捷键。 如果没有,循环会将消息调度到相应的窗口过程。

编写框架窗口过程

MDI 框架窗口的窗口过程类似于非 MDI 应用程序的main窗口。 区别在于框架窗口过程会将它不处理的所有消息传递给 DefFrameProc 函数,而不是 DefWindowProc 函数。 此外,框架窗口过程还必须传递它确实处理的某些消息,包括下表中列出的消息。

消息 响应
WM_COMMAND 激活用户选择的 MDI 子窗口。 当用户从 MDI 框架窗口的窗口菜单中选择 MDI 子窗口时,将发送此消息。 此消息附带的窗口标识符标识要激活的 MDI 子窗口。
WM_MENUCHAR 当用户按下 Alt+ - (减去) 组合键时,打开活动 MDI 子窗口的窗口菜单。
WM_SETFOCUS 将键盘焦点传递到 MDI 客户端窗口,后者又将其传递到活动的 MDI 子窗口。
WM_SIZE 调整 MDI 客户端窗口的大小以适应新框架窗口的工作区。 如果框架窗口过程将 MDI 客户端窗口的大小调整为不同的大小,则它不应将消息传递给 DefWindowProc 函数。

 

Multipad 中的框架窗口过程称为 MPFrameWndProc。 MPFrameWndProc 处理其他消息的方式与非 MDI 应用程序类似。 Multipad 中的WM_COMMAND 消息由本地定义的 CommandHandler 函数处理。 对于 Multipad 不处理的命令消息,CommandHandler 调用 DefFrameProc 函数。 如果 Multipad 默认情况下不使用 DefFrameProc ,则用户无法从窗口菜单激活子窗口,因为通过单击窗口的菜单项发送 的WM_COMMAND 消息将丢失。

编写子窗口过程

与框架窗口过程一样,默认情况下,MDI 子窗口过程使用特殊函数来处理消息。 子窗口过程不处理的所有消息都必须传递到 DefMDIChildProc 函数,而不是 DefWindowProc 函数。 此外,某些窗口管理消息必须传递到 DefMDIChildProc,即使应用程序处理该消息,才能使 MDI 正常工作。 下面是应用程序必须传递到 DefMDIChildProc 的消息

消息 响应
WM_CHILDACTIVATE 在调整 MDI 子窗口大小、移动或显示时执行激活处理。 必须传递此消息。
WM_GETMINMAXINFO 根据 MDI 客户端窗口的当前大小计算最大化的 MDI 子窗口的大小。
WM_MENUCHAR 将消息传递到 MDI 框架窗口。
WM_MOVE 重新计算 MDI 客户端滚动条(如果存在)。
WM_SETFOCUS 激活子窗口(如果子窗口不是活动的 MDI 子窗口)。
WM_SIZE 执行更改窗口大小所需的操作,尤其是最大化或还原 MDI 子窗口。 未能将此消息传递到 DefMDIChildProc 函数会产生非常不理想的结果。
WM_SYSCOMMAND 处理以前称为系统) 菜单命令的窗口 (: SC_NEXTWINDOWSC_PREVWINDOWSC_MOVESC_SIZESC_MAXIMIZE

 

创建子窗口

若要创建 MDI 子窗口,应用程序可以调用 CreateMDIWindow 函数或向 MDI 客户端窗口发送 WM_MDICREATE 消息。 (应用程序可以使用具有WS_EX_MDICHILD样式的 CreateWindowEx 函数来创建 MDI 子窗口。) 单线程 MDI 应用程序可以使用任一方法创建子窗口。 多线程 MDI 应用程序中的线程必须使用 CreateMDIWindowCreateWindowEx 函数在不同的线程中创建子窗口。

WM_MDICREATE消息的 lParam 参数是指向 MDICREATESTRUCT 结构的远指针。 结构包括四个维度成员: xy(指示窗口的水平和垂直位置)和 cxcy(指示窗口的水平和垂直范围)。 这些成员中的任何一个都可以由应用程序显式分配,也可以设置为 CW_USEDEFAULT,在这种情况下,系统根据级联算法选择一个位置、大小或两者。 在任何情况下,都必须初始化所有四个成员。 多板对所有维度使用 CW_USEDEFAULT

MDICREATESTRUCT 结构的最后一个成员是样式成员,它可能包含窗口的样式位。 若要创建可以具有任意窗口样式组合的 MDI 子窗口,请指定 MDIS_ALLCHILDSTYLES 窗口样式。 如果未指定此样式,则 MDI 子窗口将 WS_MINIMIZEWS_MAXIMIZEWS_HSCROLLWS_VSCROLL 样式作为默认设置。

多板使用源文件 MPFILE 中本地定义的 AddFile 函数 (创建其 MDI 子窗口。C) 。 AddFile 函数通过将窗口 MDICREATESTRUCT 结构的 szTitle 成员分配给正在编辑的文件的名称或“无标题”来设置子窗口的标题。szClass 成员设置为在 Multipad 的 InitializeApplication 函数中注册的 MDI 子窗口类的名称。 hOwner 成员设置为应用程序的实例句柄。

以下示例演示 Multipad 中的 AddFile 函数。

HWND APIENTRY AddFile(pName) 
TCHAR * pName; 
{ 
    HWND hwnd; 
    TCHAR sz[160]; 
    MDICREATESTRUCT mcs; 
 
    if (!pName) 
    { 
 
        // If the pName parameter is NULL, load the "Untitled" 
        // string from the STRINGTABLE resource and set the szTitle 
        // member of MDICREATESTRUCT. 
 
        LoadString(hInst, IDS_UNTITLED, sz, sizeof(sz)/sizeof(TCHAR)); 
        mcs.szTitle = (LPCTSTR) sz; 
    } 
    else 
 
        // Title the window with the full path and filename, 
        // obtained by calling the OpenFile function with the 
        // OF_PARSE flag, which is called before AddFile(). 
 
        mcs.szTitle = of.szPathName; 
 
    mcs.szClass = szChild; 
    mcs.hOwner  = hInst; 
 
    // Use the default size for the child window. 
 
    mcs.x = mcs.cx = CW_USEDEFAULT; 
    mcs.y = mcs.cy = CW_USEDEFAULT; 
 
    // Give the child window the default style. The styleDefault 
    // variable is defined in MULTIPAD.C. 
 
    mcs.style = styleDefault; 
 
    // Tell the MDI client window to create the child window. 
 
    hwnd = (HWND) SendMessage (hwndMDIClient, WM_MDICREATE, 0, 
        (LONG) (LPMDICREATESTRUCT) &mcs); 
 
    // If the file is found, read its contents into the child 
    // window's client area. 
 
    if (pName) 
    { 
        if (!LoadFile(hwnd, pName)) 
        { 
 
            // Cannot load the file; close the window. 
 
            SendMessage(hwndMDIClient, WM_MDIDESTROY, 
                (DWORD) hwnd, 0L); 
        } 
    } 
    return hwnd; 
} 

WM_MDICREATE消息的 lParam 参数中传递的指针将传递到 CreateWindow 函数,并显示为 CREATESTRUCT 结构中的第一个成员,在WM_CREATE消息中传递。 在 Multipad 中,子窗口在 WM_CREATE 消息处理期间通过在其额外数据中初始化文档变量和创建编辑控件的子窗口来初始化自身。