Untitled
1. Многооконный интерфейс MDI
1.1. Окна MDI-приложения
1.2. Инициализация MDI-приложения
1.3. Изменения в цикле обработки сообщений
1.4. Функции окон MDI-приложения
1.5. Создание и уничтожение окна Document Window
1.6. Приложение MDIAPP
1.7. Добавление окон Toolbar и Statusbar
1.8. Приложение MDITB
1.9. Работа с окнами Document Window
Как пользователь операционной системы Windows вы, наверное, хорошо
знакомы с многооконным интерфейсом MDI (Multiple Document Interface),
позволяющим в одном приложении работать одновременно с несколькими
документами или с разными представлениями одного и того же документа.
Этот интерфейс описан в руководстве по разработке интерфейса пользователя
System Application Architecture Common User Access Advanced Interface
Design Guide (SAA/CUA), созданном IBM. Интерфейс MDI использован
в Windows, начиная с версии 3.0, в Windows NT, а также, разумеется,
в графической оболочке Presentation Manager операционной системы
OS/2.
В качестве примера приложения, использующего интерфейс MDI (MDI-приложения),
можно привести текстовый процессор Microsoft Word for Windows
версии 2.0. Рис. 1.1 иллюстрирует работу одновременно с двумя
документами, один из которых находится в файле wadvance.doc, расположенном
в текущем каталоге, а второй - в файле winmm-1.doc, расположенном
в каталоге winmm.
Рис. 1.1. Работа одновременно с двумя документами
Посмотрите на рис. 1.2. Из этого рисунка видно, что текстовый
процессор Microsoft Word for Windows способен также предоставить
пользователю два различных представления одного и того же документа.
В окне WINMM1.DOC:1 отображается обычное представление документа,
а в окне WINMM1.DOC:2 - представление в режиме Outline (режим
просмотра оглавления документа).
Рис. 1.2. Работа одновременно с двумя представлениями
одного и того же документа
Любое MDI-приложение содержит в главном меню строку "Windows",
при выборе которой на экране появляется временное (pop up) меню
"Windows", предназначенное для управления окнами, отображающими
различные документы или различные представления одного и того
же документа (пока мы будем называть окно документов окном Document
Window, позже уточним терминологию). На рис. 1.3. показано меню
"Windows" приложения Program Manager.
Рис. 1.3. Меню "Window" приложения Program Manager
Как правило, в меню "Windows" есть строки "Cascade"
и "Tile", с помощью которых пользователь может расположить
окна Document Window с перекрытием (друг за другом) или рядом
друг с другом. Могут быть и другие строки, управляющие расположением
окон Document Window. Например, если приложение допускает сворачивание
окон Document Window в пиктограммы, в меню "Windows"
присутствует строка "Arrange Icons", с помощью которой
пользователь может упорядочить расположение пиктограмм свернутых
окон.
Ниже разделительной черты в меню "Windows" находятся
строки, соответствующие окнам Document Window. Если выбрать одну
из этих строк, указанное окно будет активизировано и показано
на первом плане.
Состав меню "Windows" может быть различным в различных
MDI-приложениях, в чем вы можете убедиться сами. Однако в любом
случае это меню позволяет вам автоматически упорядочить расположение
окон Document Window и выбрать нужное окно из списка для активизации.
Поведение окон Document Window напоминает поведение дочерних окон.
В частности, двигая их при помощи заголовка, вы не сможете переместить
такие окна за пределы главного окна приложения. В то же время
окна Document Window напоминают перекрывающиеся окна, так как
их можно делать активными, при этом заголовок активного окна Document
Window выделяется цветом.
Каждое окно Document Window имеет, как правило, системное меню
(рис. 1.4) и кнопки изменения размера.
Рис. 1.4. Системное меню окна Document Window текстового
процессора Microsoft Word for Windows версии 2.0
С помощью системного меню пользователь может изменять размеры
или перемещать окно Document Window (строки "Restore",
"Move", "Size", "Maximize", "Minimize"),
закрывать окно (строка "Close"), передавать фокус ввода
от одного окна Document Window другому (строка "Next Window")
и выполнять другие действия, в зависимости от назначения приложения.
Для ускоренного доступа к функциям системного меню окна Document
Window используются акселераторы. Вы можете активизировать системное
меню окна Document Window мышью или при помощи комбинации клавиш
<Alt "">.
Если окно Document Window сворачивается в пиктограмму (минимизируется),
пиктограмма располагается в нижней части главного окна приложения
(рис. 1.5).
Рис. 1.5. Окна Document Window могут быть свернуты в пиктограмму
Если же увеличить размеры окна Document Window до максимальных
(при помощи кнопки максимизации или системного меню окна Document
Window), внешний вид главного меню приложения изменится (рис.
1.6).
Рис. 1.6. Изменение главного меню приложения при максимальном
увеличении размеров окна Document Window
Теперь в левой части главного меню находится пиктограмма системного
меню окна Document Window, а в правой - пиктограмма восстановления
размера окна Document Window.
Активное окно Document Window выделяется изменением цвета заголовка.
Здесь используется тот же способ выделения, что и для главного
окна активного приложения. Однако если активно MDI-приложение,
выделяются сразу два окна - главное окно MDI-приложения и активное
окно Document Window (если оно есть). Такая ситуация показана,
например, на рис. 1.1.
Способ создания новых окон Document Window и удаления имеющихся
полностью определяется приложением. Обычно новое окно Document
Window создается при создании нового документа (строка "New"
меню "File") или при загрузке уже существующего документа
для редактирования или просмотра (строка "Open" меню
"File").
Для того чтобы удалить окно Document Window, можно закрыть документ
(строка "Close" меню "File") или закрыть само
окно Document Window при помощи системного меню (если этот способ
предусмотрен приложением).
Как видите, поведение MDI-приложения выглядит достаточно сложно.
Вероятно, вы сможете создать такое приложение с использованием
обычных окон, но для этого придется затратить немало усилий. К
счастью, операционная система Windows начиная с версии 3.0 имеет
встроенную поддержку MDI-приложений, поэтому большинство из описанных
выше свойств окон Document Window реализуется Windows, а не приложением.
Даже с учетом поддержки операционной системы, "полновесные"
MDI-приложения выглядят достаточно громоздко. Примером может послужить
приложение MULTIPAD, исходные тексты которого поставляются вместе
с Microsoft SDK for Windows 3.1 и в составе примеров приложений
системы разработки Microsoft Visual C++. Приложение MULTIPAD слишком
сложно, для того чтобы начинать с него изучение интерфейса MDI,
однако оно является хорошим примером того, как нужно делать подобные
приложения. Мы рекомендуем вам после прочтения этой главы разобраться
в том, как оно работает.
Не огорчайтесь, если вам покажется, что исходные тексты этого,
в общем-то, простого приложения, очень сложны. На данном этапе
для вас важно понять принципы, положенные в основу интерфейса
MDI. В ближайших томах "Библиотеки системного программиста"
мы научим вас создавать такие приложения с использованием библиотек
классов Borland Object Windows и Microsoft Foundation Classes.
Объем исходных текстов таких приложений, снабженных к тому же
окнами Toolbar и Statusbar, намного меньше объема приложений,
составленных на "чистом" Си. К тому же современные средства
разработки позволяют создавать заготовку приложений автоматически,
генерируя все необходимые исходные тексты.
Тем не менее, мы не считаем, что рассказ об интерфейсе MDI следует
отложить "до лучших времен" (т. е. до тех времен, когда
вы будете использовать в своих разработках готовые библиотеки
классов). Несмотря на то, что библиотеки классов скрывают многие
детали внутренних процессов операционной системы Windows, знание
этих процессов позволит вам создавать приложения более эффективно
и с меньшими затратами сил на поиск ошибок, которые возникают
из-за непонимания происходящего.
На первый взгляд может показаться, что MDI-приложение состоит
из окон двух типов: главного окна приложения (которое можно отнести
к перекрывающимся окнам) и дочерних окон Document Window, в которых
отображаются документы или другая информация. Это не совсем верно.
На самом деле в MDI-приложении имеется больше окон, чем кажется.
Кроме того, окна Document Window ведут себя немного не так, как
обычные дочерние окна.
Запустите приложение WSTYLE, загрузочный модуль которого есть
на дискете, прилагаемой к 11 тому "Библиотеки системного
программиста" (приложение WSTYLE описано в 3 главе 11 тома,
оно демонстрирует поведение окон, имеющих различные стили). Внешний
вид окон, создаваемых этим приложением, показан на рис. 1.7.
Рис. 1.7. Окна, создаваемые приложением WSTYLE
Проведя эксперимент, вы сможете легко убедиться в том, что дочерние
окна Document Window ведут себя не так, как временные (pop up),
перекрывающиеся (overlapped) или "настоящие" дочерние
(child) окна. В отличие от перекрывающихся и временных окон, окна
Document Window не могут выходить за границы создавшего их окна.
В этом окна Document Window похожи на дочерние окна. Однако дочернее
окно не может стать активным, и его заголовок (если он есть) никогда
не будет выделен цветом, сколько бы вы не щелкали по нему левой
клавишей мыши. Если же выбрать окно Document Window, оно становится
активным и цвет его заголовка изменяется, становясь таким же,
как и у активного перекрывающегося или временного окна.
Секрет такого странного поведения окон Document Window заключается
в том, что Windows создает заголовок окна Document Window в виде
отдельного окна и для активного окна Document Window изменяет
цвет фона окна заголовка. Таким образом, для Windows окно Document
Window состоит как бы из двух отдельных дочерних окон - окна заголовка
и окна, отображающего документ.
Но это еще не все. Оказывается, MDI-приложение создает еще одно,
невидимое окно, которое является родительским для окон Document
Window. Это окно называется Client Window и оно является дочерним
по отношению к главному окну приложения.
Как правило, окно Client Window занимает всю внутреннюю область
главного окна приложения (client area). Поэтому окна Document
Window располагаются внутри внутреннего пространства главного
окна приложения. Если же в приложении используется орган управления
Toolbar (набор кнопок с изображением пиктограмм, дублирующих функции
меню) или Statusbar (строка состояния в нижней части главного
окна приложения), необходимо искусственно уменьшить размер окна
Client Window. Позже мы расскажем вам, как это сделать.
Иерархия окон, создаваемых MDI-приложением, показана на рис. 1.8.
Рис. 1.8. Иерархия окон MDI-приложения
Прежде всего, приложение создает главное окно Frame Window, вызывая
функцию CreateWindow. Это окно создается аналогично обычному главному
окну приложения и является перекрывающимся. Оно может иметь меню,
системное меню, кнопки и рамку для изменения размера.
Перед созданием окна Frame Window приложение должно зарегистрировать
класс окна обычным образом, назначив стиль окна, а также при необходимости
меню и пиктограмму. Разумеется, в приложении должна быть определена
соответствующая функция окна. Она похожа на обычную, но в ней
вместо функции DefWindowProc вызывается функция DefFrameProc,
выполняющая дополнительную обработку сообщений.
После создания окна Frame Window следует создать окно Client Window,
во внутренней области которого будут располагаться окна Document
Window. Окно Client Window создается на базе предопределенного
класса окна "MDICLIENT" с помощью функции CreateWindow.
Для окна Client Window не нужно определять функцию окна, так как
она уже определена в Windows.
Внутренняя область окна Client Window называется рабочим пространством
приложения (application workspace). Внутри этой области создаются
дочерние окна Document Window.
И, наконец, по мере необходимости MDI-приложение создает окна
Document Window, посылая окну Client Window при помощи функции
SendMessage сообщение WM_MDICREATE. В ответ на это сообщение функция
окна Client Window, определенная в Windows, создает новое окно
Document Window. Отметим, что для создания окон Document Window
нельзя использовать функцию CreateWindow.
Перед тем как приступить к созданию окон Document Window, приложение
должно зарегистрировать класс окна Document Window и определить
соответствующую функцию окна. Функция окна Document Window определяет
реакцию окна на сообщения, предназначенные окну Document Window,
и выполняет рисование во внутренней области окна Document Window.
В этой функции вместо функции DefWindowProc вызывается функция
DefMDIChildProc.
Таким образом, MDI-приложение при инициализации создает окно Frame
Window и Client Window, определяя для окна Frame Window класс
окна и специальную функцию окна. В процессе работы приложение
создает окна Document Window, посылая окну Client Window сообщение
WM_MDICREATE.
При необходимости упорядочить расположение окон Document Window
или представляющих их пиктограмм приложение посылает окну Client
Window соответствующие сообщения. Например, для каскадного расположения
окон Document Window нужно послать сообщение WM_MDICASCADE. Функция
окна Client Window сама выполнит необходимое перемещение окон
и изменит их размеры, избавляя программиста от рутинной работы.
В процессе инициализации MDI-приложения вам надо зарегистрировать
как минимум два класса окна - класс окна Frame Window (главного
окна приложения) и класс окна Document Window.
Регистрация окна Frame Window может выполняться, например, следующим
образом:
ATOM aWndClass; // атом для кода возврата
WNDCLASS wc; // структура для регистрации класса окна
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU";
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)FrameWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, "APP_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
wc.lpszClassName = (LPSTR)szFrameClassName;
aWndClass = RegisterClass(&wc);
В классе окна Frame Window, как правило, указывается меню приложения,
хотя это меню может быть создано динамически. В поле lpfnWndProc
следует записать адрес функции окна Frame Window. Эта функция
имеет особенности, о которых мы расскажем позже.
Остальные поля структуры WNDCLASS заполняются обычным образом.
Отметим только, что для цвета фона окна имеет смысл использовать
константу COLOR_APPWORKSPACE. В этом случае для управления фоном
окна можно будет использовать приложение Control Panel, что даст
пользователю возможность настраивать цвета по своему вкусу.
Регистрация класса для окна Document Window выполняется, например,
так:
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = 0;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)ChildWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = sizeof(WORD);
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, "APPCLIENT_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szChildClassName;
aWndClass = RegisterClass(&wc);
В поле lpfnWndProc вы должны указать адрес функции окна Document
Window, которая, как и функция окна Frame Window, имеет свои особенности.
Обратите внимание на поле hIcon. Так как окно Document Window
может быть минимизировано пользователем (свернуто в пиктограмму),
вы должны определить эту пиктограмму в классе окна Document Window.
Если приложение создает окна Document Window на базе нескольких
классов и эти окна будут использованы для отображения документов
различного типа, для каждого класса имеет смысл определить свою
пиктограмму.
Для определения цвета фона окна Document Window мы рекомендуем
воспользоваться константой COLOR_WINDOW. При этом пользователь
сможет управлять цветом фона окна Document Window при помощи приложения
Control Panel.
Заметим, что приложение, которое "ведет себя хорошо",
не навязывает пользователю вкусы разработчика приложения, а позволяет
ему выполнить настройку внешнего вида самостоятельно. Для того
чтобы ваше MDI-приложение было похоже по внешнему виду на стандартные
(такие как, например, Program Manager), используйте системные
цвета.
Итак, мы зарегистрировали класс для главного окна приложения Frame
Window и один или несколько классов для создания окон Document
Window. На следующем этапе инициализации нужно создать окна Frame
Window и Client Window.
Окно Frame Window создается точно также, как и главное окно обычного
приложения. Например:
hwndFrame = CreateWindow(
szFrameClassName, // имя класса окна
szWindowTitle, // заголовок окна
WS_OVERLAPPEDWINDOW, // стиль окна
CW_USEDEFAULT, 0, // задаем размеры и расположение
CW_USEDEFAULT, 0, // окна, принятые по умолчанию
0, // идентификатор родительского окна
0, // идентификатор меню
hInstance, // идентификатор приложения
NULL); // указатель на дополнительные параметры
Для создания окна Client Window необходимо использовать предопределенный
класс окна "MDICLIENT":
CLIENTCREATESTRUCT clcs;
clcs.hWindowMenu = GetSubMenu(GetMenu(hwnd), ID_WINDOWMENU);
clcs.idFirstChild = ID_MDIWINDOW;
hwndClient = CreateWindow(
"MDICLIENT", // имя класса окна
NULL, // заголовок окна
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | // стиль окна
WS_HSCROLL | WS_VSCROLL,
0, 0, 0, 0,
hwnd, // идентификатор родительского окна
(HMENU)ID_CLIENTWINDOW, // идентификатор дочернего окна
hInst, // идентификатор приложения
(LPSTR)&clcs); // указатель на дополнительные параметры
Для окна Client Window не нужно указывать заголовок, зато следует
использовать стиль WS_CLIPCHILDREN (так как функция этого окна
не будет рисовать поверх окон Document Window). Размеры окна не
имеют значения, потому что они автоматически устанавливаются равными
размерам внутренней области (client region) окна Frame Window.
Так как окно Client Window является дочерним по отношению к окну
Frame Window, в девятом параметре функции CreateWindow необходимо
указать идентификатор. Можно использовать произвольное значение,
не конфликтующее с идентификаторами других дочерних окон, создаваемых
окном Frame Window (например, с идентификаторами окон Toolbar
и Statusbar).
Через последний параметр функции CreateWindow следует передать
указатель на предварительно проинициализированную структуру CLIENTCREATESTRUCT.
Поле hWindowMenu этой структуры должно содержать идентификатор
временного меню "Window", которое определено для любого
стандартного MDI-приложения. По мере создания окон Document Window
это меню будет дополняться снизу строками, состоящими из заголовков
окон Document Window. Эти строки можно использовать для выбора
и активизации нужного окна Document Window.
Как получить нужный идентификатор?
Создание окна Client Window целесообразно выполнять в функции
окна Frame Window при обработке сообщения WM_CREATE, так как окно
Client Window дочернее по отношению к окну Frame Window. В приведенном
выше фрагменте кода, взятом как раз из обработчика этого сообщения,
вызывается функция GetMenu. В качестве параметра ей передается
идентификатор окна Frame Window, поэтому она возвращает идентификатор
главного меню приложения.
Далее, пользуясь этим идентификатором и зная порядок расположения
временных меню (pop up menu) в главном меню приложения, с помощью
функции GetSubMenu можно легко получить идентификатор для любого
временного меню. В качестве второго параметра функции GetSubMenu
следует передать порядковый номер временного меню. Самому левому
временному меню соответствует номер 0, следующему - 1, и т. д.
Поэтому идентификатор ID_WINDOWMENU должен быть равен порядковому
номеру временного меню "Window" в главном меню приложения.
Теперь о поле idFirstChild.
При создании окон Document Window они получают идентификаторы
(как и обычные дочерние окна). Первое созданное окно Document
Window получает идентификатор, определенный в поле idFirstChild
при создании окна Client Window. Идентификаторы других окон получаются
последовательным увеличением значения, заданного в этом поле.
А что произойдет, если одно из окон Document Window будет уничтожено?
В этом случае идентификаторы оставшихся окон Document Window будут
изменены таким образом, чтобы они по-прежнему монотонно возрастали
начиная со значения idFirstChild.
Основное правило для выбора идентификатора первого окна Document
Window состоит в том, что идентификаторы окон Document Window
не должны конфликтовать с идентификаторами органов управления
и строк меню, создаваемых приложением. Поэтому для инициализации
поля idFirstChild следует выбрать такое значение, которое заведомо
больше значений других идентификаторов.
Время от времени мы вносили небольшие изменения в самую "устойчивую"
часть приложения - цикл обработки сообщений. Наши очередные нововведения
касаются трансляции сообщений для MDI-приложений. Специальная
трансляция сообщений требуется для обеспечения стандартного клавиатурного
интерфейса MDI-приложений.
В простейшем случае цикл обработки сообщений MDI-приложения может
выглядеть следующим образом:
while(GetMessage(&msg, NULL, 0, 0))
{
if(!TranslateMDISysAccel(hwndClient, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Как видите, перед тем как передать сообщение функции TranslateMessage,
приложение предварительно обрабатывает его функцией TranslateMDISysAccel:
BOOL TranslateMDISysAccel(HWND hwndClient, MSG FAR* lpmsg);
В качестве первого параметра функции передается идентификатор
окна Client Window, который был возвращен функцией CreateWindow
при создании этого окна. Второй параметр - указатель на структуру
MSG, содержащую обрабатываемое сообщение.
Назначение функции TranslateMDISysAccel заключается в преобразовании
клавиатурных сообщений WM_KEYDOWN и WM_KEYUP в сообщения WM_SYSCOMMAND,
что необходимо для нормальной работы акселераторов, назначенных
для строк системного меню окон Document Window.
Заметьте, что строки системного меню имеют акселераторы, аналогичные
строкам системного меню главного окна приложения. Вместо клавиши
<Alt> в них используется клавиша <Ctrl>. Например,
для выбора строки "Close" в системном меню обычного
окна используется комбинация клавиш <Alt + F4>, а для выбора
этой же строки в системном меню окна Document Window предназначена
комбинация клавиш <Ctrl + F4>.
Если функция TranslateMDISysAccel выполнила преобразование, она
возвращает значение TRUE. В этом случае для данного сообщения
уже не нужно вызывать функции TranslateMessage и DispatchMessage.
Приложение может определить собственные акселераторы. В этом случае
в цикле обработки сообщений нужно вызывать функцию TranslateAccelerator:
while(GetMessage(&msg, NULL, 0, 0))
{
if(!TranslateMDISysAccel(hwndClient, &msg)
&& !TranslateAccelerator(hwndFrame, hAccel, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Как мы уже говорили, MDI-приложение должно определить как минимум
две функции окна. Одну функцию окна необходимо указать при регистрации
класса окна Frame Window, другую - при регистрации класса окна
Document Window. Если приложение создает окна Document Window
для отображения разнотипной информации, может потребоваться определить
несколько разных функций (по одной для окна Document Window каждого
типа). Для окна Client Window, создаваемого на базе предопределенного
класса "MDICLIENT" функция окна находится внутри Windows,
поэтому вам не нужно о ней беспокоиться.
Функция окна Frame Window
По своему назначению функция окна Frame Window напоминает функцию
главного окна обычного приложения, однако есть и принципиальные
отличия.
Во-первых, вместо функции DefWindowProc все необработанные сообщения
должны передаваться специальной функции DefFrameProc. Последняя
выполняет обработку таких сообщений, как WM_COMMAND, WM_MENUCHAR,
WM_NEXTMENU, WM_SETFOCUS, WM_SIZE, обеспечивая соответствующую
реакцию на них окон Document Window.
Во-вторых, только что перечисленные сообщения нужно всегда передавать
функции DefFrameProc, даже если функция окна Frame Window обрабатывает
их самостоятельно. Изъятие этих сообщений приведет к неправильной
работе MDI-приложения.
На функцию окна Frame Window возлагается задача обработки сообщений,
поступающих от меню и других органов управления MDI-приложения.
В частности, некоторые или все сообщения могут передаваться для
обработки функции активного окна Document Window.
Функция окна Document Window
Функция окна Document Window очень похожа на функцию обычного
дочернего окна, но все необработанные ей сообщения должны передаваться
функции DefMDIChildProc (а не функции DefWindowProc). Функция
DefMDIChildProc обрабатывает сообщения WM_CHILDACTIVATE, WM_GETMINMAXINFO,
WM_MENUCHAR, WM_MOVE, WM_NEXTMENU, WM_SETFOCUS, WM_SIZE, WM_SYSCOMMAND.
Перечисленные сообщения должны в обязательном порядке передаваться
функции DefMDIChildProc, даже если функция окна Document Window
обрабатывает их самостоятельно.
Основная задача функции окна Document Window - обработка сообщений,
предназначенных активному окну Document Window, в том числе поступающих
из функции окна Frame Window. В частности, эта функция выполняет
обработку сообщения WM_COMMAND.
Последнее, о чем мы вам расскажем, перед тем как перейти к рассмотрению
исходных текстов готового MDI-приложения, это процесс создания
окна Document Window. В отличие от всех других рассмотренных нами
окон, окно Document Window не создается функцией CreateWindow.
Более того, окно Document Window нельзя создать этой функцией.
Для того чтобы создать окно Document Window, следует послать сообщение
WM_MDICREATE окну Client Window при помощи функции SendMessage:
MDICREATESTRUCT mdics;
mdics.szClass = szChildClassName; // класс окна
mdics.szTitle = "MDI Child Window"; // заголовок окна
mdics.hOwner = hInst; // идентификатор приложения
mdics.x = CW_USEDEFAULT; // размеры окна Document Window
mdics.y = CW_USEDEFAULT;
mdics.cx = CW_USEDEFAULT;
mdics.cy = CW_USEDEFAULT;
mdics.style = 0; // дополнительные стили
mdics.lParam = NULL; // 32-битное значение
hwndChild = (HWND)SendMessage(hwndClient,
WM_MDICREATE, 0, (LPARAM)&mdics);
Через последний параметр функции SendMessage передается указатель
на заполненную структуру MDICREATESTRUCT:
typedef struct tagMDICREATESTRUCT
{
LPCSTR szClass;
LPCSTR szTitle;
HINSTANCE hOwner;
int x;
int y;
int cx;
int cy;
DWORD style;
LPARAM lParam;
} MDICREATESTRUCT;
Поля этой структуры определяют такие характеристики окна Document
Window, как класс окна (поле szClass), заголовок окна (szTitle),
размеры и расположение (x, y, cx, cy), стиль окна (style) и произвольное
32-битовое значение lParam, которое может быть проанализировано
функцией окна Document Window при получении сообщения WM_CREATE.
В поле hOwner при инициализации структуры нужно также указать
идентификатор приложения.
В целом назначение этих полей аналогично назначению параметров
функции CreateWindow, так как, в конечном счете, именно с помощью
этой функции Windows (но не приложение) создает окно Document
Window.
Остановимся на поле style, которое используется для определения
стиля окна Document Window. Если при создании окна Client Window
был использован стиль MDIS_ALLCHILDSTYLES, в этом поле вы можете
указать любой стиль, разрешенный для функции CreateWindow.
В противном случае вы должны ограничиться следующими стилями:
WS_MINIMIZE (окно Document Window будет создано в минимизированном
состоянии, т. е. в виде пиктограммы);
WS_MAXIMIZE (размеры окна Document Window сразу после создания
будут увеличены до максимальных пределов);
WS_HSCROLL (окно Document Window будет иметь горизонтальную полосу
просмотра), WS_VSCROLL (окно Document Window будет иметь вертикальную
полосу просмотра).
В любом случае для окна Document Window следует указать стили
WS_CHILD и WS_CLIPSIBLINGS.
Для уничтожения окна Document Window приложение должно послать
окну Client Window сообщение:
SendMessage(hwndClient, WM_MDIDESTROY, hwndDoc, 0l);
Через третий параметр следует передать функции SendMessage идентификатор
уничтожаемого окна Document Window.
Итак, теперь мы готовы приступить к созданию MDI-приложения. В
этом разделе мы приведем исходные тексты простейшего MDI-приложения
MDIAPP (рис. 1.9).
Это приложение имеет все основные свойства стандартного MDI-приложения,
в частности, оно имеет стандартное меню "Window", с
помощью которого можно управлять расположением окон Document Window.
Рис. 1.9. Простейшее MDI-приложение MDIAPP
Все функции приложения определены в файле mdiapp.cpp (листинг
1.1). В частности, в этом файле определена функция WinMain, функция
окна Frame Window (которая имеет имя FrameWndProc) и функция окна
Document Window (с именем ChildWndProc).
Листинг 1.1. Файл mdiapp/mdiapp.cpp
// ============================================================
// Простейшее MDI-приложение
// ============================================================
#define STRICT
#include <windows.h>
#include <mem.h>
#include "mdiapp.hpp"
// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export FrameWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK _export ChildWndProc(HWND, UINT, WPARAM, LPARAM);
// Имена классов окна
char const szFrameClassName[] = "MDIAppClass";
char const szChildClassName[] = "MDIchildAppClass";
// Заголовок окна
char const szWindowTitle[] = "Simple MDI Application";
HINSTANCE hInst;
HWND hwndFrame; // окно Frame Window
HWND hwndClient; // окно Client Window
HWND hwndChild; // окно Child Window
// =====================================
// Функция WinMain
// =====================================
#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
MSG msg; // структура для работы с сообщениями
hInst = hInstance; // сохраняем идентификатор приложения
if(hPrevInstance) // может быть запущена
return FALSE; // только одна копия приложения
// Инициализируем приложение
if(!InitApp(hInstance))
return FALSE;
// Создаем главное окно приложения - Frame Window
hwndFrame = CreateWindow(
szFrameClassName, // имя класса окна
szWindowTitle, // заголовок окна
WS_OVERLAPPEDWINDOW, // стиль окна
CW_USEDEFAULT, 0, // задаем размеры и расположение
CW_USEDEFAULT, 0, // окна, принятые по умолчанию
0, // идентификатор родительского окна
0, // идентификатор меню
hInstance, // идентификатор приложения
NULL); // указатель на дополнительные параметры
// Если создать окно не удалось, завершаем приложение
if(!hwndFrame)
return FALSE;
// Рисуем главное окно
ShowWindow(hwndFrame, nCmdShow);
UpdateWindow(hwndFrame);
// Запускаем цикл обработки сообщений
while(GetMessage(&msg, NULL, 0, 0))
{
// Трансляция для MDI-приложения
if(!TranslateMDISysAccel(hwndClient, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
// =====================================
// Функция InitApp
// Выполняет регистрацию классов окон
// =====================================
BOOL
InitApp(HINSTANCE hInstance)
{
ATOM aWndClass; // атом для кода возврата
WNDCLASS wc; // структура для регистрации
// класса окна
// Регистрируем класс для главного окна приложения
// (т. е. для окна Frame Window)
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU";
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)FrameWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, "APP_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
wc.lpszClassName = (LPSTR)szFrameClassName;
aWndClass = RegisterClass(&wc);
if(!aWndClass)
return FALSE;
// Регистрируем класс окна для дочернего окна Document Window
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = 0;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)ChildWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, "APPCLIENT_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szChildClassName;
aWndClass = RegisterClass(&wc);
if(!aWndClass)
return FALSE;
return TRUE;
}
// =====================================
// Функция FrameWndProc
// =====================================
LRESULT CALLBACK _export
FrameWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Структура для создания окна Client Window
CLIENTCREATESTRUCT clcs;
// Структура для создания дочернего окна Document Window
MDICREATESTRUCT mdics;
switch (msg)
{
// При создании окна Frame Window создаем
// окно Client Window, внутри которого будут создаваться
// дочерние окна Document Window
case WM_CREATE:
{
// Получаем и сохраняем в структуре clcs идентификатор
// временного меню "Window". Так как это меню второе
// слева, его позиция равна 1 (меню "File" имеет
// позицию 0)
clcs.hWindowMenu = GetSubMenu(GetMenu(hwnd), 1);
// Идентификатор первого дочернего окна Document Window
clcs.idFirstChild = 500;
// Создаем окно Client Window
hwndClient = CreateWindow(
"MDICLIENT", // имя класса окна
NULL, // заголовок окна
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | // стиль окна
WS_HSCROLL | WS_VSCROLL,
0, 0, 0, 0,
hwnd, // идентификатор родительского окна
(HMENU)1, // идентификатор меню
hInst, // идентификатор приложения
(LPSTR)&clcs);// указатель на дополнительные параметры
break;
}
// Обработка сообщений от главного меню приложения
case WM_COMMAND:
{
switch (wParam)
{
// Создание нового окна Document Window
case CM_FILENEW:
{
// Заполняем структуру MDICREATESTRUCT
mdics.szClass = szChildClassName; // класс окна
mdics.szTitle = "MDI Child Window"; // заголовок окна
mdics.hOwner = hInst; // идентификатор приложения
mdics.x = CW_USEDEFAULT; // размеры окна
mdics.y = CW_USEDEFAULT; // Document Window
mdics.cx = CW_USEDEFAULT;
mdics.cy = CW_USEDEFAULT;
mdics.style = 0; // дополнительные стили
mdics.lParam = NULL; // 32-битное значение
// Посылаем сообщение WM_MDICREATE окну Client
// Window. В результате будет создано
// новое окно Document Window
hwndChild = (HWND)SendMessage(hwndClient,
WM_MDICREATE, 0, (LPARAM)&mdics);
break;
}
// Размещение окон Document Window рядом друг с другом
case CM_WINDOWTILE:
{
SendMessage(hwndClient, WM_MDITILE, 0, NULL);
break;
}
// Размещение окон Document Window с перекрытием
case CM_WINDOWCASCADE:
{
SendMessage(hwndClient, WM_MDICASCADE, 0, NULL);
break;
}
// Размещение пиктограмм минимизированных
// окон Document Window
// в нижней части окна Client Window
case CM_WINDOWICONS:
{
SendMessage(hwndClient, WM_MDIICONARRANGE, 0, NULL);
break;
}
// Уничтожение всех окон Document Window
case CM_WINDOWCLOSEALL:
{
HWND hwndTemp;
// Скрываем окно Client Window для того чтобы
// избежать многократной перерисовки окон Document
// Window во время их уничтожения
ShowWindow(hwndClient, SW_HIDE);
for(;;)
{
// Получаем идентификатор дочернего окна
// для окна Client Window
hwndTemp = GetWindow(hwndClient, GW_CHILD);
// Если дочерних окон больше нет, выходим из цикла
if(!hwndTemp)
break;
// Пропускаем окна-заголовки
while(hwndTemp && GetWindow(hwndTemp, GW_OWNER))
hwndTemp = GetWindow(hwndTemp, GW_HWNDNEXT);
// Удаляем дочернее окно Document Window
if(hwndTemp)
SendMessage(hwndClient, WM_MDIDESTROY,
(WPARAM)hwndTemp, NULL);
else
break;
}
// Отображаем окно Client Window
ShowWindow(hwndClient, SW_SHOW);
break;
}
case CM_HELPABOUT:
{
MessageBox(hwnd,
"Приложение MDIAPPn(C) Фролов А.В., 1995",
"Simple MDI Application",
MB_OK | MB_ICONINFORMATION);
break;
}
// Завершаем работу приложения
case CM_FILEEXIT:
{
DestroyWindow(hwnd);
break;
}
default:
break;
}
// Определяем идентификатор
// активного окна Document Window
HWND hwndChild = (HWND)LOWORD(SendMessage(hwndClient,
WM_MDIGETACTIVE, 0, 0l));
// Если это окно, посылаем ему сообщение WM_COMMAND
if(IsWindow(hwndChild))
SendMessage(hwndChild, WM_COMMAND, wParam, lParam);
return DefFrameProc(hwnd, hwndClient,
msg, wParam, lParam);
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
break;
}
return DefFrameProc(hwnd, hwndClient, msg, wParam, lParam);
}
// =====================================
// Функция ChildWndProc
// =====================================
LRESULT CALLBACK _export
ChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rc;
switch (msg)
{
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
DrawText(hdc, "Child Window", -1, &rc,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
}
default:
break;
}
return DefMDIChildProc(hwnd, msg, wParam, lParam);
}
Функция WinMain вызывает для инициализации приложения функцию
InitApp, задачей которой является регистрация классов окон Frame
Window и Document Window. После инициализации создается главное
окно приложения Frame Window, для чего используется функция CreateWindow.
После отображения главного окна запускается цикл обработки сообщений.
В этом цикле для обеспечения стандартного клавиатурного интерфейса
MDI-приложения мы вызываем функцию TranslateMDISysAccel.
Рассмотрим функцию окна Frame Window, которая называется FrameWndProc.
В процессе создания окна Frame Window ей передается сообщение
WM_CREATE. Обработчик этого сообщения создает окно Client Window,
во внутренней области которого будут впоследствии создаваться
окна Document Window.
Способ создания окна Client Window был рассмотрен нами ранее.
Отметим, что, так как временное меню "Window" является
вторым слева в главном меню приложения, его позиция равна 1. Поэтому
поле hWindowMenu структуры CLIENTCREATESTRUCT заполняется следующим
образом:
clcs.hWindowMenu = GetSubMenu(GetMenu(hwnd), 1);
Когда вы выбираете строку "New" из меню "File",
функция окна Frame Window получает сообщение WM_COMMAND с параметром
wParam, равным CM_FILENEW. Соответствующий обработчик создает
новое окно Document Window, посылая окну Client Window сообщение
WM_MDICREATE. Перед посылкой сообщения заполняется структура MDICREATESTRUCT,
определяющая характеристики создаваемого окна.
Обратите внимание на реализацию обработчиков сообщения WM_COMMAND
для меню "Window". Для того чтобы упорядочить расположение
окон Document Window или представляющих их пиктограмм во внутренней
области окна Client Window, наше приложение посылает специальные
сообщения окну Client Window.
Например, если приложению нужно расположить все активные окна
Document Window рядом, оно посылает окну Client Window сообщение
WM_MDITILE:
SendMessage(hwndClient, WM_MDITILE, 0, NULL);
Функция окна, определенная в Windows, выполняет необходимые изменения
в расположении и размерах активных окон.
Так же просто выполняется каскадное размещение окон Document Window
и размещение пиктограмм свернутых окон Document Window. В этом
случае окну Client Window посылаются, соответственно, сообщения
WM_MDICASCADE и WM_MDIICONARRANGE:
SendMessage(hwndClient, WM_MDICASCADE, 0, NULL);
SendMessage(hwndClient, WM_MDIICONARRANGE, 0, NULL);
К сожалению, реализация обработчика сообщения WM_COMMAND для строки
"Close All" меню "Window" выглядит несколько
сложнее:
case CM_WINDOWCLOSEALL:
{
HWND hwndTemp;
ShowWindow(hwndClient, SW_HIDE);
for(;;)
{
hwndTemp = GetWindow(hwndClient, GW_CHILD);
if(!hwndTemp)
break;
while(hwndTemp && GetWindow(hwndTemp, GW_OWNER))
hwndTemp = GetWindow(hwndTemp, GW_HWNDNEXT);
if(hwndTemp)
SendMessage(hwndClient, WM_MDIDESTROY,
(WPARAM)hwndTemp, NULL);
else
break;
}
ShowWindow(hwndClient, SW_SHOW);
break;
}
В этом фрагменте кода, прежде всего, мы делаем невидимым окно
Client Window и (как следствие) его дочерние окна Document Window,
для того чтобы избежать неприятной для глаз многократной перерисовки
удаляемых окон. Напомним, что окно можно скрыть или показать вновь
с помощью функции ShowWindow, указав ей для этого, соответственно,
параметры SW_HIDE и SW_SHOW.
Далее мы организуем цикл по всем дочерним окнам окна Client Window,
получая идентификатор первого дочернего окна с помощью функции
GetWindow с параметром GW_CHILD.
Однако нельзя удалять все дочерние окна, созданные окном Client
Window. Дело в том, что для окон Document Window, свернутых в
пиктограммы, создаются два окна. Одно окно содержит саму пиктограмму,
а второе - подпись под ней (заголовок пиктограммы). При удалении
окон Document Window мы не должны удалять окна заголовков, так
как они будут удалены автоматически при удалении соответствующего
окна Document Window.
Окно-заголовок отличается от окна Document Window тем, что оно
имеет окно-владельца (окно Document Window владеет окном заголовка).
В это же время окно Document Window не имеет окна-владельца, так
как оно является дочерним по отношению к окну Client Window. Этим
обстоятельством можно воспользоваться, для того чтобы отличить
окна-заголовки от обычных окон Document Window.
Простой способ определения идентификатора окна-владельца заключается
в вызове функции GetWindow с параметром GW_OWNER. Вызывая эту
функцию в цикле, наше приложение пропускает все окна-заголовки.
Для перебора всех окон, имеющих одних и тех же родителей (окон-братьев)
используется все та же функция GetWindow с параметром GW_HWNDNEXT.
Для удаления окна Document Window окну Client Window посылается
сообщение WM_MDIDESTROY, для чего вызывается функция SendMessage.
После завершения цикла удаления окно Client Window вновь делается
видимым при помощи функции ShowWindow с параметром SW_SHOW.
Перед тем как вызвать функцию DefFrameProc, обработчик сообщения
WM_COMMAND посылает это же сообщение активному окну Document Window.
Так как пользователь может сделать активным любое окно Document
Window, сначала нужно определить идентификатор активного окна.
Это можно сделать, если послать окну Client Window сообщение WM_MDIGETACTIVE:
HWND hwndChild = (HWND)LOWORD(SendMessage(hwndClient,
WM_MDIGETACTIVE, 0, 0l));
Функция SendMessage вернет идентификатор активного окна Document
Window. После проверки этого идентификатора функцией IsWindow
можно посылать сообщение в активное окна Document Window:
if(IsWindow(hwndChild))
SendMessage(hwndChild, WM_COMMAND, wParam, lParam);
В завершении своей работы обработчик сообщения WM_COMMAND обязан
вызвать функцию DefFrameProc:
return DefFrameProc(hwnd, hwndClient, msg, wParam, lParam);
Заметьте, что этой функции нужно указать в первом параметре идентификатор
окна Frame Window, а в качестве второго - идентификатор окна Client
Window. Остальные параметры аналогичны параметрам функции DefWindowProc.
Теперь займемся функцией окна Document Window.
В нашем простейшем MDI-приложении эта функция делает только одну
вещь - при обработке сообщения WM_PAINT пишет в центре окна Document
Window текстовую строку "Child Window", вызывая для
этого функцию DrawText:
LRESULT CALLBACK _export
ChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rc;
switch (msg)
{
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
DrawText(hdc, "Child Window", -1, &rc,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
}
default:
break;
}
return DefMDIChildProc(hwnd, msg, wParam, lParam);
}
Все сообщения, обработанные и необработанные, эта функция передает
функции DefMDIChildProc, параметры которой аналогичны параметрам
функции DefWindowProc.
Идентификаторы строк меню приложения MDIAPP определены в файле
mdiapp.hpp (листинг 1.2).
Листинг 1.2. Файл mdiapp/mdiapp.hpp
#define CM_HELPABOUT 100
#define CM_FILEEXIT 101
#define CM_FILENEW 102
#define CM_WINDOWTILE 103
#define CM_WINDOWCASCADE 104
#define CM_WINDOWICONS 105
#define CM_WINDOWCLOSEALL 106
Ресурсы приложения (меню и две пиктограммы) определены в файле
mdiapp.rc (листинг 1.3).
Листинг 1.3. Файл mdiapp/mdiapp.rc
#include "mdiapp.hpp"
APP_MENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&New", CM_FILENEW
MENUITEM SEPARATOR
MENUITEM "E&xit", CM_FILEEXIT
END
POPUP "&Window"
BEGIN
MENUITEM "&Tile", CM_WINDOWTILE
MENUITEM "&Cascade", CM_WINDOWCASCADE
MENUITEM "Arrange &Icons",CM_WINDOWICONS
MENUITEM "Close &All", CM_WINDOWCLOSEALL
END
POPUP "&Help"
BEGIN
MENUITEM "&About...", CM_HELPABOUT
END
END
APP_ICON ICON "mdiapp.ico"
APPCLIENT_ICON ICON "mdiappcl.ico"
Файл определения модуля для приложения MDIAPP представлен в листинге
1.4.
Листинг 1.4. Файл mdiapp/mdiapp.def
NAME MDIAPP
DESCRIPTION 'Приложение MDIAPP, (C) 1995, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 8120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple
Большинство профессиональных MDI-приложений создает в своем главном
окне инструментальные средства, облегчающие работу с меню - окна
Toolbar и Statusbar. Операционная система Windows версии 3.1 не
содержит поддержки для этих окон, поэтому вы должны создавать
их самостоятельно.
Окно Toolbar содержит кнопки с пиктограммами, дублирующие функции
меню. Это окно может располагаться горизонтально (в верхней или
нижней части внутренней области окна Frame Window) или вертикально
(слева или справа).
Окно Statusbar обычно находится в нижней части окна Frame Window
и используется для отображения справочной информации. Например,
когда пользователь перемещает курсор мыши по кнопкам окна Toolbar,
в окне Statusbar могут появляться текстовые строки, поясняющие
назначение кнопок, на которые указывает курсор. Это окно также
часто используется для отображения текущего состояния переключающих
клавиш, таких как <Insert>, <Scroll Lock>, <Caps
Lock> и <Num Lock>.
MDI-приложение создает окна Toolbar, Statusbar и аналогичные как
дочерние для окна Frame Window (рис. 1.10).
Рис. 1.10. Иерархия окон MDI-приложения с окнами Toolbar
и Statusbar
Основная проблема, возникающая при этом, заключается в необходимости
уменьшения размера окна Client Window. В стандартном MDI-приложении
это окно занимает всю внутреннюю область окна Frame Window, причем
его размеры автоматически изменяются при изменении размеров окна
Frame Window.
Как устанавливаются размеры окна Client Window?
Очень просто. При создании окна Frame Window или изменении его
размеров функция окна Frame Window получает сообщение WM_SIZE.
Если вы помните, параметр lParam содержит новые размеры внутренней
области окна (LOWORD(lParam) - ширина внутренней области окна,
HIWORD(lParam) - высота). Функция окна Frame Window обычно передает
это сообщение функции DefFrameProc, которая и устанавливает размеры
окна Client Window равными размерам внутренней области окна Frame
Window.
Следовательно, для того чтобы изменить размеры окна Client Window,
функция окна Frame Window должна обрабатывать сообщение WM_SIZE
самостоятельно, не передавая его функции DefFrameProc. Обработка
в этом случае заключается в установке новых размеров окна Client
Window, а также окон Toolbar и Statusbar. Последнее необходимо
для того, чтобы при горизонтальном расположении окон Toolbar и
Statusbar их ширина была всегда равна ширине внутренней области
окна Frame Window, а при вертикальном расположении - высоте внутренней
области окна Frame Window.
Что же касается создания окон Toolbar и Statusbar, то приложение
может создавать их одновременно с созданием окна Client Window.
Так как все эти окна являются дочерними для окна Frame Window,
их удобно создавать при обработке сообщения WM_CREATE функции
окна Frame Window.
Описанный способ изменения размера окна Client Window для размещения
окон Toolbar и Statusbar демонстрируется в приложении MDITB, к
описанию которого мы и перейдем.
Приложение MDITB отличается от только что рассмотренного нами
приложения MDIAPP наличием дополнительных окон Toolbar и Statusbar
(рис. 1.11).
Рис. 1.11. Приложение MDITB
Для сокращения объема исходного текста мы не стали полностью реализовывать
стандартные для окон Toolbar и Statusbar функции, ограничившись
демонстрацией способов создания этих окон в MDI-приложении. Один
из возможных способов реализации функций окна Toolbar мы описали
в 13 томе "Библиотеки системного программиста" (см.
разделы "Орган управления TOOLBAR" и "Приложение
SMARTPAD" главы "Меню"). Реализация функций окна
Statusbar не отнимет у вас много сил, поэтому вы справитесь с
этим окном самостоятельно.
Итак, обратимся к листингу 1.5, содержащему определения всех функций
приложения MDITB. В книге приведен сокращенный вариант листинга
(без комментариев), полный вариант вы найдете на дискете, которая
продается вместе с книгой.
Листинг 1.5. Файл mditb/mditb.cpp
// ============================================================
// MDI-приложение с окнами Toolbar и Statusbar
// ============================================================
#define STRICT
#include <windows.h>
#include <mem.h>
#include "mditb.hpp"
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export FrameWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK _export ChildWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK _export TbWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK _export SbWndProc(HWND, UINT, WPARAM, LPARAM);
char const szFrameClassName[] = "MDITBAppClass";
char const szChildClassName[] = "MDITBChildAppClass";
char const szTbClassName[] = "MDITBCtrlAppClass";
char const szSbClassName[] = "MDISBCtrlAppClass";
char const szWindowTitle[] = "MDI Application";
HINSTANCE hInst;
HWND hwndFrame; // окно Frame Window
HWND hwndClient; // окно Client Window
HWND hwndChild; // окно Child Window
HWND hwndTb; // окно Toolbar
HWND hwndSb; // окно Statusbar
// =====================================
// Функция WinMain
// =====================================
#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
MSG msg; // структура для работы с сообщениями
hInst = hInstance; // сохраняем идентификатор приложения
if(hPrevInstance) // может быть запущена
return FALSE; // только одна копия приложения
if(!InitApp(hInstance))
return FALSE;
hwndFrame = CreateWindow(
szFrameClassName, // имя класса окна
szWindowTitle, // заголовок окна
WS_OVERLAPPEDWINDOW, // стиль окна
CW_USEDEFAULT, 0, // задаем размеры и расположение
CW_USEDEFAULT, 0, // окна, принятые по умолчанию
0, // идентификатор родительского окна
0, // идентификатор меню
hInstance, // идентификатор приложения
NULL); // указатель на дополнительные параметры
if(!hwndFrame)
return FALSE;
ShowWindow(hwndFrame, nCmdShow);
UpdateWindow(hwndFrame);
while(GetMessage(&msg, NULL, 0, 0))
{
if(!TranslateMDISysAccel(hwndClient, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================
BOOL
InitApp(HINSTANCE hInstance)
{
ATOM aWndClass; // атом для кода возврата
WNDCLASS wc; // структура для регистрации
// класса окна
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU";
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)FrameWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, "APP_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
wc.lpszClassName = (LPSTR)szFrameClassName;
aWndClass = RegisterClass(&wc);
if(!aWndClass)
return FALSE;
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = 0;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)ChildWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, "APPCLIENT_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szChildClassName;
aWndClass = RegisterClass(&wc);
if(!aWndClass)
return FALSE;
// Регистрируем класс для окна Toolbar
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = 0;
wc.style = 0;
wc.lpfnWndProc = (WNDPROC)TbWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szTbClassName;
aWndClass = RegisterClass(&wc);
if(!aWndClass)
return FALSE;
// Регистрируем класс для окна Statusbar
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = 0;
wc.style = 0;
wc.lpfnWndProc = (WNDPROC)SbWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szSbClassName;
aWndClass = RegisterClass(&wc);
if(!aWndClass)
return FALSE;
return TRUE;
}
// =====================================
// Функция FrameWndProc
// =====================================
LRESULT CALLBACK _export
FrameWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
CLIENTCREATESTRUCT clcs;
MDICREATESTRUCT mdics;
switch (msg)
{
// Устанавливаем размеры окон Toolbar, Statusbar.
// Уменьшаем размер окна Client Window
case WM_SIZE:
{
// Располагаем окно Toolbar в верхней части
// окна Frame Window
MoveWindow(hwndTb,
0, // x-координата
0, // y-координата
LOWORD(lParam), // ширина
TBAR_SIZE, // высота
TRUE); // требуется перерисовка окна
// Располагаем окно Statusbar в нижней части
// окна Frame Window
MoveWindow(hwndSb,
0, // x-координата
HIWORD(lParam) - SBAR_SIZE, // y-координата
LOWORD(lParam), // ширина
SBAR_SIZE, // высота
TRUE); // требуется перерисовка окна
// Если окно не свернуто в пиктограмму и его
// идентификатор отличен от нуля, вызываем
// функцию MoveWindow
if(wParam != SIZEICONIC && hwndClient)
{
MoveWindow(hwndClient,
0, // x-координата
TBAR_SIZE, // y-координата
LOWORD(lParam), // ширина
HIWORD(lParam) - (TBAR_SIZE + SBAR_SIZE), // высота
TRUE); // требуется перерисовка окна
// После уменьшения размеров окна нельзя
// отдавать сообщение WM_SIZE функции DefFrameProc,
// так как иначе размеры будут восстановлены
return 0;
}
break;
}
case WM_CREATE:
{
clcs.hWindowMenu = GetSubMenu(GetMenu(hwnd), 1);
clcs.idFirstChild = 500;
// Создаем окно Client Window
hwndClient = CreateWindow(
"MDICLIENT", // имя класса окна
NULL, // заголовок окна
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | // стиль окна
WS_HSCROLL | WS_VSCROLL,
0, 0, 0, 0,
hwnd, // идентификатор родительского окна
(HMENU)1, // идентификатор меню
hInst, // идентификатор приложения
(LPSTR)&clcs);// указатель на дополнительные параметры
// Создаем окно Toolbar
hwndTb = CreateWindow(
szTbClassName, // имя класса окна
NULL, // заголовок окна
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_DLGFRAME,
0, 0, 0, 0,
hwnd, // идентификатор родительского окна
(HMENU)2, // идентификатор меню
hInst, // идентификатор приложения
NULL); // указатель на дополнительные параметры
// Создаем окно Statusbar
hwndSb = CreateWindow(
szSbClassName, // имя класса окна
NULL, // заголовок окна
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_DLGFRAME,
0, 0, 0, 0,
hwnd, // идентификатор родительского окна
(HMENU)3, // идентификатор меню
hInst, // идентификатор приложения
NULL); // указатель на дополнительные параметры
break;
}
case WM_COMMAND:
{
switch (wParam)
{
case CM_FILENEW:
{
mdics.szClass = szChildClassName; // класс окна
mdics.szTitle = "MDI Child Window"; // заголовок окна
mdics.hOwner = hInst; // идентификатор приложения
mdics.x = CW_USEDEFAULT; // размеры окна
mdics.y = CW_USEDEFAULT; // Document Window
mdics.cx = CW_USEDEFAULT;
mdics.cy = CW_USEDEFAULT;
mdics.style = 0; // дополнительные стили
mdics.lParam = NULL; // 32-битное значение
// Посылаем сообщение WM_MDICREATE окну
// Client Window. В результате будет создано новое
// окно Document Window
hwndChild = (HWND)SendMessage(hwndClient,
WM_MDICREATE, 0, (LPARAM)&mdics);
break;
}
case CM_WINDOWTILE:
{
SendMessage(hwndClient, WM_MDITILE, 0, NULL);
break;
}
case CM_WINDOWCASCADE:
{
SendMessage(hwndClient, WM_MDICASCADE, 0, NULL);
break;
}
case CM_WINDOWICONS:
{
SendMessage(hwndClient, WM_MDIICONARRANGE, 0, NULL);
break;
}
case CM_WINDOWCLOSEALL:
{
HWND hwndTemp;
ShowWindow(hwndClient, SW_HIDE);
for(;;)
{
hwndTemp = GetWindow(hwndClient, GW_CHILD);
if(!hwndTemp)
break;
while(hwndTemp && GetWindow(hwndTemp, GW_OWNER))
hwndTemp = GetWindow(hwndTemp, GW_HWNDNEXT);
if(hwndTemp)
SendMessage(hwndClient, WM_MDIDESTROY,
(WPARAM)hwndTemp, NULL);
else
break;
}
ShowWindow(hwndClient, SW_SHOW);
break;
}
case CM_HELPABOUT:
{
MessageBox(hwnd,
"Приложение MDIAPPn(C) Фролов А.В., 1995",
"Simple MDI Application",
MB_OK | MB_ICONINFORMATION);
break;
}
case CM_FILEEXIT:
{
DestroyWindow(hwnd);
break;
}
default:
break;
}
HWND hwndChild =
(HWND)LOWORD(SendMessage(hwndClient,
WM_MDIGETACTIVE, 0, 0l));
if(IsWindow(hwndChild))
SendMessage(hwndChild, WM_COMMAND, wParam, lParam);
return DefFrameProc(hwnd, hwndClient,
msg, wParam, lParam);
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
break;
}
return DefFrameProc(hwnd, hwndClient, msg, wParam, lParam);
}
// =====================================
// Функция ChildWndProc
// =====================================
LRESULT CALLBACK _export
ChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rc;
switch (msg)
{
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
DrawText(hdc, "Child Window", -1, &rc,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
}
default:
break;
}
return DefMDIChildProc(hwnd, msg, wParam, lParam);
}
// =====================================
// Функция TbWndProc
// =====================================
LRESULT CALLBACK _export
TbWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rc;
switch (msg)
{
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
DrawText(hdc, "Toolbar Window", -1, &rc,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
}
default:
break;
}
return DefMDIChildProc(hwnd, msg, wParam, lParam);
}
// =====================================
// Функция SbWndProc
// =====================================
LRESULT CALLBACK _export
SbWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rc;
switch (msg)
{
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
DrawText(hdc, "Statusbar Window", -1, &rc,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
}
default:
break;
}
return DefMDIChildProc(hwnd, msg, wParam, lParam);
}
На этапе инициализации приложения функция InitApp регистрирует
классы для окон Frame Window, Document Window, а также для окон
Toolbar и Statusbar. Эта процедура не имеет никаких особенностей.
Отметим только, что вы можете изменить форму курсора мыши для
окна Toolbar, указав нужный идентификатор курсора в поле hCursor
структуры WNDCLASS перед регистрацией класса, или задать цвет
окна Toolbar.
Окна Toolbar и Statusbar создаются в функции окна Frame Window
при обработке сообщения WM_CREATE:
hwndTb = CreateWindow(
szTbClassName, // имя класса окна
NULL, // заголовок окна
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_DLGFRAME,
0, 0, 0, 0,
hwnd, // идентификатор родительского окна
(HMENU)2, // идентификатор меню
hInst, // идентификатор приложения
NULL); // указатель на дополнительные параметры
hwndSb = CreateWindow(
szSbClassName, // имя класса окна
NULL, // заголовок окна
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_DLGFRAME,
0, 0, 0, 0,
hwnd, // идентификатор родительского окна
(HMENU)3, // идентификатор меню
hInst, // идентификатор приложения
NULL); // указатель на дополнительные параметры
Обратите внимание, что для этих окон мы указали нулевые размеры,
так как в момент их создания размеры внутренней области окна Frame
Window еще неизвестны.
Вы можете указать для окон Toolbar и Statusbar любые стили, применимые
к дочерним окнам. Для того чтобы эти окна появились на экране
сразу после их создания, мы использовали стиль WS_VISIBLE.
Займемся теперь обработчиком сообщения WM_SIZE.
Прежде всего, он располагает окно Toolbar в верхней части внутренней
области окна Frame Window, вызывая функцию MoveWindow:
MoveWindow(hwndTb,
0, // x-координата
0, // y-координата
LOWORD(lParam), // ширина
TBAR_SIZE, // высота
TRUE); // требуется перерисовка окна
Координаты верхнего левого угла окна Toolbar устанавливаются равными
значению (0,0), поэтому верхний левый угол этого окна совмещается
с верхним левым углом внутренней области окна Frame Window.
Для определения ширины окна Toolbar анализируется параметр lParam,
который для сообщения WM_SIZE равен ширине внутренней области
окна.
Высота окна Toolbar в нашем приложении задается константой TBAR_SIZE,
которая определена в файле mditb.hpp (листинг 1.6).
Аналогичным образом устанавливаются координаты и размеры окна
Statusbar:
MoveWindow(hwndSb,
0, // x-координата
HIWORD(lParam) - SBAR_SIZE, // y-координата
LOWORD(lParam), // ширина
SBAR_SIZE, // высота
TRUE); // требуется перерисовка окна
Высота окна Statusbar задается константой SBAR_SIZE.
Затем мы проверяем, создано ли окно Client Window, и не свернуто
ли оно в пиктограмму. Если с окном все в порядке, устанавливаем
для него новое расположение и размеры, оставляя место для окон
Toolbar и Statusbar:
if(wParam != SIZEICONIC && hwndClient)
{
MoveWindow(hwndClient,
0, // x-координата
TBAR_SIZE, // y-координата
LOWORD(lParam), // ширина
HIWORD(lParam) - (TBAR_SIZE + SBAR_SIZE), // высота
TRUE); // требуется перерисовка окна
return 0;
}
break;
Еще раз обращаем ваше внимание на то, что обработанное сообщение
WM_SIZE нельзя отдавать функции DefFrameProc, поэтому после вызова
функции MoveWindow мы выполняем возврат из функции окна оператором
return.
При регистрации классов для окон Toolbar и Statusbar мы указали
соответствующие функции окна. Эти функции определены в нашем приложении
и называются TbWndProc (для окна Toolbar) и SbWndProc (для окна
Statusbar). Эти функции полностью определяют поведение окон Toolbar
и Statusbar, выполняя обработку предназначенных для них сообщений.
В нашем случае мы просто рисуем в центре этих окон их названия,
вызывая функцию DrawText.
В листинге 1.6 вы найдете файл mditb.hpp, содержащий определения
констант для размеров окон Toolbar и Statusbar, а также для работы
с меню.
Листинг 1.6. Файл mditb/mditb.hpp
#define TBAR_SIZE 35
#define SBAR_SIZE 35
#define CM_HELPABOUT 100
#define CM_FILEEXIT 101
#define CM_FILENEW 102
#define CM_WINDOWTILE 103
#define CM_WINDOWCASCADE 104
#define CM_WINDOWICONS 105
#define CM_WINDOWCLOSEALL 106
Файл ресурсов приложения MDITB приведен в листинге 1.7.
Листинг 1.7. Файл mditb/mditb.rc
#include "mditb.hpp"
APP_MENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&New", CM_FILENEW
MENUITEM SEPARATOR
MENUITEM "E&xit", CM_FILEEXIT
END
POPUP "&Window"
BEGIN
MENUITEM "&Tile", CM_WINDOWTILE
MENUITEM "&Cascade", CM_WINDOWCASCADE
MENUITEM "Arrange &Icons",CM_WINDOWICONS
MENUITEM "Close &All", CM_WINDOWCLOSEALL
END
POPUP "&Help"
BEGIN
MENUITEM "&About...", CM_HELPABOUT
END
END
APP_ICON ICON "mditb.ico"
APPCLIENT_ICON ICON "mditbcl.ico"
Файл определения модуля приложения MDITB приведен в листинге 1.8.
Листинг 1.8. Файл mditb/mditb.def
NAME MDITB
DESCRIPTION 'Приложение MDITB, (C) 1995, Frolov A.V.'
EXETYPE windows
STUB 'winstub.exe'
STACKSIZE 8120
HEAPSIZE 1024
CODE preload moveable discardable
DATA preload moveable multiple
В этом разделе мы кратко рассмотрим некоторые дополнительные вопросы,
возникающие при создании MDI-приложений. Мы расскажем о безопасном
способе уничтожения окон Document Window, о динамическом изменении
главного меню приложения и о том, как идентифицировать окна Document
Window.
Безопасное уничтожение окон Document Window
Так как MDI-приложения могут работать одновременно с несколькими
документами, следует соблюдать осторожность при уничтожении окон
Document Window, которые используются для редактирования документов,
а также при завершении работы приложения. Необходимо убедиться,
что пользователь сохранил все измененные документы в соответствующих
файлах.
Обычный способ заключается в использовании сообщения WM_QUERYENDSESSION,
о котором мы уже рассказывали в предыдущих томах "Библиотеки
системного программиста". Напомним, что это сообщение посылается
всем приложениям при завершении работы операционной системы Windows.
Если приложение может завершить свою работу, в ответ на это сообщение
оно должно возвратить значение TRUE. Если же будет возвращено
значение FALSE, завершение работы Windows будет отменено.
Большинство приложений, редактирующих документы, реагируют на
сообщение WM_QUERYENDSESSION следующим образом. Если пользователь
не загружал документы или не изменил ни один из загруженных документов,
обработчик сообщения WM_QUERYENDSESSION (который находится в функции
окна Frame Window) возвращает значение TRUE, разрешая завершение
Windows. Если же один или несколько из загруженных документов
был изменен, но не сохранен, обработчик предлагает пользователю
сохранить документ, или отказаться от сохранения документа. Аналогичные
действия выполняются и при завершении работы приложения в обработчике
сообщения WM_CLOSE.
Для проверки возможности уничтожения окна Document Window лучше
всего послать ему сообщение WM_QUERYENDSESSION, предусмотрев в
функции окна Document Window соответствующий обработчик. Этот
обработчик проверяет, нужно ли сохранять документ, связанный с
данным окном Document Window, и при необходимости предлагает пользователю
сохранить документ или отказаться от сохранения. Если функция
SendMessage, с помощью которой было послано сообщение WM_QUERYENDSESSION,
возвратила значение TRUE, данное окно можно уничтожать.
Аналогичные проверки требуется выполнять в обработчике сообщения
для строки "Close All" меню "Window", так
как в противном случае могут быть потеряны результаты работы по
редактированию одного или нескольких документов.
Динамическое изменение главного меню приложения
Окна Document Window MDI-приложения могут содержать документы
различных типов либо документы одного и того же типа, но находящиеся
в различном состоянии. Поэтому в зависимости от того, какое окно
Document Window активно, внешний вид главного меню приложения
(и органов управления окна Toolbar, если такое предусмотрено в
приложении) должно изменяться. О том, как динамически изменять
меню, мы рассказывали в первой главе 13 тома "Библиотеки
системного программиста". Теперь нам надо научиться выполнять
такие изменения при активизации окон Document Window.
Функция окна Document Window может обнаружить, когда окно становится
активным (получает фокус ввода) или неактивным (теряет фокус ввода),
отслеживая сообщение WM_MDIACTIVATE. Параметр wParam этого сообщения
принимает значение TRUE, если окно Document Window становится
активным, или FALSE, если оно становится неактивным.
Если окно становится активным, младшее слово параметра lParam
сообщения WM_MDIACTIVATE содержит идентификатор активного окна
Document Window. Если же окно теряет фокус ввода и становится
неактивным, старшее слово параметра lParam сообщения WM_MDIACTIVATE
содержит идентификатор окна Document Window, которое становится
неактивным.
Предусмотрев в функции окна Document Window обработчик сообщения
WM_MDIACTIVATE, приложение может изменять главное меню приложения
и выполнять другие действия в зависимости от того, какое окно
Document Window стало активным.
Идентификация окон Document Window
Обычно приложение связывает с каждым окном Document Window определенную
структуру, в которой хранятся сведения о содержимом и состоянии
каждого конкретного экземпляра окна Document Window.
При регистрации класса окна Document Window вы можете использовать
поле cbClsExtra структуры WNDCLASS, определив в нем место, достаточное
для хранения указателя на структуру, содержащую перечисленную
выше информацию. Эта структура должна инициализироваться каждый
раз при создании нового окна Document Window. Для записи адреса
структуры в память, зарезервированную для окна, используйте функцию
SetWindowLong. Для доступа к структуре в этом случае можно вызвать
функцию GetWindowLong.
Если вы получаете память для структуры динамически, не забывайте
отдавать ее системе при уничтожении окна Document Window.
|