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

Операционная система Microsoft Windows 3.1 для программиста

© Александр Фролов, Григорий Фролов
Том 13, М.: Диалог-МИФИ, 1993, 284 стр.

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

1.6. Приложение DMENU

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

Сразу после запуска приложения в полосе меню определены два временных меню - "File" и "Help" (рис. 1.15). В меню "File" вы можете использовать строки "New" и "Open", предназначенные, соответственно, для создания нового документа или загрузки документа из файла. Кроме этих двух строк вы можете выбрать строку "Exit", завершающую работу приложения. Строка "Demo Version" заблокирована и отмечена галочкой. Так как мы еще не научились работать с принтером, строки "Print", "Page Setup" и "Printer Setup" заблокированы и отображаются серым цветом.

Рис. 1.15. Исходный вид меню приложения DMENU

Пока вы не создали новый документ или не загрузили документ, созданный ранее, строки "Close", "Save", "Save as..." заблокированы. Так как документ не загружен, его нельзя закрыть или сохранить, поэтому соответствующие строки в меню отображаются серым цветом.

После того, как вы выберете строку "New" или "Open", внешний вид меню приложения изменится (рис. 1.16).

Рис. 1.16. Изменения в меню приложения DMENU

Так как приложение DMENU рассчитано на "работу" с одним документом, после загрузки документа строки "New" и "Open" блокируются. Для их разблокирования вы должны закрыть документ, выбрав строку "Close". В этом случае меню приложения примет исходный вид, представленный на рис. 1.15.

После загрузки документа в меню "File" разблокируются строки "Close", "Save" и "Save as...". Кроме этого, появляется новое временное меню "Edit", аналогичное меню "Edit" предыдущего приложения. Меню "Edit" присутствует в окне только тогда, когда загружен документ. Если документ не загружен, то редактировать нечего. В этом случае меню "Edit" не нужно.

Многие приложения Windows изменяют меню похожим образом. Внешний вид меню может зависеть от типа обрабатываемого документа, от используемых параметров и режимов (краткое меню, полное меню, расширенное меню и т. д.) или от текущего состояния документа.

Исходный текст главного файла приложения DMENU, реализующего описанный выше алгоритм изменения меню, представлен в листинге 1.5.


Листинг 1.5. Файл dmenu/dmenu.cpp


// ----------------------------------------
// Создание меню без использования шаблона
// Динамическое изменение меню
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mem.h>
#include "dmenu.hpp"

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна
char const szClassName[]   = "DMenuClass";

// Заголовок окна
char const szWindowTitle[] = "Menu Demo";

// Идентификатор меню верхнего уровня
HMENU hmenu;

// Идентификаторы временных меню
HMENU hmenuFile; // "File"
HMENU hmenuEdit; // "Edit"
HMENU hmenuHelp; // "Help"

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance, 
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine, 
        int       nCmdShow)
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Инициализируем приложение
  if(!InitApp(hInstance))
      return FALSE;

  // После успешной инициализации приложения создаем
  // главное окно приложения
  hwnd = CreateWindow(
    szClassName,         // имя класса окна
    szWindowTitle,       // заголовок окна
    WS_OVERLAPPEDWINDOW, // стиль окна
    CW_USEDEFAULT,       // задаем размеры и расположение
    CW_USEDEFAULT,       // окна, принятые по умолчанию
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    0,                   // идентификатор родительского окна
    0,                   // идентификатор меню
    hInstance,           // идентификатор приложения
    NULL);               // указатель на дополнительные
                         // параметры
  // Если создать окно не удалось, завершаем приложение
  if(!hwnd)
    return FALSE;

  // Рисуем главное окно
  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================

BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна
  // Записываем во все поля структуры нулевые значения
  memset(&wc, 0, sizeof(wc));

  wc.lpszMenuName  = NULL;
  wc.style         = 0;
  wc.lpfnWndProc   = (WNDPROC) WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszClassName = (LPSTR)szClassName;

  // Регистрация класса
  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}

