68898

Многооконный интерфейс

Лекция

Информатика, кибернетика и программирование

Эти дочерние окна выглядят совершенно так же как обычные окна приложений. Меню главного окна приложения относится и к окнам документов. В каждый конкретный момент времени только одно окно документа активно об этом говорит выделенная подсветкой строка заголовка и находится над всеми остальными...

Русский

2014-09-26

121.5 KB

2 чел.

Лекция Многооконный интерфейс

В настоящее время любые программы, работающие с документами, позволяют одновременно открывать два, три и более файлов. Естественно предположить, что операционная система должна как-то поддерживать эту возможность и предоставлять программам специальные функции API по работе с этим интерфейсом.

Многооконный интерфейс (Multiple Document Interface, MDI) является спецификацией для приложений, которые обрабатывают документы в Microsoft Windows. Спецификация описывает структуру окон и пользовательский интерфейс, который позволяет пользователю работать с несколькими документами внутри одного приложения (с документами в текстовом процессоре или с таблицами в программе электронных таблиц). Точно также, как Windows поддерживает несколько окон приложений на одном экране, приложение MDI поддерживает несколько окон документов в одной рабочей области. Первым приложением MDI для Windows была первая версия Microsoft Excel. Microsoft Word for Windows и Microsoft Access являются приложениями MDI. Хотя спецификация MDI была введена, уже начиная с Windows 2, в то время писать приложения MDI было трудно, и от программиста требовалось большая очень сложная работа. Однако, начиная с Windows 3, большая часть этой работы была сделана. Windows 95 добавляет только одну новую функцию и одно новое сообщение к набору функций, структурам данных и сообщениям, которые существуют специально для упрощения создания приложений MDI.

Элементы MDI

Главное окно приложения программы MDI обычно: в нем имеется строка заголовка, меню, рамка изменения размера, значок системного меню и значки свертывания. Рабочая область, однако, не используется непосредственно для вывода выходных данных программы. В этой рабочей области может находиться несколько дочерних окон, в каждом из которых отображается какой - документ.

Эти дочерние окна выглядят совершенно так же, как обычные окна приложений. В них имеется строка заголовка, рамка изменения размера, значок системного меню, значки свертывания и, возможно, полосы прокрутки. Однако, ни в одном из окон документов нет меню. Меню главного окна приложения относится и к окнам документов.

В каждый конкретный момент времени только одно окно документа активно (об этом говорит выделенная подсветкой строка заголовка) и находится над всеми остальными окнами документов. Все дочерние окна документов находятся только в рабочей области главного окна приложения и никогда не выходят за ее границы.

Поначалу,  MDI для Windows-программиста кажется совершенно понятным. Все, что нужно сделать — это создать для каждого документа окно WS_CHILD, делая главное окно приложения родительским окном для окна документа. Но при более близком знакомстве с приложением MDI, таким как Microsoft Excel, обнаруживаются определенные трудности, требующие сложного программирования. Например: 

Окно документа MDI может быть свернуто. Соответствующий значок выводится в нижней части рабочей области ( правило: в приложении MDI для главного окна приложения и для каждого типа окна документа будут использоваться разные значки).

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

Системные быстрые клавиши для закрытия окна документа те же , что и для закрытия главного окна, за исключением того, что клавиша <Ctrl> используется вместо клавиши <Alt>. Таким образом, комбинация <Alt>+<F4> закрывает окно приложения, а комбинация <Ctrl>+<F4> закрывает окно документа. Вдобавок к остальным быстрым клавишам, комбинация <Ctrl>+<F6> позволяет переключаться между дочерними окнами документов активного приложения MDI. Комбинация <Alt>+<Spacebar>, как обычно, вызывает системное меню главного окна. Комбинация <Alt>+<-> () вызывает системное меню активного дочернего окна документа .

При использовании клавиш управления курсором для перемещения по пунктам меню, обычно происходит переход от системного меню к первому пункту строки меню. В приложении MDI порядок перехода изменяется: от системного меню приложения — к системному меню активного документа — и далее к первому пункту строки меню.

Если приложение имеет возможность поддерживать несколько типов дочерних окон (электронные таблицы и диаграммы в Microsoft Excel), то меню должно отражать операции, ассоциированные с каждым типом документа. Для этого требуется, чтобы программа изменяла меню программы, когда становится активным окно документа другого типа. Кроме этого, при отсутствии окна документа, в меню должны быть представлены только операции, связанные с открытием нового документа.

