3. Создание меню

Практически любое приложение Presentation Manager имеет меню , расположенное под заголовком главного окна. С помощью меню пользователь может выполнять отдельные команды или операции, либо изменять режимы работы приложения.

Процецура создания меню в приложении Presentation Manager намного проще, чем аналогичная процедура в программе MS-DOS , так как для этого в программном интерфейсе Presentation Manager предусмотрены специальные средства.

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

Подключить меню к главному окну несложно - достаточно при создании окна функцией WinCreateStdWindow указать флаг FCF_MENU и организовать в функции окна обработку сообщения WM_COMMAND . Это сообщение посылается в функцию окна, создавшего меню, когда пользователь выбирает одну из строк меню. Через один из параметров сообщения в функцию окна передается код выбранной строки.

3.1. Классификация типов меню

В окне Frame Window имеется несколько органов управления, которые имеют отношение к меню.

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

Рис. 3.1. Системное меню приложения OS/2 System Editor в частично локализованной версии операционной системы IBM OS/2

Системное меню представляет собой дочернее окно главного окна приложения Frame Window и имеет идентификатор FID_SYSMENU .

В правом верхнем углу главного окна приложения имеются кнопки минимизации и максимизации окна, которые дублируют соответствующие строки системного меню. Эти кнопки являются отдельным органом управления, идентифкатор окна которого равен FID_MINMAX .

На рис. 3.2 показано главное окно приложения OS/2 System Editor, имеющее меню верхнего уровня . Это меню состоит из строк File, Edit, Options и Help.

Рис. 3.2. Меню верхнего уровня и временное меню в окне приложения OS/2 System Editor

Помимо меню верхнего уровня, на рис. 3.2 также показано в раскрытом состоянии временное меню File, содержащее строки New, Open, Save, Save as и Autosave.

Обратите внимание, что после названия некоторых строк стоит многоточие. Это означает, что при выборе таких строк вместо немедленного выполнения функции на экране появится диалоговая панель, при помощи которой надо указать дополнительные параметры или выполнить другие действия.

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

На рис. 3.3 показана еще одна разновидность меню - меню второго уровня , которое появляется при выборе соответствующей строки временного меню . Это меню состоит из строк On и Off.

Рис. 3.3. Меню второго уровня в приложении OS/2 System Editor

Заметим, что меню второго уровня может содержать ссылки на меню третьего уровня и т. д. Однако не следует увлекаться созданием многоуровневых меню, так как они неудобны в работе. Если вам нужно организовать настройку большого количества параметров, это лучше сделать при помощи другого органа управления, который называется блокнотом.

Последний тип меню, который мы рассмотрим в этой главе - это плавающие меню , которые в оригинальной документации называются меню Pop-Up . Обычно такое меню появляется, когда пользователь делает щелчок правой клавишей мыши по тому или иному объекту, расположенному в окне приложения. На рис. 3.4 показано плавающее меню, которое появляется после щелчка правой клавишей мыши по поверхности рабочего стола Workplace Shell. Это меню позволяет изменить свойства рабочего стола, завершить работу операционной системы и т. д.

Рис. 3.4. Контекстное меню рабочего стола Workplace Shell, которое отображается в виде плавающего меню

Плавающее меню, точно также как и временное, может содержать ссылки на меню второго уровня и графические изображения.

3.2. Подготовка шаблона меню

Проще всего создать меню на базе шаблона, записанного в файле описания ресурсов приложения. Такой шаблон имеет следующий вид:

MENU  MenuID
BEGIN 
  ...
END 

В качестве MenuID вы должны указать идентификатор меню. Если вы создаете меню верхнего уровня, идентификатором меню должен служить идентификатор ресурсов, указанный при создании главного окна функцией WinCreateStdWindow .

Между строками BEGIN и END (которые, кстати, можно заменить скобками { и }, соответственно) располагаются операторы описания временных меню SUBMENU и операторы описания отдельных строк меню MENUITEM .

Для примера приведем сокращенный образец шаблона меню, взятый из приложения MENUAPP, исходные тексты которого будут приведены в этой главе:

MENU ID_APP_FRAMEWND
BEGIN
  SUBMENU "~File",             IDM_FILE
    BEGIN
	MENUITEM  "~New...",     IDM_FILE_NEW
	MENUITEM  "~Open...",    IDM_FILE_OPEN
	MENUITEM  SEPARATOR
	MENUITEM  "~Save...",    IDM_FILE_SAVE
	MENUITEM  "Save ~as...", IDM_FILE_SAVEAS
	MENUITEM  SEPARATOR
	MENUITEM  "~Exit",       IDM_FILE_EXIT
    END

  SUBMENU "~Help", IDM_HELP
    BEGIN
	MENUITEM  "Help ~index...",     IDM_HELP_INDEX
	MENUITEM  "~General help...",   IDM_HELP_GENERAL
	MENUITEM  "~Using help...",     IDM_HELP_USING
	MENUITEM  "~Keys help...",      IDM_HELP_KEYS
	MENUITEM  SEPARATOR
	MENUITEM  "~Product information...",IDM_HELP_ABOUT
    END
END

В этом шаблоне описано меню с идентификатором ID_APP_FRAMEWND. Этот идентификатор был использован при создании главного окна приложения.

В меню с помощью операторов SUBMENU определено два временных меню: File и Help.

Каждое временное меню имеет свой идентификатор, указанный в операторе SUBMENU. Описание каждого временного меню ограничено операторами BEGIN и END.

Общий вид оператора SUBMENU представлен ниже:

SUBMENU text, id

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

Если в строке имеется символ ~, следующий за ним символ изображается подчеркнутым и используется для ускоренного выбора в комбинации с клавишей <Alt>.

В качестве идентификатора вы можете использовать целое число или символические константы, не совпадающие с идентификаторами других меню или строк меню.

Оператор описания строк меню имеет следующий вид:

MENUITEM  text, id, style, attribute

Поле text определяет текстовую строку, которая будет отображаться в строке меню. Для обеспечения ускоренного доступа к строке меню вы можете выделить один из символов строки подчеркиванием, указав перед ним символ ~.

Идентификатор id должен быть указан как целое число или символическая константа.

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

Константа Описание
MIS_SUBMENU Данная строка является меню следующего уровня
MIS_SEPARATOR Горизонтальная разделительная линия
MIS_BITMAP В строке меню отображается не текст, а графическое изображение
MIS_TEXT В строке меню отображается текстовая строка. Этот стиль используется по умолчанию
MIS_BUTTONSEPARATOR Разделитель меню
MIS_BREAK Начало нового столбца или строки меню
MIS_BREAKSEPARATOR Аналогично MIS_BREAK, однако дополнительно отображается разделитель. Этот стиль используется только для вложенных меню
MIS_SYSCOMMAND Если указан этот стиль, то при выборе пользователем строки из меню в родительское окно вместо сообщения с кодом WM_COMMAND передается сообщение с кодом WM_SYSCOMMAND
MIS_OWNERDRAW При использовании этого стиля родительское окно рисует строку меню самостоятельно. Для того чтобы функция родительского окна имела возможность определить видимые границы, занимаемые строкой, ей передается сообщение WM_MEASUREITEM . Рисование выполняется при получении родительским окном сообщения WM_DRAWITEM
MIS_HELP При выборе строки меню родительское окно вместо сообщения с кодом WM_COMMAND получает сообщение с кодом WM_HELP
MIS_STATIC Строка с таким стилем не может быть выбрана ни с помощью мыши, ни с помощью клавиатуры

