Поделиться через


Использование интерфейса нескольких документов

В этом разделе объясняется, как выполнять следующие задачи:

Чтобы проиллюстрировать эти задачи, в этом разделе содержатся примеры из Multipad, типичное приложение многодокументного интерфейса (MDI).

Регистрация дочерних и оконных классов фреймов

Обычное приложение MDI должно зарегистрировать два класса окон: один для окна фрейма и один для дочерних окон. Если приложение поддерживает несколько типов документов (например, электронную таблицу и диаграмму), необходимо зарегистрировать класс окна для каждого типа.

Структура классов для окна фрейма аналогична структуре класса для главного окна в приложениях, отличных от MDI. Структура классов для дочерних окон 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. После создания окна фрейма приложение снова создает окно клиента с помощью CreateWindow или CreateWindowEx. Приложение должно указать MDICLIENT в качестве имени класса клиентского окна; MDICLIENT — это предварительно зарегистрированный класс окна, определенный системой. Параметр lpvParamCreateWindow или CreateWindowEx должен указывать на структуру CLIENTCREATESTRUCT. Эта структура содержит элементы, описанные в следующей таблице:

Член Описание
hWindowMenu Дескриптор меню окна, используемого для управления дочерними окнами MDI. По мере создания дочерних окон приложение добавляет свои заголовки в меню окна в виде элементов меню. Затем пользователь может активировать дочернее окно, щелкнув его заголовок в меню окна.
idFirstChild Указывает идентификатор первого дочернего окна MDI. Первое дочернее окно MDI, созданное, назначается этому идентификатору. Дополнительные окна создаются с добавочными идентификаторами окон. При уничтожении дочернего окна система немедленно переназначает идентификаторы окна, чтобы сохранить их диапазон непрерывным.

 

При добавлении заголовка дочернего окна в меню окна система назначает идентификатор дочернему окну. Когда пользователь щелкает заголовок дочернего окна, основное окно получает сообщение WM_COMMAND с идентификатором в параметре wParam. Необходимо указать значение для элемента idFirstChild, который не конфликтует с идентификаторами элементов меню в меню окна фрейма.

Процедура основного окна многопанельного приложения создает окно клиента MDI при обработке сообщения WM_CREATE. В следующем примере показано, как создается окно клиента.

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-приложения аналогичен циклу обработки клавиш ускорителя в приложениях без 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. Разница заключается в том, что процедура окна кадра передает все сообщения, которые не обрабатываются функции DefFrameProc, а не в функцию DefWindowProc. Кроме того, процедура окна кадров также должна передавать некоторые сообщения, которые он обрабатывает, включая те, которые перечислены в следующей таблице.

Сообщение Ответ
WM_COMMAND Активирует дочернее окно MDI, выбранное пользователем. Это сообщение отправляется, когда пользователь выбирает дочернее окно MDI из меню окна в рамке окна MDI. Идентификатор окна, сопровождающий это сообщение, определяет дочернее окно MDI для активации.
WM_MENUCHAR Открывает меню окна активного дочернего окна MDI, когда пользователь нажимает сочетание клавиш ALT+ (минус).
WM_SETFOCUS Передает фокус клавиатуры в окно клиента MDI, которое, в свою очередь, передает его активному дочернему окну MDI.
WM_SIZE Изменяет размер окна клиента MDI в соответствии с клиентской областью нового окна фрейма. Если процедура окна рамки изменяет размер окна клиента MDI, она не должна передавать сообщение в функцию DefWindowProc.

 

Процедура окна кадра в Multipad называется MPFrameWndProc. Обработка других сообщений MPFrameWndProc аналогична обработке в приложениях, не являющихся MDI. WM_COMMAND сообщения в Multipad обрабатываются локально определенной функцией 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_NEXTWINDOW, SC_PREVWINDOW, SC_MOVE, SC_SIZEи SC_MAXIMIZE.

 

Создание дочернего окна

Чтобы создать дочернее окно MDI, приложение может вызвать функцию CreateMDIWindow или отправить сообщение WM_MDICREATE в окно клиента MDI. (Приложение может использовать функцию CreateWindowExс стилем WS_EX_MDICHILD для создания дочерних окон MDI.) Однопоточное приложение MDI может использовать любой метод для создания дочернего окна. В многопоточном MDI-приложении поток должен использовать функцию CreateMDIWindow или CreateWindowEx, чтобы создать дочернее окно в другом потоке.

Параметр lParam сообщения WM_MDICREATE является далёким указателем на структуру MDICREATESTRUCT. Структура включает четыре элемента измерения: x и y, которые указывают горизонтальные и вертикальные позиции окна, а cx и cy, которые указывают на горизонтальные и вертикальные экстенты окна. Любой из этих членов может быть явно назначен приложением, или они могут быть заданы CW_USEDEFAULT, в таком случае система выбирает позицию, размер или и то, и другое в соответствии с каскадным алгоритмом. В любом случае все четыре члена должны быть инициализированы. Multipad использует CW_USEDEFAULT для всех измерений.

Последний элемент структуры MDICREATESTRUCT является элементом стиля, который может содержать биты стиля для окна. Чтобы создать дочернее окно MDI, которое может иметь любое сочетание стилей окон, укажите стиль окна MDIS_ALLCHILDSTYLES. Если этот стиль не указан, дочернее окно MDI имеет WS_MINIMIZE, WS_MAXIMIZE, WS_HSCROLLи WS_VSCROLL стили в качестве параметров по умолчанию.

Multipad создает дочерние окна MDI с помощью локально определенной функции AddFile (расположенной в исходном ФАЙЛЕ MPFILE). C). Функция AddFile задает заголовок дочернего окна, назначив член szTitle структуры MDICREATESTRUCT окна либо имени редактируемого файла, либо "Без названия". Член szClass установлен в имя класса дочернего окна MDI, зарегистрированного в функции InitializeApplication Multipad. Для элемента hOwner задан дескриптор экземпляра приложения.

В следующем примере показана функция AddFile в Multipad.

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; 
} 

Указатель, переданный в параметре lParam сообщения WM_MDICREATE, передается в функцию CreateWindow и отображается в качестве первого члена в структуре CREATESTRUCT, переданной в сообщении WM_CREATE. В Мультипаде дочернее окно инициализируется во время обработки сообщений WM_CREATE путем инициализации переменных документа в дополнительных данных и путем создания дочернего окна элемента управления редактирования.