В строке основного меню имеется пункт Window. По соглашению, он является последним пунктом строки основного меню, исключая Help. В подменю Window обычно имеются опции для упорядочивания окон документов внутри рабочей области. Окна документов можно расположить cascaded, начиная от верхнего левого угла , или tiled так , что окно каждого документа будет полностью видимо .

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

Все эти аспекты MDI поддерживаются в Windows.

Windows и MDI

При знакомстве с поддержкой MDI в Windows  требуется новая терминология. Окно приложения в целом называется главным окном (frame window). Также как в традиционной программе для Windows, это окно имеет стиль WS_OVERLAPPEDWINDOW.

Приложение MDI также создает окно - (client window) на основе предопределенного класса окна MDICLIENT. Окно создается с помощью вызова функции CreateWindow с использованием этого класса окна и стиля WS_CHILD. Последним параметром функции CreateWindow является указатель на небольшую структуру типа CLIENTCREATESTRUCT. Это окно - охватывает всю рабочую область главного окна и обеспечивает основную поддержку MDI. Цветом окна - является системный цвет COLOR_APPWORKSPACE.

Рис . 11.1. Иерархия родительских и дочерних окон приложения MDI в Windows

Окна документов называются дочерними окнами (child windows). Эти окна создаются путем инициализации структуры типа MDICREATESTRUCT и посылки окну - сообщения WM_MDICREATE с указателем на эту структуру.

Окна документов являются дочерними окнами окна-клиента, которое, в свою очередь, является дочерним окном главного окна. Эта иерархия показана на рис. 11.1

Для главного окна и для каждого типа дочерних окон, которые поддерживаются в приложении, необходим класс окна ( оконная процедура ). Для окна администратора - оконная процедура не нужна, поскольку ее класс окна предварительно зарегистрирован в системе.

Для поддержки MDI в Windows  имеется один класс окна, пять функций, две структуры данных и двенадцать сообщений. О классе окна MDICLIENT и структурах данных CLIENTCREATESTRUCT и MDICREATESTRUCT уже упоминалось. Две из пяти функций заменяют в приложениях MDI функцию DefWindowProc: вместо вызова функции DefWindowProc для всех необрабатываемых сообщений, оконная процедура главного окна вызывает функцию DefFrameProc, а оконная процедура дочернего окна вызывает функцию DefMDIChildProc. Другая характерная функция MDI TranslateMDISysAccel используется также, как функция TranslateAccelerator, о которой рассказывалось в Лекции 10.

Если в дочерних окнах MDI выполняются протяженные во времени операции, рассмотрите возможность их запуска в отдельных потоках. Это позволит пользователю покинуть дочернее окно и продолжить работу в другом окне, пока первое дочернее окно решает свою задачу в фоновом режиме. В Windows специально для этой цели имеется новая функция CreateMDIWindow. Поток вызывает функцию CreateMDIWindow для создания дочернего окна MDI; таким образом окно действует исключительно внутри контекста потока. В программе, имеющей один поток, для создания дочернего окна функция CreateMDIWindow не требуется, поскольку то же самое выполняет сообщение WM_MDICREATE.

Приведем пример - потоковую программы, в которой будет показано девять из двенадцати сообщений MDI. Эти сообщения имеют префикс WM_MDI. Главное окно посылает одно из этих сообщений окну - для выполнения операции над дочерним окном или для получения информации о дочернем окне (главное окно посылает сообщение WM_MDICREATE окну - для создания дочернего окна). Исключение составляет сообщение WM_MDIACTIVATE: в то время, как главное окно может послать это сообщение окну – для активизации одного из дочерних окон, окно - также посылает сообщение тем дочерним окнам, которые будут активизированы и тем, которые потеряют активность, чтобы проинформировать их о предстоящем изменении.

Пример программы

#include <windows.h>

#include <stdlib.h>

#include "mdidemo.h"

//Предварительное объявление функций

LRESULT CALLBACK FrameWndProc (HWND, UINT, WPARAM, LPARAM);

BOOL CALLBACK CloseEnumProc(HWND, LPARAM);

LRESULT CALLBACK HelloWndProc (HWND, UINT, WPARAM, LPARAM);

LRESULT CALLBACK RectWndProc (HWND, UINT, WPARAM, LPARAM);

//  Глобальные переменнве

char szFrameClass[] = "MdiFrame";

char szHelloClass[] = "MdiHelloChild";

char szRectClass[] = "MdiRectChild";

HINSTANCE hInst;

HMENU hMenuInit, hMenuHello, hMenuRect;