В поле attribute оператора MENUITEM можно указывать одно из следующих значений, влияющих на внешний вид соовтетствующей строки меню:

Значение Описание
MIA_HILITED Этот атрибут устанавливается если пользователь выделил соответствующую строку меню
MIA_CHECKED Выделение строки символом "галочки"
MIA_DISABLED Строка заблокирована и не может быть выбрана
MIA_FRAMED Вокруг строки отображается рамка
MIA_NODISMISS Если указан этот атрибут, то сответстсвующее временное меню не исчезнет с экрана до тех пор, пока родительское окно не получит извещение о выборе строки, или пока пользователь не перейдет к работе с другим меню, или пока он не нажмет клавишу <Esc>

При необходимости приложение может изменять атрибуты строк меню, посылая ему сообщение MM_SETITEMATTR . Посылая меню сообщение MM_QUERYITEM ATTR можно узнать текущие атрибуты заданной строки.

3.3. Обработка сообщения WM_COMMAND

Сообщение с кодом WM_COMMAND передается в функцию главного окна приложения, когда пользователь выбирает одну из строк меню верхнего уровня.

Первый параметр сообщения содержит код выбранной строки, поэтому обработку сообщения WM_COMMAND можно выполнять, например, так (фрагмент кода взят из приложения MENUAPP):

case WM_COMMAND :
{
  switch(LOUSHORT(mp1))
  {
    case IDM_FILE_NEW:
    case IDM_FILE_OPEN:
    . . .
    case IDM_HELP_KEYS:
    {
       WinMessageBox (HWND_DESKTOP, hWnd,
         "Функция не реализована",
         szAppTitle, 0, MB_INFORMATION | MB_OK);
      break;
    }

    case IDM_HELP_ABOUT:
    {
       WinMessageBox (HWND_DESKTOP, hWnd,
         "Приложение MenuApp, (C) Frolov A., 1996",
         szAppTitle, 0, MB_INFORMATION | MB_OK);
      break;
    }

    case IDM_FILE_EXIT:
    {
       WinPostMsg (hWnd, WM_QUIT , 0L, 0L);
       break;
    }
  }
  return(WinDefWindowProc (hWnd, msg, mp1, mp2));
}

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

Заметим, что сообщение WM_COMMAND передается в родительское окно не только от меню, но также и от других органов управления, таких как кнопки, акселераторы и т. д. Однако в приложении MENUAPP источником сообщения WM_COMMAND служит только меню, поэтому вторым параметром этого сообщения мы пренебрегаем.

3.4. Плавающее меню

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

Обычно с помощью плавающих меню создают контекстные меню, состав строк котоых зависит от объекта, по которому был сделан щелчок правой клавишей мыши.

Проще всего создать плавающее меню на базе шаблона, определенного в файле описания ресурсов приложения. Для этого нужно загрузить меню из ресурсов приложения при помощи функции WinLoadMenu , а затем, когда это потребуется, отобразить меню функцией WinPopupMenu :

hwndPopupMenu = WinLoadMenu (hWnd, 
  NULLHANDLE, POPUP_MENU);
. . .
WinPopupMenu (hWnd, hWnd, hwndPopupMenu,
    SHORT1FROMMP (mp1), SHORT2FROMMP (mp1),
    IDM_FILE_NEW,
    PU_POSITIONONITEM | PU_HCONSTRAIN | PU_VCONSTRAIN |
    PU_MOUSEBUTTON1 | PU_MOUSEBUTTON2 | PU_KEYBOARD);

Прототип функции WinLoadMenu представлен ниже:

HWND WinLoadMenu (
  HWND hwndFrame, // родительское окно
  HMODULE hmod,   // идентификатор ресурса
  HWND idMenu);   // идентификатор окна меню

Через параметр hwndFrame функции WinLoadMenu следует передать идентификатор окна Frame Window , которое станет родительским для загружаемого меню. Можно также указывать идентификаторы объектного окна HWND_OBJECT и окна рабочего стола HWND_DESKTOP .

Параметр hmod должен содержать идентификатор модуля, из ресурсов которого загружается меню. В качестве такого модуля может выступать либо загрузочный файл приложения, либо библиотека динамической загрузки DLL. В первом случае значение параметра hmod может быть равно NULLHANDLE. Вторую возможность мы рассмотрим позже, когда будем рассказывать о библиотеках DLL.

Параметр idMenu предназначен для передачи идентификатора меню, с которым это меню определено в файле описания ресурсов.

При необходимости вы можете подготовить шаблон меню в оперативной памяти и затем загрузить его при помощи фукнции WinCreateMenu .

Функция WinPopupMenu , при помощи которой выполняется отображение плавающего меню, имеет такой прототип:

BOOL WinPopupMenu (
  HWND hwndParent, // родительское окно
  HWND hwndOwner,  // окно-владелец
  HWND hwndMenu,   // идентификатор плавающего меню
  LONG x,          // координата X для отображения меню
  LONG y,          // координата Y для отображения меню
  LONG idItem,     // идентификатор строки меню, 
                // которая будет выбрана по умолчанию
  ULONG fs);       // дополнительные параметры

Через параметры hwndParent и hwndOwner этой функции необходимо передать, соответственно, идентификатор родительского окна и идентификатор окна-владельца. Последнее будет получать все извещающие сообщения от меню. Вы можете через оба этих параметра передать идентификатор главного окна вашего приложения Frame Window .

Параметр hwndMenu предназначен для передачи идентификатора отображаемого плавающего меню, загруженного ранее при помощи функции WinLoadMenu .

Через параметры x и y необходимо передать координаты, которые будут использованы для определения места отображения меню. Если плавающее меню отображается после щелчка левой или правой клавишей мыши, эти координаты имеет смысл сделать равными координатам курсора мыши в момент щелчка.

Параметр idItem используется в зависимости от значения дополнителных параметров fs. Дополнительные параметры указываются как комбинация следующих значений, которые можно объединять при помощи логической операции ИЛИ:

Значение Описание
PU_POSITIONONITEM Меню размещается таким образом, что строка меню, идентификатор которой указан при помощи параметра idItem, имеет координаты, заданные параметрами x и y
PU_HCONSTRAIN Меню размещается на экране таким образом, что оно полностью отображается по своей ширине
PU_VCONSTRAIN Аналогично предыдущему, но по высоте
PU_MOUSEBUTTON1DOWN Меню отображается, когда нажата левая клавиша мыши, и исчезает, когда пользователь отжимает левую клавишу мыши
PU_MOUSEBUTTON2DOWN Аналогично предыдущему, но для правой клавиши двухкнопочной мыши или средней клавиши трехкнопочной мыши
PU_MOUSEBUTTON3DOWN Аналогично предыдущему, но используется для правой клавиши трехклавишной мыши
PU_NONE Меню отображается без предварительного выбора какой-либо строки
PU_SELECTITEM Если дополнительно указано значение PU_NONE, при отображении меню выделяется строка с идентификатором, переданным через параметр idItem. Если же этот идентификатор соответствует вложенному меню, последнее отображается в раскрытом состоянии
PU_KEYBOARD Для работы с меню можно использовать клавиатуру
PU_MOUSEBUTTON1 Для работы с меню можно использовать левую клавишу мыши
PU_MOUSEBUTTON2 Для работы с меню можно использовать правую клавишу двухкнопочной мыши или среднюю клавишу трехкнопочной мыши
PU_MOUSEBUTTON3 Для работы с меню можно использовать правую клавишу трехкнопочной мыши

Если плавающее меню отображено, функция WinPopupMenu возвращает значение TRUE. При ошибке возвращается значение FALSE.