// =====================================
// Функция WndProc
// =====================================

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    case WM_CREATE:
    {
      // Создаем пустое меню верхнего уровня
      hmenu = CreateMenu();

      // Подключаем меню к главному окну приложения
      SetMenu(hwnd, hmenu);

      // Создаем два временных меню - "File" и "Help"
      hmenuFile = CreatePopupMenu();
      hmenuHelp = CreatePopupMenu();

      // Добавляем строки к меню "File"
      AppendMenu(hmenuFile, MF_ENABLED | MF_STRING,
         CM_FILENEW,    "&New");
      AppendMenu(hmenuFile, MF_ENABLED | MF_STRING,
         CM_FILEOPEN,   "&Open");
      AppendMenu(hmenuFile, MF_GRAYED | MF_STRING,
         CM_FILECLOSE,  "&Close");
      AppendMenu(hmenuFile, MF_GRAYED | MF_STRING,
         CM_FILESAVE,   "&Save");
      AppendMenu(hmenuFile, MF_GRAYED | MF_STRING,
         CM_FILESAVEAS, "Save &as...");

      // Добавляем разделительную линию
      AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL);

      AppendMenu(hmenuFile, MF_GRAYED | MF_STRING,
         CM_FILEPRINT,         "&Print");
      AppendMenu(hmenuFile, MF_GRAYED | MF_STRING,
         CM_FILEPAGE_SETUP,    "Page Se&tup");
      AppendMenu(hmenuFile, MF_GRAYED | MF_STRING,
         CM_FILEPRINTER_SETUP, "P&rinter Setup");

      AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL);

      AppendMenu(hmenuFile, MF_DISABLED | MF_STRING,
         CM_FILEDEMO, "&Demo Version");

      AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL);

      AppendMenu(hmenuFile, MF_ENABLED | MF_STRING,
         CM_FILEEXIT, "E&xit");

      // Отмечаем галочкой строку "Demo Version"
      CheckMenuItem(hmenuFile, CM_FILEDEMO,
         MF_BYCOMMAND | MF_CHECKED);

      // Добавляем строки к меню "Help"
      AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING,
         CM_HELPINDEX, "&Index\tF1");
      AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING,
         CM_HELPKEYBOARD, "&Keyboard");
      AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING,
         CM_HELPCOMMANDS, "&Commands");
      AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING,
         CM_HELPPROCEDURES, "&Procedures");
      AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING,
         CM_HELPUSING_HELP, "&Using help");

      AppendMenu(hmenuHelp, MF_SEPARATOR, 0, NULL);

      AppendMenu(hmenuHelp, MF_ENABLED | MF_STRING,
         CM_HELPABOUT, "&About...");

      // Добавляем временные меню к меню верхнего уровня
      AppendMenu(hmenu, MF_ENABLED | MF_POPUP,
         (UINT)hmenuFile, "&File");
      AppendMenu(hmenu, MF_ENABLED | MF_POPUP,
         (UINT)hmenuHelp, "&Help");

      // Записываем в идентификатор меню "Edit" значение
      // NULL. Если это меню не будет создано, мы не будем
      // вызывать функцию DestroyMenu для его уничтожения
      hmenuEdit = NULL;

      // Перерисовываем меню
      DrawMenuBar(hwnd);

      return 0;
    }

    case WM_COMMAND:
    {
      switch (wParam)
      {
        // Сообщения от меню
        case CM_HELPUSING_HELP:
        case CM_HELPPROCEDURES:
        case CM_HELPCOMMANDS:
        case CM_HELPKEYBOARD:
        case CM_HELPINDEX:
        case CM_EDITPASTE:
        case CM_EDITCOPY:
        case CM_EDITCUT:
        case CM_EDITUNDO:
        case CM_FILEPRINTER_SETUP:
        case CM_FILEPAGE_SETUP:
        case CM_FILEPRINT:
        case CM_FILESAVEAS:
        case CM_FILESAVE:
        {
          // Выводим сообщение об ошибке
          MessageBox(hwnd, "Функция не реализована",
            NULL, MB_OK);
          return 0;
        }

        // Выбрали строку "About..." в меню "Help"
        case CM_HELPABOUT:
        {
           MessageBox(hwnd, 
             "Приложение DMENU\n(C) Фролов А.В., 1994",
             szWindowTitle, MB_OK | MB_ICONINFORMATION);
           return 0;
        }

        // Выбрали строки "Open" или "New" в меню "File"
        case CM_FILEOPEN:
        case CM_FILENEW:
        {
          // Создаем временное меню "Edit"
          hmenuEdit = CreatePopupMenu();

          // Добавляем строки в меню "Edit"
         AppendMenu(hmenuEdit, MF_GRAYED  | MF_STRING,
           CM_EDITUNDO,  "&Undo\tCtrl+Z");
         AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING,
           CM_EDITCUT,   "&Cut\tCtrl+X");
         AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING,
           CM_EDITCOPY,  "&Copy\tCtrl+C");
         AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING,
           CM_EDITPASTE, "&Paste\tCtrl+V");

         // Вставляем меню "Edit" между меню "File" 
         // и в меню "Help"
         InsertMenu(hmenu, 1,
            MF_BYPOSITION | MF_ENABLED | MF_POPUP,
            (UINT)hmenuEdit, "&Edit");

         // Разблокируем строки "Save", "Save as..."
         //  и "Close" в меню "File"
         EnableMenuItem(hmenuFile, CM_FILESAVE,
           MF_ENABLED | MF_BYCOMMAND);
         EnableMenuItem(hmenuFile, CM_FILESAVEAS,
           MF_ENABLED | MF_BYCOMMAND);
         EnableMenuItem(hmenuFile, CM_FILECLOSE,
           MF_ENABLED | MF_BYCOMMAND);

         // Блокируем строки "New" и "Open" в меню "File"
         EnableMenuItem(hmenuFile, CM_FILENEW,
           MF_GRAYED | MF_BYCOMMAND);
         EnableMenuItem(hmenuFile, CM_FILEOPEN,
           MF_GRAYED | MF_BYCOMMAND);

         // Перерисовываем меню
         DrawMenuBar(hwnd);
         return 0;
       }

       // Выбрали строку "Close" из меню "File"
       case CM_FILECLOSE:
       {
         // Уничтожаем временное меню "Edit"
         DestroyMenu(hmenuEdit);

         // Удаляем соответствующую строку из меню
         // верхнего уровня
         RemoveMenu(hmenu, 1, MF_BYPOSITION);

         // Блокируем строки "Save", "Save as..."
         // и "Close" в меню "File"
         EnableMenuItem(hmenuFile, CM_FILESAVE,
           MF_GRAYED | MF_BYCOMMAND);
         EnableMenuItem(hmenuFile, CM_FILESAVEAS,
           MF_GRAYED | MF_BYCOMMAND);
         EnableMenuItem(hmenuFile, CM_FILECLOSE,
           MF_GRAYED | MF_BYCOMMAND);

         // Разблокируем строки "New" и "Open" в меню "File"
         EnableMenuItem(hmenuFile, CM_FILENEW,
           MF_ENABLED | MF_BYCOMMAND);
         EnableMenuItem(hmenuFile, CM_FILEOPEN,
           MF_ENABLED | MF_BYCOMMAND);

         // Перерисовываем меню
         DrawMenuBar(hwnd);
         return 0;
       }

       // Завершаем работу приложения
       case CM_FILEEXIT:
       {
         DestroyWindow(hwnd);
         return 0;
       }

       default:
         return 0;
     }
   }

   case WM_DESTROY:
   {
      // Если было создано меню "Edit",
      // уничтожаем его
      if(hmenuEdit != NULL)
      {
        DestroyMenu(hmenuEdit);
      }

      // Уничтожаем созданные ранее меню
      DestroyMenu(hmenuFile);
      DestroyMenu(hmenuHelp);
      DestroyMenu(hmenu);

      PostQuitMessage(0);
      return 0;
   }

   default:
      break;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

