Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Операционная система 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

[Назад] [Содеожание] [Дальше]