HMENU hMenuInitWindow, hMenuHelloWindow, hMenuRectWindow;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

 HACCEL hAccel;

 HWND hwndFrame, hwndClient;

 MSG msg;

 WNDCLASSEX wndclass;

 hInst = hInstance;

 if(!hPrevInstance)

 {

   // Регистрация класса главного окна

    wndclass.cbSize = sizeof(wndclass);

    wndclass.style = CS_HREDRAW | CS_VREDRAW;

    wndclass.lpfnWndProc = FrameWndProc;

    wndclass.cbClsExtra = 0;

    wndclass.cbWndExtra = 0;

    wndclass.hInstance = hInstance;

    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);

    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

    wndclass.hbrBackground =(HBRUSH)(COLOR_APPWORKSPACE + 1);

    wndclass.lpszMenuName = NULL;

    wndclass.lpszClassName = szFrameClass;

    wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassEx(&wndclass);

  // Регистрация класса окна, в котором выводится фраза "Привет,  мир!"

    wndclass.cbSize = sizeof(wndclass);

    wndclass.style = CS_HREDRAW | CS_VREDRAW;

    wndclass.lpfnWndProc = HelloWndProc;

    wndclass.cbClsExtra = 0;

    wndclass.cbWndExtra = sizeof(HANDLE);

    wndclass.hInstance = hInstance;

    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);

    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

    wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH);

    wndclass.lpszMenuName = NULL;

    wndclass.lpszClassName = szHelloClass;

    wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassEx(&wndclass);

   // Регистрация класса окна, в котором выводится прямоугольник

    wndclass.cbSize = sizeof(wndclass);

    wndclass.style = CS_HREDRAW | CS_VREDRAW;

    wndclass.lpfnWndProc = RectWndProc;

    wndclass.cbClsExtra = 0;

    wndclass.cbWndExtra = sizeof(HANDLE);

    wndclass.hInstance = hInstance;

    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);

    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

    wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH);

    wndclass.lpszMenuName = NULL;

    wndclass.lpszClassName = szRectClass;

    wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassEx(&wndclass);

 }

// Формирование описателей меню для разных окон

hMenuInit = LoadMenu(hInst, "MdiMenuInit");

hMenuHello = LoadMenu(hInst, "MdiMenuHello");

hMenuRect = LoadMenu(hInst, "MdiMenuRect");

hMenuInitWindow = GetSubMenu(hMenuInit, INIT_MENU_POS);

hMenuHelloWindow = GetSubMenu(hMenuHello, HELLO_MENU_POS);

hMenuRectWindow = GetSubMenu(hMenuRect, RECT_MENU_POS);

// Чтение горячих клавиш

hAccel = LoadAccelerators(hInst, "MdiAccel");

// Создание главного окна

hwndFrame = CreateWindow(szFrameClass, "MDI Demonstration",

                       WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,

                      CW_USEDEFAULT, CW_USEDEFAULT,

                      CW_USEDEFAULT, CW_USEDEFAULT,

                      NULL, hMenuInit, hInstance, NULL);

 

hwndClient = GetWindow(hwndFrame, GW_CHILD);

ShowWindow(hwndFrame, iCmdShow);

UpdateWindow(hwndFrame);

// Цикл обработки сообщений

while(GetMessage(&msg, NULL, 0, 0))

 {

  if(!TranslateMDISysAccel(hwndClient, &msg) &&

      !TranslateAccelerator(hwndFrame, hAccel, &msg))

       {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

      }

}

// Удаление меню

DestroyMenu(hMenuHello);

DestroyMenu(hMenuRect);

return msg.wParam;

}

// оконная процедура главного окна

LRESULT CALLBACK FrameWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

static HWND hwndClient;

CLIENTCREATESTRUCT clientcreate;

HWND hwndChild;

MDICREATESTRUCT mdicreate;

switch(iMsg)