В приложении определены четыре глобальные переменные типа HMENU, предназначенные для хранения идентификаторов одного меню верхнего уровня (переменная hmenu) и трех временных меню (переменные hmenuFile, hmenuEdit, hmenuHelp).

Меню верхнего уровня создается в функции главного окна приложения во время обработки сообщения WM_CREATE. Созданное пустое меню подключается к главному окну приложения при помощи функции SetMenu:

hmenu = CreateMenu();
SetMenu(hwnd, hmenu);

Далее создаются два временных меню - "File" и "Help", для чего два раза вызывается функция CreatePopupMenu:

hmenuFile = CreatePopupMenu();
hmenuHelp = CreatePopupMenu();

На данный момент все меню пустые. Прежде всего обработчик сообщения WM_CREATE добавляет к меню "File" несколько строк, вызывая соответствующее число раз функцию AppendMenu:

AppendMenu(hmenuFile, MF_ENABLED | MF_STRING,
   CM_FILENEW,    "&New");
AppendMenu(hmenuFile, MF_ENABLED | MF_STRING,
   CM_FILEOPEN,   "&Open");
.......
и т. д.
.......

Обратите внимание на способ, которым в меню добавляется разделительная линия:

AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL);

Если в качестве второго параметра функции AppendMenu указано значение MF_SEPARATOR, третий и четвертый параметр этой функции игнорируются.