3.5. Приложение MENUAPP

В приложении MENUAPP мы создадим стандартное меню верхнего уровня, показанное на рис. 3.5.

Рис. 3.5. Стандартное меню верхнего уровня в приложении MENUAPP

Временное меню Options, показанное на рис. 3.6, демонстрирует использование стилей меню MIS_BREAKSEPARATOR и MIS_STATIC , а также атрибута строки меню MIA_FRAMED .

Рис. 3.6. Временное меню Options

Если в окне Client Window сделать щелчок правой клавишей мыши, около курсора появится плавающее меню, которое в точности повторяет временное меню File (рис. 3.7).

Рис. 3.7. Плавающее меню, повторяющее временное меню File

Исходный текст приложения MENUAPP представлен в листинге 3.1.

Листинг 3.1. Файл menuapp\menuapp.c

// ===================================================
// Определения
// ===================================================

#define INCL_WIN
#define INCL_GPI
#define INCL_WINDIALOGS

#include <os2.h>
#include <stdio.h>
#include "menuapp.h"

// Прототип функции окна приложения
MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM);

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

// Идентификатор Anchor-block
HAB  hab;

// Идентификатор окна Frame Window 
HWND hWndFrame;

// Идентификатор окна Client Window 
HWND hWndClient;

// Заголовок приложения
CHAR szAppTitle[] = "Menu Demo Application";

// Идентификатор временного меню
HWND    hwndPopupMenu;

// ===================================================
// Главная функция приложения main 
// ===================================================

int main ()
{
  // Идентификатор очереди сообщений
  HMQ   hmq;

  // Структура, в которую записывается сообщение,
  // извлеченное из очереди
  QMSG   qmsg;

  // Переменная для хранения кода возврата
  BOOL  fRc;

  // Флаги для создания окна Frame Window 
  ULONG flFrameFlags =
    FCF_SYSMENU    | FCF_TITLEBAR       | FCF_MINMAX   |
    FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
    FCF_ICON | FCF_MENU;

  // Имя класса главного окна
  CHAR  szWndClass[] = "MENUDEMO";

  // Инициализация приложения, необходимая для 
  // использования функций Presentation Manager
  hab = WinInitialize (0);

  if(hab == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка инициализации",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    return(-1);
  }

  // Создаем очередь сообщений
  hmq = WinCreateMsgQueue (hab, 0);

  if(hmq == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании очереди сообщений",
      "Ошибка", 0, MB_ICONHAND | MB_OK);

    WinTerminate (hab);
    return(-1);
  }

  // Регистрация главного окна приложения
  fRc = WinRegisterClass (hab, szWndClass, 
    (PFNWP)WndProc, 0, 0);

  if(fRc == FALSE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при регистрации класса главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);

    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Создаем главное окно приложения
  hWndFrame = WinCreateStdWindow (HWND_DESKTOP, 
    WS_VISIBLE ,
    &flFrameFlags, szWndClass, szAppTitle,
    0, 0, ID_APP_FRAMEWND, &hWndClient);

  if(hWndFrame == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);

    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Запускаем цикл обработки сообщений
  while(WinGetMsg (hab, &qmsg, 0, 0, 0))
    WinDispatchMsg (hab, &qmsg);

  // Уничтожаем главное окно приложения
  WinDestroyWindow(hWndFrame);

  // Удаляем очередь сообщений и вызываем
  // функцию WinTerminate 
  WinDestroyMsgQueue (hmq);
  WinTerminate (hab);

  // Возвращаем управление операционной системе
  return(0);
}

// ===================================================
// Функция главного окна приложения
// ===================================================

MRESULT EXPENTRY
WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  // Временный буфер для подготовки сообщения
  CHAR szMsg[100];

  USHORT  usItemId; // идентификатор меню
  HWND    hwndMenu; // идентификатор окна меню

  switch (msg)
  {
    case WM_ERASEBACKGROUND :
      return(MRFROMLONG(1L));

    // Выполняем инициализацию главного меню приложения
    case WM_INITMENU :
    {
      // Идентификатор меню
      usItemId = SHORT1FROMMP (mp1);

      // Идентификатор окна меню
      hwndMenu = HWNDFROMMP(mp2);

      // В меню File блокируем строки New и Open
      if(usItemId == IDM_FILE)
      {
        WinEnableMenuItem(hwndMenu, IDM_FILE_NEW,
          FALSE);
        WinEnableMenuItem(hwndMenu, IDM_FILE_OPEN, 
          FALSE);
      }

      // В меню Edit блокируем строки Undo и Redo
      else if(usItemId == IDM_EDIT)
      {
        WinEnableMenuItem(hwndMenu, IDM_EDIT_UNDO, 
          FALSE);
        WinEnableMenuItem(hwndMenu, IDM_EDIT_REDO, 
          FALSE);
      }
      return(WinDefWindowProc (hWnd, msg, mp1, mp2));
    }

    // Сообщение WM_COMMAND  поступает в функцию окна,
    // кодгда пользователь выбирает одну из строк меню
    case WM_COMMAND :
    {
      // Анализируем код строки меню
      switch(LOUSHORT(mp1))
      {
        case IDM_FILE_NEW:
        case IDM_FILE_OPEN:
        case IDM_FILE_SAVE:
        case IDM_FILE_SAVEAS:

        case IDM_EDIT_UNDO:
        case IDM_EDIT_REDO:
        case IDM_EDIT_CUT:
        case IDM_EDIT_COPY:
        case IDM_EDIT_PASTE:
        case IDM_EDIT_CLEAR:
        case IDM_EDIT_DUPLICATE:
        case IDM_EDIT_SELECTALL:

        case IDM_OPTIONS_FONT_NORMAL:
        case IDM_OPTIONS_FONT_BOLD:
        case IDM_OPTIONS_FONT_ITALIC:
        case IDM_OPTIONS_FONT_UNDERLINE:
        case IDM_OPTIONS_PARA_LEFT:
        case IDM_OPTIONS_PARA_CENTER:
        case IDM_OPTIONS_PARA_RIGHT:
        case IDM_OPTIONS_PARA_JUSTIFY:

        case IDM_HELP_INDEX:
        case IDM_HELP_GENERAL:
        case IDM_HELP_USING:
        case IDM_HELP_KEYS:
        {
           WinMessageBox (HWND_DESKTOP, hWnd,
             "Функция не реализована",
             szAppTitle, 0, MB_INFORMATION | MB_OK);
          break;
        }

        case IDM_HELP_ABOUT:
        {
           WinMessageBox (HWND_DESKTOP, hWnd,
             "Приложение MenuApp, (C) Frolov A., 1996",
             szAppTitle, 0, MB_INFORMATION | MB_OK);
          break;
        }

        // Если из меню File выбрана строка Exit,
        // завершаем работу приложения
        case IDM_FILE_EXIT:
        {
           WinPostMsg (hWnd, WM_QUIT , 0L, 0L);
           break;
        }
      }
      return(WinDefWindowProc (hWnd, msg, mp1, mp2));

    }

    // В процессе инициализации главного окна 
    // приложения загружаем временное меню и 
    // определяем идентфикатор его окна
    case WM_CREATE :
    {
      hwndPopupMenu = WinLoadMenu (hWnd,
        NULLHANDLE, POPUP_MENU);
      return FALSE;
    }

    // Перед уничтожением главного окна приложения
    // уничтожаем окно временного меню
    case WM_DESTROY :
    {
      WinDestroyWindow(hwndPopupMenu);
      break;
    }

    // Если пользователь сделал в окне приложения 
    // щелчок правой клавишей мыши, отображаем 
    // временное меню
    case WM_BUTTON2DOWN :
    {
      WinPopupMenu (hWnd, hWnd, hwndPopupMenu,
        SHORT1FROMMP (mp1), SHORT2FROMMP (mp1),
        IDM_FILE_NEW,
        PU_POSITIONONITEM | PU_HCONSTRAIN | 
        PU_VCONSTRAIN |
        PU_MOUSEBUTTON1 | PU_MOUSEBUTTON2 | 
        PU_KEYBOARD);

      return 0;
    }

    default:
      return(WinDefWindowProc (hWnd, msg, mp1, mp2));
  }
  return(WinDefWindowProc (hWnd, msg, mp1, mp2));
}

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