{

 case WM_CREATE : // Создание клиентского окна

                                   clientcreate.hWindowMenu = hMenuInitWindow;

                                   clientcreate.idFirstChild = IDM_FIRSTCHILD;

                                   hwndClient = CreateWindow("MDICLIENT", NULL,

                                                                                    WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,

                                                                                    0, 0, 0, 0, hwnd,(HMENU) 1, hInst, (LPSTR) &clientcreate);

                                    return 0;

case WM_COMMAND:

switch(wParam)

{

   case IDM_NEWHELLO: // Создание дочернего окна,  в котором выводится фраза "Привет,  мир!"

                                           mdicreate.szClass = szHelloClass;

       mdicreate.szTitle = "Hello";

                         mdicreate.hOwner = hInst;

                         mdicreate.x = CW_USEDEFAULT;

                         mdicreate.y = CW_USEDEFAULT;

                         mdicreate.cx = CW_USEDEFAULT;

                         mdicreate.cy = CW_USEDEFAULT;

                         mdicreate.style = 0;

                         mdicreate.lParam = 0;

       hwndChild =(HWND) SendMessage(hwndClient, WM_MDICREATE, 0,

(LPARAM)(LPMDICREATESTRUCT) &mdicreate);

       return 0;

 case IDM_NEWRECT : // Создание дочернего окна, в котором выводится прямоугольник

      mdicreate.szClass = szRectClass;

      mdicreate.szTitle = "Rectangles";

      mdicreate.hOwner = hInst;

      mdicreate.x = CW_USEDEFAULT;

     mdicreate.y = CW_USEDEFAULT;

      mdicreate.cx = CW_USEDEFAULT;

      mdicreate.cy = CW_USEDEFAULT;

      mdicreate.style = 0;

      mdicreate.lParam = 0;

      hwndChild =(HWND) SendMessage(hwndClient, WM_MDICREATE, 0,

(LPARAM)(LPMDICREATESTRUCT) &mdicreate);

      return 0;

   case IDM_CLOSE : // Закрыти активного окна

hwndChild =(HWND) SendMessage(hwndClient, WM_MDIGETACTIVE, 0, 0);

if (SendMessage(hwndChild, WM_QUERYENDSESSION, 0, 0))

SendMessage(hwndClient, WM_MDIDESTROY, (WPARAM) hwndChild, 0);

return 0;

  case IDM_EXIT: // Выход из программы

     SendMessage(hwnd, WM_CLOSE, 0, 0);

return 0;

//Сообщения о сортироке окон

  case IDM_TILE :

SendMessage(hwndClient, WM_MDITILE, 0, 0);

return 0;

  case IDM_CASCADE :

SendMessage(hwndClient, WM_MDICASCADE, 0, 0);

return 0;

  case IDM_ARRANGE :

SendMessage(hwndClient, WM_MDIICONARRANGE, 0, 0);

return 0;

  case IDM_CLOSEALL : // Закрыть все дочерние окна

  EnumChildWindows(hwndClient, &CloseEnumProc, 0);

return 0;

 default :

hwndChild =(HWND) SendMessage(hwndClient, WM_MDIGETACTIVE, 0, 0);

if(IsWindow(hwndChild))

SendMessage(hwndChild, WM_COMMAND, wParam, lParam);

break; // ...and then to DefFrameProc

}

break;

case WM_QUERYENDSESSION:

case WM_CLOSE: // Attempt to close all children

SendMessage(hwnd, WM_COMMAND, IDM_CLOSEALL, 0);

if(NULL != GetWindow(hwndClient, GW_CHILD))

return 0;

break;

case WM_DESTROY :

PostQuitMessage(0);

return 0;

}

// Использоваие DefFrameProc(not вместо DefWindowProc

return DefFrameProc(hwnd, hwndClient, iMsg, wParam, lParam);

}

// Функция закрытия окон

BOOL CALLBACK CloseEnumProc(HWND hwnd, LPARAM lParam)

{

 if(GetWindow(hwnd, GW_OWNER))  return 1;

 SendMessage(GetParent(hwnd), WM_MDIRESTORE,(WPARAM) hwnd, 0);

 if(!SendMessage(hwnd, WM_QUERYENDSESSION, 0, 0)) return 1;

 SendMessage(GetParent(hwnd), WM_MDIDESTROY,(WPARAM) hwnd, 0);

 return 1;

}

// Оконная процедура первого окна

LRESULT CALLBACK HelloWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

static COLORREF clrTextArray[] = { RGB(0, 0, 0), RGB(255, 0, 0),

RGB(0, 255, 0), RGB( 0, 0, 255),

RGB(255, 255, 255) };

static HWND hwndClient, hwndFrame;

HDC hdc;

HMENU hMenu;

LPHELLODATA lpHelloData;

PAINTSTRUCT ps;

RECT rect;

switch(iMsg)

 {

case WM_CREATE :

// Save some window handles

hwndClient = GetParent(hwnd);

hwndFrame = GetParent(hwndClient);

return 0;

case WM_PAINT :

// Рисование

  hdc = BeginPaint(hwnd, &ps);

GetClientRect(hwnd, &rect);

DrawText(hdc, "Привет, Мир!", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

EndPaint(hwnd, &ps);

return 0;

case WM_MDIACTIVATE :

// Установка нового меню

if(lParam ==(LPARAM) hwnd)

SendMessage(hwndClient, WM_MDISETMENU,

(WPARAM) hMenuHello,(LPARAM) hMenuHelloWindow);

DrawMenuBar(hwndFrame);

return 0;

case WM_QUERYENDSESSION :

case WM_CLOSE :

 if(IDOK != MessageBox(hwnd, "OK to close window?", "Hello",

MB_ICONQUESTION | MB_OKCANCEL))

return 0;

break;

case WM_DESTROY :

return 0;

}

return DefMDIChildProc(hwnd, iMsg, wParam, lParam);

}