Для того чтобы отметить галочкой строку "Demo Version", вызывается функция CheckMenuItem:

CheckMenuItem(hmenuFile, CM_FILEDEMO,
   MF_BYCOMMAND | MF_CHECKED);

Аналогичным образом формируется меню "Help".

Далее сформированные временные меню "File" и "Help" добавляются к меню верхнего уровня при помощи функции AppendMenu:

AppendMenu(hmenu, MF_ENABLED | MF_POPUP,
   (UINT)hmenuFile, "&File");
AppendMenu(hmenu, MF_ENABLED | MF_POPUP,
   (UINT)hmenuHelp, "&Help");

В заключение вызывается функция DrawMenuBar, которая отображает внесенные изменения на экране:

DrawMenuBar(hwnd);

После формирования меню от него в функцию окна начинают поступать сообщения WM_COMMAND.

Так как меню, которые вы создаете, занимают системные ресурсы, их необходимо уничтожать, если они больше не нужны. При завершении работы приложения мы удалим все созданные меню. Однако меню "Edit" может так и не быть создано, так как вы можете сразу после запуска завершить работу приложения. Для того чтобы определить, нужно ли удалять меню "Edit", мы при создании главного окна приложения записываем в переменную hmenuEdit, предназначенную для хранения идентификатора меню, значение NULL:

hmenuEdit = NULL;

Если меню "Edit" будет создано, в переменную hmenuEdit будет записано значение соответствующего идентификатора. При завершении работы приложения мы проверим состояние этой переменной и, если ее содержимое отлично от значения NULL, уничтожим меню.

На многие из этих сообщений функция окна реагирует выводом сообщения о том, что данная функция не реализована. При выборе строки "About..." в меню "Help" на экран выводится диалоговая панель с сообщением о названии приложения и сведения о разработчике. Это стандартная реакция на выбор строки "About..." в меню "Help" любого приложения Windows.

Когда вы выбираете из меню "File" строки "New" или "Open", в функцию окна приложения приходит сообщение WM_COMMAND со значением парамера wParam, равным, соответственно, CM_FILENEW и CM_FILEOPEN. В ответ на эти сообщения создается новое временное меню "Edit", которое вставляется между временными меню "File" и временным меню "Help":

hmenuEdit = CreatePopupMenu();

AppendMenu(hmenuEdit, MF_GRAYED  | MF_STRING,
   CM_EDITUNDO,  "&Undo\tCtrl+Z");
AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING,
   CM_EDITCUT,   "&Cut\tCtrl+X");
AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING,
   CM_EDITCOPY,  "&Copy\tCtrl+C");
AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING,
   CM_EDITPASTE, "&Paste\tCtrl+V");

Для вставки меню вызывается функция InsertMenu:

