Операционная система Microsoft Windows 3.1 для программиста. Дополнительные главы© Александр Фролов, Григорий ФроловТом 17, М.: Диалог-МИФИ, 1994, 287 стр. 1.6. Приложение MDIAPPИтак, теперь мы готовы приступить к созданию 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, "Приложение MDIAPP\n(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 |