// Оконная процедура второго дочернего окна

LRESULT CALLBACK RectWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

static HWND hwndClient, hwndFrame;

HBRUSH hBrush;

HDC hdc;

LPRECTDATA lpRectData;

PAINTSTRUCT ps;

int xLeft, xRight, yTop, yBottom;

short nRed, nGreen, nBlue;

switch(iMsg)

{

 case WM_CREATE :

  // Save some window handles

hwndClient = GetParent(hwnd);

hwndFrame = GetParent(hwndClient);

return 0;

case WM_PAINT : // Рисование

hdc = BeginPaint(hwnd, &ps);

Rectangle(hdc,10,10,200,200);

EndPaint(hwnd, &ps);

return 0;

 case WM_MDIACTIVATE : // Установка меню.

      if(lParam ==(LPARAM) hwnd)

SendMessage(hwndClient, WM_MDISETMENU,(WPARAM) hMenuRect,

(LPARAM) hMenuRectWindow);

else

SendMessage(hwndClient, WM_MDISETMENU,(WPARAM) hMenuInit,

(LPARAM) hMenuInitWindow);

DrawMenuBar(hwndFrame);

return 0;

case WM_DESTROY :

return 0;

}

// Пересылка необработанных сообщений в DefMDIChildProc

return DefMDIChildProc(hwnd, iMsg, wParam, lParam);

}

Рис . 11.2 Текст программы MDIDEMO

Программа MDIDEMO поддерживает два типа предельно простых окон документов: в одном в центре рабочей области выводится фраза "Hello, World!", а во втором отображается прямоугольник. С каждым из этих двух типов окон документов связано свое меню.

Три меню

Начнем анализ программы с файла описания ресурсов MDIDEMO.RC. В нем определяются три шаблона меню, используемые в программе. Программа выводит на экран меню MdiMenuInit при отсутствии окон документов. Это меню просто позволяет создать новый документ или завершить программу. Меню MdiMenuHello связано с окном документа, в котором выводится фраза "Hello, World!". Подменю File позволяет открыть новый документ любого типа, закрыть активный документ и завершить программу. В подменю Window имеются опции для упорядочивания окон документов в каскадном или мозаичном виде, упорядочивания значков документов и закрытия всех окон. В этом подменю также имеется список всех открытых окон документов.

Меню MdiMenuRect связано с документом с прямоугольником. В заголовочном файле MDIDEMO.Н все идентификаторы меню определяются как три константы:

#define INIT_MENU_POS 0

#define HELLO_MENU_POS 2

#define RECT_MENU_POS 1

Эти идентификаторы задают положение подменю Window в каждом из трех шаблонов меню. Эта информация необходима программе, чтобы информировать окно -клиент о том , когда должен появиться список документов. Конечно, в меню MdiMenuInit нет подменю Window, поэтому в файле обозначено, что список должен быть присоединен к первому подменю. Однако, фактически здесь список никогда не появится. ( он нужен станет ясно при дальнейшем анализе программы .)

Идентификатор IDM_FIRSTCHILD не соответствует ни одному пункту меню. Этот идентификатор будет связан с первым окном документа в списке, который появляется в подменю Window. Значение этого идентификатора должно быть больше, чем значение всех остальных идентификаторов меню.

Инициализация программы

В файле MDIDEMO.С WinMain начинается с регистрации классов окна для главного окна и двух дочерних окон. Их оконные процедуры называются FrameWndProc, HelloWndProc и RectWndProc. Как правило, с каждым из этих трех классов связан свой значок. Для простоты в программе и для главного и для дочерних окон использован стандартный значок IDI_APPLICATION. Обратите внимание, что в поле hbrBackground структуры WNDCLASSEX для класса главного окна задан системный цвет COLOR_APPWORKSPACE. Однако, когда главное окно появляется на экране первым, одинаковый цвет смотрится несколько лучше.

В поле lpszMenuName заносится значение NULL для каждого из трех классов окна. Для классов окна дочерних окон Hello и Rect это нормально. Для главного окна это сделано для того, чтобы указать описатель меню при создании главного окна в функции CreateWindow.

Далее WinMain использует функцию LoadMenu для загрузки трех меню и сохраняет их описатели в глобальных переменных. Три вызова функции GetSubMenu позволяют получить описатели подменю Window, в которому будет добавлен список окон документов. Они также запоминаются в глобальных переменных. Функция LoadAccelerators загружает таблицу быстрых клавиш.