В области глобальных переменных определены идентификаторы окон FrameWindow и ClientWindow (соответственно, переменные hWndFrame и hWndClient), заголовок приложения szAppTitle.

Кроме этого, определен идентификатор hwndPopupMenu. В него будет записан идентификатор плавающего меню, которое появляется после щелчка правой клавишей мыши в окне приложения.

Функция main

В функции main приложения MENUAPP нет ничего необычного. Так как мы будем использовать меню верхнего уровня, определенное в ресурсах приложения, среди флагов окна Frame Window указан флаг FCF_MENU :

ULONG flFrameFlags =
    FCF_SYSMENU    | FCF_TITLEBAR       | FCF_MINMAX   |
    FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
    FCF_ICON | FCF_MENU;

Так как при создании главного окна приложения мы указали функции WinCreateStdWindow идентификатор ресурсов ID_APP_FRAMEWND, меню верхнего уровня также должно иметь этот идентификатор.

Функция окна WndProc

Функция главного окна приложения WndProc обрабатывает два сообщения, поступающие от меню. Это сообщения WM_INITMENU и WM_COMMAND .

Сообщение WM_INITMENU

Сообщение WM_INITMENU поступает в момент инициализации каждого временного меню. Наше приложение получает вместе с этим сообщением идентификатор временного меню и идентификатор окна меню, сохраняя их, соответственно, в переменных usItemId и hwndMenu.

Для временного меню File обработчик сообщения WM_INITMENU выполняет блокировку строк New и Open, вызывая функцию WinEnableMenuItem. Во временном меню Edit аналогичным образом блокируются строки Undo и Redo.

Сообщение WM_COMMAND

Задача обработчика сообщения WM_COMMAND заключается в анализе кода выбранной строки меню и выполнении соответствующей операции. Так как задача нашего приложения ограничена демонстрацией способов создания меню, при выборе строк выполняется простое отображение диалоговой панели с сообщением о том, что данная функция не реализована.

Тем не менее, при выборе строки Product Information из меню Help на экране повляется информация о разработчике приложения. Если же из меню File выбрать строку Exit, с помощью функции WinPostMsg в очередь приложения будет записано сообщение WM_QUIT . Это приведет к тому, что при его выборке из очереди цикл обработки будет завершен. В результате приложение также завершит свою работу.

Сообщение WM_CREATE

В процессе создания главного окна приложения его функция получает сообщение WM_CREATE . Обработчик этого сообщения загружает из ресурсов приложения временное меню, описанное с идентификатором POPUP_MENU. Для этого он использует функцию WinLoadMenu :

case WM_CREATE :
{
  hwndPopupMenu = WinLoadMenu (hWnd,
     NULLHANDLE, POPUP_MENU);
  return FALSE;
}

Меню с идентификатором POPUP_MENU будет использовано в качестве плавающего. Оно будет появляться по щелчку правой клавишей мыши в окне приложения.

Сообщение WM_DESTROY

Перед уничтожением окна его функция получает сообщение WM_DESTROY . В ответ на это сообщение наше приложение удаляет загруженное ранее окно плавающего меню, вызывая для этого функцию WinDestroyWindow:

case WM_DESTROY :
{
  WinDestroyWindow(hwndPopupMenu);
  break;
}

Сообщение WM_BUTTON2DOWN

Отображение загруженного ранее плавающего меню выполняется с помощью функции WinPopupMenu :

case WM_BUTTON2DOWN :
{
  WinPopupMenu (hWnd, hWnd, hwndPopupMenu,
    SHORT1FROMMP (mp1), SHORT2FROMMP (mp1),
    IDM_FILE_NEW,
    PU_POSITIONONITEM | PU_HCONSTRAIN | PU_VCONSTRAIN |
    PU_MOUSEBUTTON1 | PU_MOUSEBUTTON2 | PU_KEYBOARD);
  return 0;
}

В качестве идентификаторов родительского окна и окна-владельца мы указали значение hWnd - идентификатор, полученный функцией окна.

Функция WinPopupMenu отображает меню с идентификатором hwndPopupMenu, полученным при загрузке меню в обработчике сообщения WM_CREATE .

Для отображения меню используются координаты курсора мыши, которые передаются в параметре mp1 сообщения WM_BUTTON2DOWN . Эти координаты извлекаются из параметра mp1 с помощью макрокоманд SHORT1FROMMP (координата X) и SHORT2FROMMP (координата Y).

Так как в дополнительных параметрах указано значение PU_POSITIONONITEM, при отображении будет выделена строка с идентификатором IDM_FILE_NEW (строка New плавающего меню).

Для того чтобы вне зависимости от расположения курсора мыши во время щелчка в окне приложения плавающее меню полностью поместилось на экране, мы указали значения PU_HCONSTRAIN и PU_VCONSTRAIN .

Пользователь сможет выбирать строки отображаемого меню при помощи левой или правой клавиши мыши, а также при помощи клавиатуры, так как мы указали значения PU_MOUSEBUTTON1 , PU_MOUSEBUTTON2 и PU_KEYBOARD .

Файл menuapp.h

В файле menuapp.h (листинг 3.2) сделаны определения всех необходимых констант, таких как идентификатор ресурсов главного окна приложения ID_APP_FRAMEWND, а также идентификаторы временных меню, идентификаторы строк меню и идентификатор плавающего меню.

Листинг 3.2. Файл menuapp\menuapp.h

#define ID_APP_FRAMEWND 1

#define IDM_FILE        100
#define IDM_FILE_NEW    101
#define IDM_FILE_OPEN   102
#define IDM_FILE_SAVE   103
#define IDM_FILE_SAVEAS 104
#define IDM_FILE_EXIT   105

#define IDM_EDIT        200
#define IDM_EDIT_UNDO   201
#define IDM_EDIT_REDO   202
#define IDM_EDIT_CUT    203
#define IDM_EDIT_COPY   204
#define IDM_EDIT_PASTE  205
#define IDM_EDIT_CLEAR  206
#define IDM_EDIT_DUPLICATE 207
#define IDM_EDIT_SELECTALL 208

#define IDM_HELP           300
#define IDM_HELP_INDEX     301
#define IDM_HELP_GENERAL   302
#define IDM_HELP_USING     303
#define IDM_HELP_KEYS      304
#define IDM_HELP_ABOUT     305

#define POPUP_MENU         400

#define IDM_OPTIONS                 500
#define IDM_OPTIONS_FONT_NORMAL     501
#define IDM_OPTIONS_FONT_BOLD       502
#define IDM_OPTIONS_FONT_ITALIC     503
#define IDM_OPTIONS_FONT_UNDERLINE  504

#define IDM_OPTIONS_PARA_LEFT       505
#define IDM_OPTIONS_PARA_CENTER     506
#define IDM_OPTIONS_PARA_RIGHT      507
#define IDM_OPTIONS_PARA_JUSTIFY    508