InsertMenu(hmenu, 1,
    MF_BYPOSITION | MF_ENABLED | MF_POPUP,
   (UINT)hmenuEdit, "&Edit");

В качестве второго параметра этой функции передается значение 1. Так как в третьем параметре указан флаг MF_BYPOSITION, функция вставит меню перед временным меню с номером 1, т. е. перед меню "Help".

Затем в меню "File" разблокируются строки "Save", "Save as...", "Close" и блокируются строки "New" и "Open":

  EnableMenuItem(hmenuFile, CM_FILESAVE,
     MF_ENABLED | MF_BYCOMMAND);
  EnableMenuItem(hmenuFile, CM_FILESAVEAS,
     MF_ENABLED | MF_BYCOMMAND);
  EnableMenuItem(hmenuFile, CM_FILECLOSE,
     MF_ENABLED | MF_BYCOMMAND);

  EnableMenuItem(hmenuFile, CM_FILENEW,
     MF_GRAYED | MF_BYCOMMAND);
  EnableMenuItem(hmenuFile, CM_FILEOPEN,
     MF_GRAYED | MF_BYCOMMAND);

В заключение вызывается функция DrawMenuBar, отображающая внесенные в меню изменения.

Если вы выберите из меню "File" строку "Close", функция окна получит сообщение WM_COMMAND со значением параметра wParam, равным CM_FILECLOSE. Соответствующий обработчик уничтожает временное меню "Edit" (документ закрыт, редактировать нечего), и удаляет соответствующую строку из меню верхнего уровня:

DestroyMenu(hmenuEdit);
RemoveMenu(hmenu, 1, MF_BYPOSITION);

После этого в меню "File" блокируются строки "Save", "Save as...", "Close" и разблокируются строки "New" и "Open". Для этой цели вызывается функция EnableMenuItem. Для отображения внесенных изменений вызывается функция DrawMenuBar.

При завершении работы приложения мы проверяем содержимое переменной hmenuEdit. Если в момент завершения работы приложения меню "Edit" не существует, в этой переменной находится значение NULL. В этом случае мы не вызываем функцию DestroyWindow. Остальные меню уничтожаются всегда:

case WM_DESTROY:
{
   if(hmenuEdit != NULL)
   {
     DestroyMenu(hmenuEdit);
   }
   DestroyMenu(hmenuFile);
   DestroyMenu(hmenuHelp);
   DestroyMenu(hmenu);

   PostQuitMessage(0);
   return 0;
}

Несмотря на то, что при уничтожении окна все связанные с ним меню также уничтожаются, будет лучше, если ваше приложение удалит все созданные им объекты самостоятельно. Такое поведение отвечает правилам "хорошего тона" для приложений Windows, которые совместно используют многие системные ресурсы.

Идентификаторы строк меню описаны во включаемом файле dmenu.hpp (листинг 1.6).


Листинг 1.6. Файл dmenu/dmenu.hpp


#define CM_HELPABOUT           24346
#define CM_HELPUSING_HELP      24345
#define CM_HELPPROCEDURES      24344
#define CM_HELPCOMMANDS        24343
#define CM_HELPKEYBOARD        24342
#define CM_HELPINDEX           24341

#define CM_EDITPASTE           24324
#define CM_EDITCOPY            24323
#define CM_EDITCUT             24322
#define CM_EDITUNDO            24321

#define CM_FILEEXIT            24338
#define CM_FILEPRINTER_SETUP   24337
#define CM_FILEPAGE_SETUP      24336
#define CM_FILEPRINT           24335
#define CM_FILESAVEAS             24334
#define CM_FILESAVE            24333
#define CM_FILEOPEN            24332
#define CM_FILENEW             24331
#define CM_FILECLOSE           24330
#define CM_FILEDEMO            24329

Файл определения модуля приложения приведен в листинге 1.7.


Листинг 1.7. Файл dmenu/dmenu.rc


; =============================
; Файл определения модуля
; =============================
NAME        DMENU
DESCRIPTION 'Приложение DMENU, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   8120
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple

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