Вызов функции CreateWindow в WinMain создает главное окно. При обработке в FrameWndProc сообщения WM_CREATE главное окно создает окно - администратор. При этом еще раз вызывается функция CreateWindow. Класс окна задается как MDICLIENT, который представляет собой уже зарегистрированный в системе класс для окна - MDI. Последний параметр функции CreateWindow должен быть указателем на структуру типа CLIENTCREATESTRUCT. В этой структуре имеется два поля: 

hWindowMenu является описателем подменю, в котором появится список документов. В программе MDIDEMO это описатель hMenuInitWindow, полученный в WinMain. Позже мы узнаем, как изменить меню.

idFirstChild является идентификатором меню , относящимся к первому окну документа в списке документов.

Он просто равен IDM_FIRSTCHILD.

Вернемся к WinMain. Программа MDIDEMO выводит на экран только что созданное главное окно и входит в цикл обработки сообщений. Этот цикл обработки сообщений немного отличается от обычного: после получения сообщения из очереди при помощи функции GetMessage программа MDI передает сообщение функции TranslateMDISysAccel (функции TranslateAccelerator, если, как и в программе MDIDEMO, в программе также имеются быстрые клавиши меню ).

Функция TranslateMDISysAccel преобразует любые комбинации клавиш, которые могут соответствовать специальным быстрым клавишам MDI (<Ctrl>+<F6>), в сообщение WM_SYSCOMMAND. Если одна из функций TranslateMDISysAccel или TranslateAccelerator возвращает TRUE (означает, что сообщение было преобразовано одной из этих функций ), то вызова функции TranslateMessage и функции DispatchMessage не происходит.

Обратите внимание на два описателя окон, передаваемые функциям TranslateMDISysAccel и TranslateAccelerator: соответственно hwndClient и hwndFrame. Функция WinMain получает описатель окна hwndClient, используя вызов функции GetWindow с параметром GW_CHILD.

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

Часть FrameWndProc связана с обработкой сообщений WM_COMMAND, которые информируют о выборе какого – либо пункта меню. Как обычно, параметр wParam сообщения в FrameWndProc содержит идентификатор меню.

При значениях параметра wParam IDM_NEWHELLO и IDM_NEWRECT, FrameWndProc должна создать новое окно документа. Это требует инициализации полей структуры MDICREATESTRUCT (часть которых соответствует параметрам функции CreateWindow) и отправки окну – сообщения WM_MDICREATE с параметром lParam, равным указателю на эту структуру. Затем окно – создает дочернее окно документа. FrameWndProc, вызывая функцию CreateMDIWindow, могла бы сама создать это дочернее окно. Для программы, имеющей один поток, такой как MDIDEMO, можно выбрать любой из этих методов.

Как правило, поле szTitle структуры MDICREATESTRUCT является именем файла, соответствующего документу. В поле style могут быть заданы стили окна WS_HSCROLL, или WS_VSCROLL, или оба вместе, что позволяет включить в окно документа полосы прокрутки . ( ShowScrollBar для вывода полос прокрутки на экран вызывать не обязательно). В поле style могут быть также указаны стили WS_MINIMIZE или WS_MAXIMIZE для первого появления окна документа в свернутом или развернутом состоянии.

Поле lParam структуры MDICREATESTRUCT дает возможность главному и дочернему окну использовать некоторые общие переменные. Этому полю можно присвоить значение описателя памяти , соответствующего блоку памяти , содержащему структуру . При обработке сообщения WM_CREATE в дочернем окне документа , параметр lParam — это указатель на структуру CREATESTRUCT, а поле lpCreateParams этой структуры — это указатель на структуру MDICREATESTRUCT, используемую для создания окна .

При получении сообщения WS_MDICREATE окно - создает дочернее окно документа и добавляет заголовок окна к нижней части подменю, заданного в структуре MDICREATESTRUCT, которая используется для создания окна. Когда программа MDIDEMO создает свое первое окно документа, этим подменю является подменю File меню MdiMenuInit. Позже мы увидим, как этот список документов переносится в подменю Window меню MdiMenuHello и MdiMenuRect.

В меню может быть перечислено до девяти документов, перед каждым из которых ставится номер от 1 до 9. Номер подчеркивается. Если создается более девяти окон документов, то за этим списком появляется пункт меню "More windows". Выбор этого пункта вызывает появление окна диалога содержащего список, в котором перечислены все окна документов. Поддержка такого списка документов — это одно из самых лучших характеристик поддержки MDI в Windows.