Файл ресурсов приложения MENUAPP

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

Листинг 3.3. Файл menuapp\menuapp.rc

#include <os2.h>
#include "menuapp.h"

ICON ID_APP_FRAMEWND MENUAPP.ICO

MENU ID_APP_FRAMEWND
BEGIN
  SUBMENU "~File",          IDM_FILE
    BEGIN
	MENUITEM  "~New...",     IDM_FILE_NEW
	MENUITEM  "~Open...",    IDM_FILE_OPEN
	MENUITEM  SEPARATOR
	MENUITEM  "~Save...",    IDM_FILE_SAVE
	MENUITEM  "Save ~as...", IDM_FILE_SAVEAS
	MENUITEM  SEPARATOR
	MENUITEM  "~Exit",       IDM_FILE_EXIT
    END

  SUBMENU "~Edit",          IDM_EDIT
    BEGIN
	MENUITEM  "~Undo",       IDM_EDIT_UNDO
	MENUITEM  "~Redo",       IDM_EDIT_REDO
	MENUITEM  SEPARATOR
	MENUITEM  "Cu~t",        IDM_EDIT_CUT
	MENUITEM  "~Copy",       IDM_EDIT_COPY
	MENUITEM  "~Paste",      IDM_EDIT_PASTE
	MENUITEM  "Cl~ear",      IDM_EDIT_CLEAR
	MENUITEM  "~Duplicate",  IDM_EDIT_DUPLICATE
	MENUITEM  SEPARATOR
	MENUITEM  "~Select all", IDM_EDIT_SELECTALL
    END

  SUBMENU "~Options", IDM_OPTIONS
    BEGIN
	MENUITEM  "Font",       -1, MIS_STATIC, MIA_FRAMED
	MENUITEM  "~Normal",      IDM_OPTIONS_FONT_NORMAL
	MENUITEM  "~Bold",        IDM_OPTIONS_FONT_BOLD
	MENUITEM  "~Italic",      IDM_OPTIONS_FONT_ITALIC
	MENUITEM  "~Underline",IDM_OPTIONS_FONT_UNDERLINE
	MENUITEM  "Alighnment", -1,
          MIS_BREAKSEPARATOR | MIS_STATIC, MIA_FRAMED
	MENUITEM  "~Left",        IDM_OPTIONS_PARA_LEFT
	MENUITEM  "~Center",      IDM_OPTIONS_PARA_CENTER
	MENUITEM  "~Right",       IDM_OPTIONS_PARA_RIGHT
	MENUITEM  "~Justify",     IDM_OPTIONS_PARA_JUSTIFY
    END

  SUBMENU "~Help", IDM_HELP
    BEGIN
	MENUITEM  "Help ~index...",      IDM_HELP_INDEX
	MENUITEM  "~General help...",    IDM_HELP_GENERAL
	MENUITEM  "~Using help...",      IDM_HELP_USING
	MENUITEM  "~Keys help...",       IDM_HELP_KEYS
	MENUITEM  SEPARATOR
	MENUITEM  "~Product information...",
         IDM_HELP_ABOUT
    END
END

MENU POPUP_MENU
BEGIN
  MENUITEM  "~New...",     IDM_FILE_NEW
  MENUITEM  "~Open...",    IDM_FILE_OPEN
  MENUITEM  SEPARATOR
  MENUITEM  "~Save...",    IDM_FILE_SAVE
  MENUITEM  "Save ~as...", IDM_FILE_SAVEAS
  MENUITEM  SEPARATOR
  MENUITEM  "~Exit",       IDM_FILE_EXIT
END

В меню верхнего уровня с идентификатором ID_APP_FRAMEWND определены четыре временных меню с идентификаторами IDM_FILE, IDM_EDIT, IDM_OPTIONS и IDM_HELP.

Временное меню IDM_OPTIONS отображается в виде таблицы, состоящей из двух столбцов, озаглавленных, соответственно, Font и Alighnment.

Строка Font имеет атрибуты MIS_STATIC и MIA_FRAMED , поэтому ее нельзя выбрать и вокруг этой строки нарисована рамка:

MENUITEM  "Font", -1, MIS_STATIC, MIA_FRAMED

Строка Alighnment дополнительно имеет атрибут MIS_BREAKSEPARATOR , поэтому она отображается в отдельном столбце:

MENUITEM  "Alighnment", -1,
          MIS_BREAKSEPARATOR | MIS_STATIC, MIA_FRAMED

В файле описания ресурсов приложения также определено плавающее меню с идентификатором POPUP_MENU, которое полностью повторяет временное меню File.

Файл определения модуля

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

Листинг 3.4. Файл menuapp\menuapp.def

NAME        MENUAPP   WINDOWAPI
DESCRIPTION 'MenuApp Application (C) Frolov A., 1996'
HEAPSIZE    4096
STACKSIZE   32768
EXPORTS     WndProc

3.6. Изменение системного меню

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

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

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

HWND hwndSystemMenu;
hwndSystemMenu = WinWindowFromID (
  WinQueryWindow (hWnd, QW_PARENT ), FID_SYSMENU );

В качестве первого параметра функции WinQueryWindow мы передали идентификатор окна Client Window , который передается в функцию окна. Второй параметр, равный константе QW_PARENT , сообщает функции WinQueryWindow о необходимости вернуть идентификатор окна, которое является родительским по отношению к окну hWnd. При этом мы получим идентификатор окна Frame Window , которое, очевидно, является родительским для окна системного меню.

Приведем прототип для функции WinQueryWindow :

HWND WinQueryWindow (
  HWND hwnd,   // идентификатор окна
  LONG lCode); // тип информации об окне

В зависимости от значения параметра lCode функция возвращает различную информацию об окне. Вы можете использовать для параметра lCode одну из следующих констант:

Константа Информация, возвращаемая функцией
QW_BOTTOM Дочернее окно самого нижнего уровня
QW_FRAMEOWNER Возвращается идентификатор окна-владельца для окна hwnd. Это окно имеет такое же родительское окно, что и окно hwnd
QW_NEXT Идентификатор окна, расположенного под окном, заданным параметром hwnd
QW_NEXTTOP Идентификатор следующего окна в иерархии окна-владельца
QW_OWNER Идентификатор окна-владельца
QW_PARENT Идентификатор родительского окна
QW_PREV Аналогично, но для окна, расположенного над заданным
QW_PREVTOP Идентификатор предыдущего окна в иерархии окна-владельца
QW_TOP Дочернее окно самого верхнего уровня

Функция WinWindowFromID возвращает идентификатор дочернего окна, заданного своим идентификатором ресурса. Она имеет такой прототип:

HWND WinWindowFromID (
  HWND hwndParent, // идентификатор родительского окна
  ULONG id); // идентификатор ресурса дочернего окна

Так как системное меню имеет идентификатор ресурса, равный FID_SYSMENU , зная его, мы легко сможем определить идентификатор окна системного меню.

На следующем этапе нам необходимо определить идентификатор ресурса для системного меню. Для этого окну системного меню необходимо послать сообщение MM_ITEMIDFROMPOSITION , как это показано ниже:

SHORT sSysMeniID;
sSysMeniID = (SHORT)WinSendMsg (hwndSystemMenu,
   MM_ITEMIDFROMPOSITION , MPFROMSHORT(0), NULL);

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

Для того чтобы вставить строку в это временное меню, нам нужно знать его идентификатор. Этот идентификатор можно узнать, если послать окну системного меню сообщение MM_QUERYITEM , передав вместе с ним через первый параметр идентификатор ресурса системного меню, а через второй параметр - адрес структуры типа MENUITEM :

