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


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

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

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

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

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

Структура класса для окна фрейма аналогична структуре класса для окна main в приложениях, отличных от 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 — это предварительно зарегистрированный класс окна, определенный системой. Параметр lpvParamобъекта CreateWindow или CreateWindowEx должен указывать на структуру 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, чтобы предотвратить рисование окна дочерних окон.

Написание основного цикла сообщений

Цикл сообщений main приложения 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 аналогична процедуре окна main приложения, отличного от 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 окна имя редактируемого файла или значение Untitled. Член 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 сообщении. В Multipad дочернее окно инициализируется во время обработки сообщений WM_CREATE путем инициализации переменных документа в дополнительных данных и путем создания дочернего окна элемента управления редактированием.