Дополнительная информация об обработке сообщений в главном окне

Перед тем, как перейти к рассмотрению дочерних окон документов, разберемся с обработкой сообщений в FrameWndProc. При выборе в меню File опции Close программа MDIDEMO закрывает активное дочернее окно. Описатель активного дочернего окна она получает, посылая окну - сообщение WM_MDIGETACTIVE. Если дочернее окно отвечает утвердительно на сообщение WM_QUERYENDSESSION, то программа MDIDEMO для закрытия дочернего окна посылает окну - сообщение WM_MDIDESTROY.

Для обработки опции Exit меню File необходимо только, чтобы оконная процедура главного окна послала себе сообщение WM_CLOSE.

Обработать опции Tile, Cascade и Arrange Icons из подменю Window проще простого, нужно только послать окну - сообщения WM_MDITILE, WM_MDICASCADE и WM_MDIICONARRANGE. 

Обработка опции Close All несколько сложнее. FrameWndProc вызывает функцию EnumChildWindows, передавая указатель на функцию CloseEnumProc. Эта функция посылает сообщение WM_MDIRESTORE каждому дочернему окну, затем сообщение WM_QUERYENDSESSION и сообщение WM_MDIDESTROY. Этого не делается для окна заголовка значка, определяемого, если возвращаемое значение функции GetWindow c параметром GW_OWNER не равно NULL.

Все сообщения , которые оконная процедура главного окна не обрабатывает , должны передаваться в DefFrameProc. Эта функция заменяет в оконной процедуре главного окна функцию DefWindowProc. Даже если оконная процедура главного окна и перехватывает сообщения WM_MENUCHAR, WM_SETFOCUS или WM_SIZE, все равно они должны передаваться в DefFrameProc.

Необрабатываемые сообщения WM_COMMAND также должны передаваться в DefFrameProc. В частности, FrameWndProc не обрабатывает сообщений WM_COMMAND, появившихся в результате того, что пользователь выбирает один из документов из списка в подменю Window. ( параметра wParam для этих опций начинается с IDM_FIRSTCHILD.) Эти сообщения передаются в DefFrameProc и обрабатываются там. Обратите внимание , что главному окну не нужно поддерживать список описателей окон всех созданных документов . При необходимости (, при обработке опции Close All из меню ), эти описатели можно получить , вызывая функцию EnumChildWindows.

Дочерние окна документов

Теперь рассмотрим HelloWndProc — оконную процедуру тех дочерних окон документов , которые выводят на экран фразу "Привет, мир!".

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

Оконная процедура окна документа получает сообщение WM_MDIACTIVATE всегда, когда окно становится активным или перестает быть активным ( зависимости от того , содержится ли описатель окна в параметре lParam этого сообщения ). Вспомните, что в программе MDIDEMO имеется три различных меню: MdiMenuInit выводится, если нет ни одного окна документа, MdiMenuHello выводится, если активно окно документа Hello, и MdiMenuRect выводится , если активно окно документа с прямоугольниками .

Сообщение WM_MDIACTIVATE дает возможность окну документа изменить меню. Если в параметре lParam этого сообщения содержится описатель окна (окно становится активным), HelloWndProc изменяет меню на MdiMenuHello. Если в параметре lParam этого сообщения содержится описатель другого окна, HelloWndProc преобразует меню в MdiMenuInit.

HelloWndProc изменяет меню путем отправки сообщения WM_MDISETMENU окну - администратору. Окно - администратору обрабатывает это сообщение, удаляя список документов из текущего меню и присоединяя его к новому меню. Таким образом список документов попадает из меню MdiMenuInit (является результатом создания первого документа) в меню MdiMenuHello. Не используйте для изменения меню в приложении MDI функцию SetMenu. 

Значения параметров wParam и lParam сообщения WM_MDIACTIVATE являются, соответственно, описателями окна , которое перестает быть активным, и окна, становящегося активным. Оконная процедура получает первое сообщение WM_MDIACTIVATE с параметром lParam равным описателю текущего окна , когда это окно впервые создается , а когда окно закрывается , она получает последнее сообщение WM_MDIACTIVATE с параметром lParam равным другому значению . Когда пользователь переключается с одного документа на другой, первое окно документа получает сообщение WM_MDIACTIVATE с параметром lParam равным описателю первого окна ( это время оконная процедура устанавливает меню в MdiMenuInit). Второе окно документа получает сообщение WM_MDIACTIVATE с параметром wParam равным описателю второго окна (это время оконная процедура устанавливает меню либо в MdiMenuHello, либо в MdiMenuRect, в зависимости от описателя). При закрытии всех окон документов остается только меню MdiMenuInit. Вспомните, что FrameWndProc посылает дочернему окну асинхронное сообщение WM_QUERYENDSESSION, когда пользователь выбирает в меню опцию Close или Close All. HelloWndProc обрабатывает сообщения WM_QUERYENDSESSION и WM_CLOSE, выводя на экран окно сообщений с запросом пользователю о том, можно ли закрывать окно . ( реальной программе в этом окне сообщений может появляться запрос о том , нужно ли сохранять файл .) Если пользователь выбирает опцию , соответствующую тому , что окно закрывать не следует , оконная процедура возвращает 0.