MENUITEM  mi;
HWND hwndSystemSubMenu;
WinSendMsg (hwndSystemMenu, MM_QUERYITEM ,
  MPFROMSHORT(sSysMeniID), MPFROMP(&mi));
hwndSystemSubMenu = mi.hwndSubMenu;

В поле hwndSubMenu этой структуры будет записан искомый идентификатор временного меню.

Структура MENUITEM и указатель на нее определены следующим образом:

typedef struct _MENUITEM  
{
  SHORT  iPosition;     // позиция элемента
  USHORT afStyle;       // стиль
  USHORT afAttribute;   // атрибуты
  USHORT id;            // идентификатор ресурса
  HWND   hwndSubMenu;   // вложенное меню
  ULONG  hItem; // идентификатор объекта отображения
  } MENUITEM ;
  typedef MENUITEM  *PMENUITEM;

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

Поля afStyle и afAttribute определяют, соответственно, стиль и атрибуты элемента меню. Здесь вы можете использовать константы с префиксом имени MIS_ и MIA_, описанные в начале этой главы в разделе "Подготовка шаблона меню".

В поле id при добавлении нового элемента необходимо записать его идентификатор. Этот идентификатор будет потом проверяться обработчиком сообщения WM_COMMAND . Если добавляется разделительная линия, в качестве идентификатора можно использовать значение -1.

Если данный элемент является вложенным меню, то в поле hwndSubMenu должен храниться идентификатор окна вложенного меню. Для обычной строки меню здесь располагается значение NULL.

Поле hItem содержит идентификатор объекта отображения, однако если элемент описывает строку меню, имеющую стиль MIS_TEXT , в поле находится нулевое значение. Это поле нужно в том случае, если в стоках меню отображаются графические пиктограммы.

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

mi.afStyle = MIS_SEPARATOR;
mi.afAttribute = 0;
mi.hwndSubMenu = 0;
mi.hItem = 0;
mi.id = -1;
mi.iPosition = MIT_END;
WinSendMsg (hwndSystemSubMenu, MM_INSERTITEM ,
  MPFROMP(&mi), NULL);

Так как мы добавляем разделительную линию, в поле стиля afStyle записываем константу MIS_SEPARATOR . Поля afAttribute, hwndSubMenu и hItem будут иметь нулевые значения.

Разделительная линия не посылает сообщение WM_COMMAND , поэтому ее идентификатор устанавливаем равным -1. Мы решили добавить разделительную линию в конец меню, поэтому устанавливаем позицию, равную MIT_END.

Для добавления строки посылаем окну временного системного меню сообщение MM_INSERTITEM . В качестве первого параметра вместе с этим сообщением необходимо передать адрес подготовленной струткуры MENUITEM , а в качестве второго - текстовую строку, которая будет отображаться в меню. При добавлении разделительной линии текстовая строка не нужна, поэтому второй параметр имеет значение NULL.

Для вставки обычной строки после разделительной линии мы снова готовим структуру MENUITEM и посылаем окну временного системного меню сообщение MM_INSERTITEM :

mi.afStyle = MIS_TEXT;
mi.afAttribute = 0;
mi.hwndSubMenu = 0;
mi.hItem = 0;
mi.iPosition = MIT_END;
mi.id = IDM_HELP_ABOUT;
WinSendMsg (hwndSystemSubMenu, MM_INSERTITEM ,
  MPFROMP(&mi), "Product Information...");

На этот раз в поле стиля находится константа MIS_TEXT, а в поле id - константа IDM_HELP_ABOUT. Во втором параметре сообщения MM_INSERTITEM мы указываем адрес текстовой строки Product Information..., которая будет отображаться в нижней части измененного нами системного меню.

3.7. Динамическое создание меню

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

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

Для начала нам нужно определить идентификатор окна меню верхнего уровня. Это можно сделать таким же образом, каким мы определяли этот идентификатор для системного меню:

HWND hwndMenu;
hwndMenu = WinWindowFromID (
  WinQueryWindow (hWnd, QW_PARENT ), FID_MENU );

Напомним, что окно меню верхнего уровня имеет идентификатор ресурсов FID_MENU .

Следующим нашим действием будет создание пустого меню Edit с помощью функции WinCreateMenu :

HWND hwndEditMenu;
hwndEditMenu = WinCreateMenu (HWND_OBJECT , NULL);

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

Так как созданное меню будет иметь идентификатор ресурсов FID_MENU , заменяем этот идентификатор на IDM_EDIT с помощью функции WinSetWindowUShort:

WinSetWindowUShort(hwndEditMenu, QWS_ID, IDM_EDIT);

Теперь можно приступать к добавлению строк в созданное нами пустое меню. Это делается таким же образом, что и добавление строк в системное меню:

mi.iPosition = MIT_END;
mi.afStyle = MIS_TEXT;
mi.afAttribute = 0;
mi.hwndSubMenu = 0;
mi.hItem = 0;
mi.id = IDM_EDIT_UNDO;

WinSendMsg (hwndEditMenu, MM_INSERTITEM ,
  MPFROMP(&mi), "~Undo");

mi.id = IDM_EDIT_REDO;
WinSendMsg (hwndEditMenu, MM_INSERTITEM ,
  MPFROMP(&mi), "~Redo");

После того как все строки добавлены, нужно вставить меню Edit в меню верхнего уровня. Эта операция выполняется аналогично операции вставки строк, с тем лишь исключением, что в поле hwndSubMenu структуры MENUITEM мы должны указать идентификатор окна временного меню Edit:

mi.iPosition = 1;
mi.afStyle = MIS_TEXT | MIS_SUBMENU;
mi.afAttribute = 0;
mi.id = IDM_EDIT;
mi.hwndSubMenu = hwndEditMenu;

WinSendMsg (hwndMenu, MM_INSERTITEM ,
  MPFROMP(&mi), "~Edit");

Так как мы добавляем меню, то в поле стилей дополнительно указывается константа MIS_SUBMENU.

В поле iPosition мы указали значение, равное единице, поэтому меню Edit будет вставлено во вторую позицию слева. Это стандартное расположение для меню Edit, так как перавая позиция (имеющая нулевой порядковый номер), предназначена для меню File.

Для удаления строки из меню или временного меню вы можете воспользоваться сообщением MM_DELETEITEM .

Через младшее слово первого параметра с этим сообщением передается идентификатор удаляемой строки или меню. Если старшее слово первого параметра содержит значение TRUE, при удалении меню будут удалены все связанные с ним вложенные меню, если FALSE - вложенные меню останутся нетронутыми.

Во второй параметр для сообщения MM_DELETEITEM вам нужно записать адрес заполненной структуры MENUITEM .

3.8. Приложение MENUMOD

Приложение MENUMOD демонстрирует описанную выше методику изменения системного меню и динамического добавления временного меню в меню верхнего уровня.

На рис. 3.8 показано системное меню приложения, в которое были добавлены разделительная линия и строка Product Information.

Рис. 3.8. Измененное системное меню в приложении MENUMOD (приложение запущено в среде частично локализованной операционной системы, поэтому вы видите русские строки системного меню)

Кроме того, в приложении MENUMOD мы показали, как создаются вложенные меню (рис. 3.9).

Рис. 3.9. Использование вложенных меню в приложении MENUMOD

Исходные тексты приложения приведены в листинге 3.5.

Листинг 3.5. Файл menumod\menumod.c

// ===================================================
// Определения
// ===================================================

#define INCL_WIN
#define INCL_GPI
#define INCL_WINDIALOGS

#include <os2.h>
#include <stdio.h>
#include "menumod.h"

// Прототип функции окна приложения
MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM);

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

// Идентификатор Anchor-block
HAB  hab;

// Идентификатор окна Frame Window 
HWND hWndFrame;

// Идентификатор окна Client Window 
HWND hWndClient;

// Заголовок приложения
CHAR szAppTitle[] = "Menu Modification Demo";

// Идентификатор меню Edit
HWND hwndEditMenu;

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

// ===================================================
// Главная функция приложения main 
// ===================================================

int main ()
{
  // Идентификатор очереди сообщений
  HMQ   hmq;

  // Структура, в которую записывается сообщение,
  // извлеченное из очереди
  QMSG   qmsg;

  // Переменная для хранения кода возврата
  BOOL  fRc;

  // Флаги для создания окна Frame Window 
  ULONG flFrameFlags =
    FCF_SYSMENU    | FCF_TITLEBAR       | FCF_MINMAX   |
    FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
    FCF_ICON | FCF_MENU;

  // Имя класса главного окна
  CHAR  szWndClass[] = "MENUMODDEMO";

  // Инициализация приложения, необходимая для 
  // использования функций Presentation Manager
  hab = WinInitialize (0);

  if(hab == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка инициализации",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    return(-1);
  }

  // Создаем очередь сообщений
  hmq = WinCreateMsgQueue (hab, 0);

  if(hmq == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании очереди сообщений",
      "Ошибка", 0, MB_ICONHAND | MB_OK);

    WinTerminate (hab);
    return(-1);
  }

  // Регистрация главного окна приложения
  fRc = WinRegisterClass (hab, szWndClass,
    (PFNWP)WndProc, 0, 0);

  if(fRc == FALSE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при регистрации класса главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);

    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Создаем главное окно приложения
  hWndFrame = WinCreateStdWindow  (HWND_DESKTOP, 
    WS_VISIBLE ,
    &flFrameFlags, szWndClass, szAppTitle,
    0, 0, ID_APP_FRAMEWND, &hWndClient);

  if(hWndFrame == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);

    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Запускаем цикл обработки сообщений
  while(WinGetMsg (hab, &qmsg, 0, 0, 0))
    WinDispatchMsg (hab, &qmsg);

  // Уничтожаем главное окно приложения
  WinDestroyWindow(hWndFrame);

  // Удаляем очередь сообщений и вызываем
  // функцию WinTerminate 
  WinDestroyMsgQueue (hmq);
  WinTerminate (hab);

  // Возвращаем управление операционной системе
  return(0);
}

// ===================================================
// Функция главного окна приложения
// ===================================================

MRESULT EXPENTRY
WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  // Временный буфер для подготовки сообщения
  CHAR szMsg[100];

  MENUITEM  mi;

  switch (msg)
  {
    case WM_CREATE :
    {
      HWND hwndSystemMenu;
      HWND hwndSystemSubMenu;
      SHORT sSysMeniID;

      // ---------------------------------------------
      // Добавляем строку к системному меню
      // ---------------------------------------------

      // Определяем идентификатор окна системного меню
      hwndSystemMenu = WinWindowFromID (
        WinQueryWindow (hWnd, QW_PARENT ), FID_SYSMENU );

      // Определяем идентификатор системного меню
      sSysMeniID = (SHORT)WinSendMsg (hwndSystemMenu,
        MM_ITEMIDFROMPOSITION , MPFROMSHORT(0), NULL);

      // Определяем характеристики системного меню
      WinSendMsg (hwndSystemMenu, MM_QUERYITEM ,
        MPFROMSHORT(sSysMeniID), MPFROMP(&mi));

      // Идентификатор окна временного системного меню
      hwndSystemSubMenu = mi.hwndSubMenu;

      // Заполняем структуру для разделительной линии
      mi.afStyle = MIS_SEPARATOR;
      mi.afAttribute = 0;
      mi.hwndSubMenu = 0;
      mi.hItem = 0;
      mi.id = -1;
      mi.iPosition = MIT_END;

      // Добавляем разделительную линию
      WinSendMsg (hwndSystemSubMenu, MM_INSERTITEM ,
        MPFROMP(&mi), NULL);

      // Заполняем структуру для добавляемой строки
      mi.afStyle = MIS_TEXT;
      mi.id = IDM_HELP_ABOUT;

      // Добавляем строку Product Information
      WinSendMsg (hwndSystemSubMenu, MM_INSERTITEM ,
        MPFROMP(&mi), "Product Information...");

      // ---------------------------------------------
      // Добавляем временное меню Edit
      // ---------------------------------------------

      // Получаем идентификатор окна меню
      // верхнего уровня
      hwndMenu = WinWindowFromID (
        WinQueryWindow (hWnd, QW_PARENT ), FID_MENU );

      // Создаем пустое меню Edit
      hwndEditMenu = WinCreateMenu (HWND_OBJECT , NULL);

      // Присваиваем меню Edit идентификатор IDM_EDIT
      WinSetWindowUShort(hwndEditMenu, QWS_ID,
        IDM_EDIT);

      // Добавляем строки в меню
      mi.iPosition = MIT_END;
      mi.afStyle = MIS_TEXT;
      mi.afAttribute = 0;
      mi.hwndSubMenu = 0;
      mi.hItem = 0;
      mi.id = IDM_EDIT_UNDO;

      WinSendMsg (hwndEditMenu, MM_INSERTITEM ,
        MPFROMP(&mi), "~Undo");

      mi.id = IDM_EDIT_REDO;
      WinSendMsg (hwndEditMenu, MM_INSERTITEM ,
        MPFROMP(&mi), "~Redo");

      mi.id = IDM_EDIT_CUT;
      WinSendMsg (hwndEditMenu, MM_INSERTITEM ,
        MPFROMP(&mi), "Cu~t");

      mi.id = IDM_EDIT_COPY;
      WinSendMsg (hwndEditMenu, MM_INSERTITEM ,
        MPFROMP(&mi), "~Copy");

      mi.id = IDM_EDIT_PASTE;
      WinSendMsg (hwndEditMenu, MM_INSERTITEM ,
        MPFROMP(&mi), "~Paste");

      mi.id = IDM_EDIT_CLEAR;
      WinSendMsg (hwndEditMenu, MM_INSERTITEM ,
        MPFROMP(&mi), "C~lear");

      // Добавляем разделительную линию
      mi.afStyle = MIS_SEPARATOR;
      mi.id = -1;
      WinSendMsg (hwndEditMenu, MM_INSERTITEM ,
        MPFROMP(&mi), "~Duplicate");

      // Добавляем еще две строки
      mi.afStyle = MIS_TEXT;
      mi.id = IDM_EDIT_DUPLICATE;
      WinSendMsg (hwndEditMenu, MM_INSERTITEM ,
        MPFROMP(&mi), "~Duplicate");

      mi.id = IDM_EDIT_SELECTALL;
      WinSendMsg (hwndEditMenu, MM_INSERTITEM ,
        MPFROMP(&mi), "~Select all");

      // Добавляем меню Edit в меню верхнего уровня
      mi.iPosition = 1;
      mi.afStyle = MIS_TEXT | MIS_SUBMENU;
      mi.afAttribute = 0;
      mi.id = IDM_EDIT;
      mi.hwndSubMenu = hwndEditMenu;

      WinSendMsg (hwndMenu, MM_INSERTITEM ,
        MPFROMP(&mi), "~Edit");

      return FALSE;
    }

    case WM_ERASEBACKGROUND :
      return(MRFROMLONG(1L));

    // Сообщение WM_COMMAND  поступает в функцию окна,
    // кодгда пользователь выбирает одну из строк меню
    case WM_COMMAND :
    {
      // Анализируем код строки меню
      switch(LOUSHORT(mp1))
      {
        case IDM_FILE_NEW:
        case IDM_FILE_OPEN:
        case IDM_FILE_SAVE:
        case IDM_FILE_SAVEAS:

        case IDM_EDIT_UNDO:
        case IDM_EDIT_REDO:
        case IDM_EDIT_CUT:
        case IDM_EDIT_COPY:
        case IDM_EDIT_PASTE:
        case IDM_EDIT_CLEAR:
        case IDM_EDIT_DUPLICATE:
        case IDM_EDIT_SELECTALL:

        case IDM_OPTIONS_FONT_NORMAL:
        case IDM_OPTIONS_FONT_BOLD:
        case IDM_OPTIONS_FONT_ITALIC:
        case IDM_OPTIONS_FONT_UNDERLINE:
        case IDM_OPTIONS_PARA_LEFT:
        case IDM_OPTIONS_PARA_CENTER:
        case IDM_OPTIONS_PARA_RIGHT:
        case IDM_OPTIONS_PARA_JUSTIFY:

        case IDM_HELP_INDEX:
        case IDM_HELP_GENERAL:
        case IDM_HELP_USING:
        case IDM_HELP_KEYS:
        {
           WinMessageBox (HWND_DESKTOP, hWnd,
             "Функция не реализована",
             szAppTitle, 0, MB_INFORMATION | MB_OK);
          break;
        }

        case IDM_HELP_ABOUT:
        {
           WinMessageBox (HWND_DESKTOP, hWnd,
             "Приложение MenuMod, (C) Frolov A., 1996",
             szAppTitle, 0, MB_INFORMATION | MB_OK);
          break;
        }

        // Если из меню File выбрана строка Exit,
        // завершаем работу приложения
        case IDM_FILE_EXIT:
        {
           WinPostMsg (hWnd, WM_QUIT , 0L, 0L);
           break;
        }
      }
      return(WinDefWindowProc (hWnd, msg, mp1, mp2));
    }

    default:
      return(WinDefWindowProc (hWnd, msg, mp1, mp2));
  }
}

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