Все необрабатываемые сообщения должны передаваться в DefMDIChildProc (не в DefWindowProc) для их обработки по умолчанию. Некоторые сообщения должны быть переданы в DefMDIChildProc независимо от того, обрабатываются ли они как - в оконной процедуре или нет . Такими сообщениями являются:

WM_CHILDACTIVATE, WM_GETMINMAXINFO, WM_MOVE, WM_SETFOCUS, WM_SIZE, WM_MENUCHAR и WM_SYSCOMMAND.

RectWndProc почти полностью аналогична HelloWndProc, поэтому нет смысла ее специально рассматривать.

Освобождение захваченных ресурсов

Программа MDIDEMO в функции WinMain использует функцию LoadMenu для загрузки трех меню, определенных в файле описания ресурсов. Обычно Windows удаляет меню, когда закрывается окно, к которому меню относится. Это касается и меню MdiMenuInit. Однако, меню, не относящиеся к какому бы то ни было окну (программе MDIDEMO это меню Hello и Rect), будут продолжать занимать некоторую область памяти, даже после завершения программы . Поэтому , для освобождения памяти, занимаемой меню Hello и Rect, в программе MDIDEMO функция DestroyMenu в WinMain вызывается дважды .

Сила оконной процедуры

Большая часть того, что в Windows создано для поддержки многооконного интерфейса, заключено в классе окна MDICLIENT. В этом совершенно отчетливо проявляется сила объектной архитектуры Windows. Оконная процедура окна - служит, как бы, связующим звеном между главным окном и различными окнами документов.


 

А также другие работы, которые могут Вас заинтересовать

78546. Учимся жить дружно 45.5 KB
  Цель классного часа - формирование добрых отношений между детьми в классе, развитие стремления быть терпимым в обществе людей, воспитания уважения к одноклассникам.
78547. Знай, люби, бережи природу рідного краю 4.85 MB
  Змістові питання Розкрити цінності природи для людини Показати різноманітність кімнатних рослин Скласти правила догляду за кімнатними рослинами Визначити рослини в народній творчості Визначити групи птахів за способом живлення та місцем проживання...
78548. Жива природа 184.5 KB
  Когнітивні рівні Конкретизовані навчальні цілі Перелік завдань –запитань які забезпечують досягнення конкретних цілей Навчальне методичне забезпечення Знання формулює визначення поняття природа; знає що таке жива і нежива природа; називає основні 5 царств живої природи...
78549. Гриби та дроб’янки 2.61 MB
  Формування предметних компетентностей: формувати в учнів поняття: гриби дроб’янки бактерії з’ясувати чому це окремі царства живої природи; знайомити дітей із найпоширенішими видами їстівних та отруйних грибів; прослідкувати вплив корисних та хвороботворних бактерій на довкілля...
78550. Формы земной поверхности Украины 116 KB
  Сегодня нас ждут наверно не зря: Холмы и равнины овраги и горы Чтобы больше о них узнать Надо карту уметь читать Быть внимательным уметь наблюдать. Горы обладают удивительным свойством настраивать человека на особый торжественный лад.
78551. Посвята в першокласники 137.5 KB
  Я всміхаюсь сонечку: Здрастуй золоте Я всміхаюсь квіточці: Хай вона росте Я всміхаюсь дощику: Лийся мов з відра Усміхаюсь людям Зичу їм добра Вчитель: Дітки Першого вересня ви переступили поріг школи. Ви прийшли до школи щоб навчитись читати й писати рахувати а також бути хорошими людьми.
78552. МОЇ ПРИХОВАНІ ПОТЕНЦІАЛИ — МОЯ ІНДИВІДУАЛЬНІСТЬ 204.5 KB
  Якщо ви хочете бути задоволені вашою роботою вам також необхідно зрозуміти що ви за особистість. Можна задатися питанням чи так вже необхідно визначати особові параметри. З свого досвіду ми знаємо що на короткий проміжок часу ми можемо поступати так як нам не властиво.