Среди глобальных переменных, обычных для наших приложений, имеются переменные hwndEditMenu и hwndMenu. В первой из них хранится идентификатор динамически создаваемого временного меню Edit, вторая предназначена для хранения идентификатора окна меню верхнего уровня.

Функция main

Функция main в этом приложении не имеет никаких особенностей. Так как первоначально меню верхнего уровня загружается из ресурсов приложения, при создании главного окна мы указали флаг FCF_MENU .

Функция окна WndProc

Изменение системного меню и создание нового временного меню Edit выполняется обработчиком сообщения WM_CREATE . При этом используется методика, описанная нами ранее.

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

Файл menumod.h

Файл menumod.h, содержащий определения всех необходимых констант, представлен в листинге 3.6.

Листинг 3.6. Файл menumod\menumod.h

#define ID_APP_FRAMEWND 1

#define IDM_FILE        100
#define IDM_FILE_NEW    101
#define IDM_FILE_OPEN   102
#define IDM_FILE_SAVE   103
#define IDM_FILE_SAVEAS 104
#define IDM_FILE_EXIT   105

#define IDM_EDIT        200
#define IDM_EDIT_UNDO   201
#define IDM_EDIT_REDO   202
#define IDM_EDIT_CUT    203
#define IDM_EDIT_COPY   204
#define IDM_EDIT_PASTE  205
#define IDM_EDIT_CLEAR  206
#define IDM_EDIT_DUPLICATE 207
#define IDM_EDIT_SELECTALL 208

#define IDM_HELP           300
#define IDM_HELP_INDEX     301
#define IDM_HELP_GENERAL   302
#define IDM_HELP_USING     303
#define IDM_HELP_KEYS      304
#define IDM_HELP_ABOUT     305

#define IDM_OPTIONS                 500
#define IDM_OPTIONS_FONT            501
#define IDM_OPTIONS_FONT_NORMAL     502
#define IDM_OPTIONS_FONT_BOLD       503
#define IDM_OPTIONS_FONT_ITALIC     504
#define IDM_OPTIONS_FONT_UNDERLINE  505

#define IDM_OPTIONS_PARA            600
#define IDM_OPTIONS_PARA_LEFT       601
#define IDM_OPTIONS_PARA_CENTER     602
#define IDM_OPTIONS_PARA_RIGHT      603
#define IDM_OPTIONS_PARA_JUSTIFY    604

Файл ресурсов menumod.rc

Файл описания ресурсов приложения menumod.rc (листинг 3.7) содержит описание пиктограммы и меню верхнего уровня. Обратите внимание, что в меню Options определены два вложенных меню Font и Alighnment.

Определение временного меню Edit отсутствует, так как это меню создается динамически при обработке сообщения WM_CREATE .

Листинг 3.7. Файл menumod\menumod.rc

#include <os2.h>
#include "menumod.h"

ICON ID_APP_FRAMEWND MENUMOD.ICO

MENU ID_APP_FRAMEWND
BEGIN
  SUBMENU "~File",          IDM_FILE
    BEGIN
	MENUITEM  "~New...",     IDM_FILE_NEW
	MENUITEM  "~Open...",    IDM_FILE_OPEN
	MENUITEM  SEPARATOR
	MENUITEM  "~Save...",    IDM_FILE_SAVE
	MENUITEM  "Save ~as...", IDM_FILE_SAVEAS
	MENUITEM  SEPARATOR
	MENUITEM  "~Exit",       IDM_FILE_EXIT
    END

  SUBMENU "~Options", IDM_OPTIONS
   BEGIN
    SUBMENU "~Font",IDM_OPTIONS_FONT
     BEGIN
       MENUITEM  "~Normal",   IDM_OPTIONS_FONT_NORMAL
	 MENUITEM  "~Bold",     IDM_OPTIONS_FONT_BOLD
	 MENUITEM  "~Italic",   IDM_OPTIONS_FONT_ITALIC
	 MENUITEM  "~Underline",IDM_OPTIONS_FONT_UNDERLINE
     END

    SUBMENU "~Alighnment",IDM_OPTIONS_PARA
     BEGIN
       MENUITEM  "~Left",      IDM_OPTIONS_PARA_LEFT
	 MENUITEM  "~Center",    IDM_OPTIONS_PARA_CENTER
	 MENUITEM  "~Right",     IDM_OPTIONS_PARA_RIGHT
	 MENUITEM  "~Justify",   IDM_OPTIONS_PARA_JUSTIFY
     END
  END

  SUBMENU "~Help", IDM_HELP
    BEGIN
	MENUITEM  "Help ~index...",    IDM_HELP_INDEX
	MENUITEM  "~General help...",  IDM_HELP_GENERAL
	MENUITEM  "~Using help...",    IDM_HELP_USING
	MENUITEM  "~Keys help...",     IDM_HELP_KEYS
	MENUITEM  SEPARATOR
	MENUITEM  "~Product information...",IDM_HELP_ABOUT
    END
END

Файл определения модуля

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

Листинг 3.8. Файл menumod\menumod.def

NAME        MENUMOD   WINDOWAPI
DESCRIPTION 'MenuMod Application (C) Frolov A., 1996'
HEAPSIZE    4096
STACKSIZE   32768
EXPORTS     WndProc