15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту

МЕНЮ

ПОДКЛЮЧЕНИЕ МЕНЮ К ОКНУ

            В этой главе мы должны научиться подключать меню к нашему окну и, естественно, научиться реагировать на сообщения, исходящие от меню. Мы узнаем о том, какие типы меню и элементов меню бывают, изучим различные способы построения меню.
       Любой, кто хоть немного работал в Windows, знает, что меню располагаются сразу под заголовком окна и позволяют пользователю осуществить выбор возможностей, предоставляемых программой. Помимо этого, существуют также всплывающие меню, которые могут появляться в любой точке экрана. Обычно их содержание зависит от того, на каком окне щелкнули клавишей мыши. Их изучение начнем с характеристик меню.
      Давайте представим себе главное меню программы как древовидную структуру. Основная часть - корень нашего дерева - это непосредственно главное меню. Само по себе оно представляет только структуру в памяти, не отображается на экране, не содержит ни одного элемента, но хранит указатель на список структур, описывающих подключаемые к нему элементы и всплывающие (popup) меню. Если читатель вспомнит свой опыт работы с Windows, он согласитсяс тем, что в окне в качестве основного меню отображается именно набор рорир-меню. В свою очередь, рорир-меню должно указать на список структур очередного, более низкого уровня и т. д. Конечные элементы меню никаких указателей на списки не имеют, но хранят некий идентификатор действия (назовем его идентификатором элемента меню), которое должна произвести программа при выборе данного элемента меню. Используя эти структуры, мы можем построить меню практически любой глубины и сложности.

Эта многоуровневая древовидная структура описывается в файле ресурсов. Описание меню имеет вид:

MenuName MENU [параметры] // это - главное меню

{ Описание всех рорир-меню и элементов меню второго уровня

}

В данном случае MenuName - это имя создаваемого нами меню. Слово MENU обозначает начало определения меню. Параметры мы пока, до изучения работы с памятью, расматривать не будем.

В Win32 API для описания .меню существуют два ключевых слова. Первое - POPUP - специфицирует всплывающее меню. Второе -MENUITEM - описывает обычный элемент меню.

Всплывающие меню описывается следующим образом:

POPUP «Имя» [.параметры] // <- описание рорир-меню

{ Описание всех рорир-меню и элементов очередного уровня

У конечного элемента меню в его описании есть еще одна характеристика - тот самый идентификатор действия:

MENUITEM «Имя», MenuID [.параметры]

В обоих случаях «Имя» - это тот текст, который будет выведен на эк-. ран при отображении меню (обратите внимание - при описании главного меню выводимого на экран текста нет!). В том случае, когда вместо имени окна записано слово SEPARATOR (без кавычек!), на ме сте элемента меню появляется горизонтальная линия. Обычно эти горизонтальные линии (сепараторы или разделители) используются для разделения элементов подменю на логические группы (логика определяется только программистом и никаких особенностей не содержит).

Если в имени меню встречается символ «&», то следующий за амперсандом символ на экране будет подчеркнут одинарной чертой. Этот элемент меню можно будет вызывать с клавиатуры посредством одновременного нажатия клавиши Alt и подчеркнутого символа.

'Таблица. Параметры, описывающие элемент меню в файле ресурсов

Флаг Значение
CHECKED Рядом с именем элемента может отображаться небольшой значок, говорящий о том, что соответствующий флаг установлен
ENABLED Элемент меню доступен
DISABLED Элемент меню недоступен, но отображается как обычный
GRAYED Элемент меню недоступен и отображается серым цветом
MENUBREAK Горизонтальные меню размещают следующие элементы в новой строке, а вертикальные - в новом столбце
MENUBARBREAK То же, что и предыдущее, но в случае вертикального меню столбцы разделяются вертикальной линией

MenuID - идентификатор действия. Он может быть передан функции окна, содержащего меню. Значение идентификатора определяется пользователем. Функция окна в зависимости от полученного MenuID производит определенные действия.

Параметры же описывают способ появления элемента на экране. Возможные значения параметров приведены в табл..

Попробуем создать описание небольшого меню. Горизонтальное меню (menubar) позволит выбирать подменю «File», «Examples» и конечный элемент «Help». Подменю «File» будет содержать элементы «Open » и «Exit», разделенные горизонтальной линией, а подменю «Examples» -несколько конечных элементов.

Ниже приведен текст скрипта для этого меню:

МуMenu  MENU
     {
           POPUP "&File"
                  {
                        MENUITEM "&0pen", 101
                                MENUITEM SEPARATOR
                                MENUITEM "E&xit", 102
                        }
               POPUP "&Examples"
                        {
                                POPUP "Example 1"

                                          {
                                     MENUITEM "l&l", 103
                                                 MENUITEM "1&2", 104
                                          }
                                POPUP "Example &2"

                                          {
                                     MENUITEM "2&1", 105
                                                 MENUITEM "2&2", 106
                                          }
               MENUITEM "&Help", 111
      }

Следует обратить внимание на то, что идентификаторы действия есть только у MENUITEM'ов. Popup-меню идентификаторов не содержат.

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

к проекту добавляется файл ресурсов (желательно, чтобы имя файла ресурсов совпадало с именем программы);

в текст программы вносится изменение - при определении класса окна полю IpszMenuName структуры типа WNDCLASS присваивается указатель на строку, содержащую имя меню. В данном случае WndClass.lpszMenuName = «MyMenu»;

производится перекомпиляция проекта.

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

Таким образом, с помощью добавления к программе меню мы определили функциональность нашей программы. Конечно, тот пример, который здесь приведен, предназначен только для того, чтобы продемонстрировать возможности по управлению меню с помощью ресурсов. Более того, из сказанного можно сделать вывод, что возможности Win32 по управлению меню с помощью ресурсов достаточно скудны. Да, это так. Существует еще масса функций, позволяющих манипулировать меню. Мы приступим к их рассмотрению после того, как научимся реагировать на манипуляции, производимые с меню.

Реакция окна на сообщения от меню

Как уже было сказано выше, элементы в меню могут быть обычными, запрещенными и «серыми». Для пользователя обычные и запрещенные элементы выглядят одинаково, а текст в «серых» элементах напечатан серым шрифтом. Но только обычные элементы позволяют пользователю произвести выбор. Запрещенные и «серые» элементы меню могут быть только подсвечены, но с их помощью произвести выбор нельзя. Кроме этого, существуют отмечаемые элементы меню. В них слева от текста может находиться какой-либо значок. Если значок есть, то считают, что флажок, определяемый этим элементом, установлен. Если флажок сброшен, то значок отсутствует.

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

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

(другими словами, все они являются структурами в памяти). Этот факт в дальнейшем будет играть важную роль.

Давайте попробуем поговорить о меню с точки зрения сообщений. При смене подсвеченного элемента меню (если, к примеру, пользователь «пробегает» по элементам меню с помощью клавиш со стрелками вверх и вниз) в оконную процедуру посылается сообщение WM_MENUSELECT. Это сообщение посылают все элементы меню. Когда же пользователь производит выбор (нажимает клавишу «Enter», к примеру), сообщение WM_COMMAND оконной процедуре посылают только обычные элементы меню. Запрещенные и «серые» элементы меню в этом случае никаких сообщений не посылают. В элементах wParam и IParam посылаемых сообщений хранится информация, достаточная для того, чтобы программа смогла определить, какие действия ей необходимо выполнить случае выбора пользователем того или иного элемента меню.
      Вспомним, что помимо обычного меню у окна в большинстве случаев есть еще и системное меню. Сказанное относится и к системному меню. Отличие между обычным меню и системным состоит в том, что оконной процедуре посылаются сообщения WM_SYSMENUSELECT и WM_SYSCOMMAND. Кроме этого, сообщения WM_SYSCOMMAND оконная процедура получает и в случае нажатия кнопок минимизации, максимизации и закрытия окна, которые находятся не в системном меню, а в правом углу заголовка окна.
      Рассмотрим параметры сообщения WM_MENUSELECT более подробно. В младшем слове wParam оконная процедура получает сведения о том, какой элемент стал подсвеченным. Если учесть, что макросы LOWORDO и HIWORD() выделяют соответственно младшее и старшее слово 32-битного аргумента, и назвать источник сообщения ultem, то можно записать:

ultem = (UINT) LOWORD(wParam);

В зависимости от обстоятельств смысл ultem различается:

если подсвеченный элемент является конечным и не влечет за собой вызов рорир-меню, то ultem содержит идентификатор элемента меню;

если подсвеченный элемент при выборе влечет за собой вызов рорир-меню, то ultem содержит номер (индекс) этого элемента в том меню, в котором оно находится;

В старшем слове wParam содержатся характеристики подсвеченного элемента. Аналогично предыдущему, fuFlags = (UINT) HIWORD(wParani);

Возможные значения fuFlags приведены в табл.


Таблица. Характеристики подсвеченного элемента меню

Флаг Значение Описание
MFB1TMAP Ox00000004L Вместо строки в качестве элемента меню
    применяется bitmap
MF CHECKED Ox00000008L Элемент отмечаемый (со «значком»)
MF DISABLED Ox00000002L Элемент запрещен
MFGRAYED 0х00000001L Элемент запрещен и отображается серым
    цветом
MF HILITE OxOOOOOOSOL Элемент подсвечен
MF MOUSESELECT OxOOOOSOOOL Элемент выбран мышью
MFOWNERDRAW 0х00000100L За прорисовку элемента отвечает не система,
    а программа
MFPOPUP 0х00000010L Элемент вызывает появление рорир-меню
    более низкого уровня
MFSYSMENU Ox00002000L Элемент из системного меню


IParam содержит в себе хэндл того меню, которому принадлежит подсвеченный элемент. Обозначив хэндл меню как hMenu, получим:

hMenu - (HMENU) IParam;

Теперь пришла очередь рассмотрения сообщения WM_COMMAND. Как и в случае с WMJSELECTMENU, младшее слово wParam содержит сведения об источнике сообщения. Так как сообщение WM_COMMAND посылается только конечными элементами меню, то в младшем слове wParam содержится идентификатор выбранного элемента меню. На языке С

wID = LOWORD(wParam);

Старшее слово wParam указывает, от какого управляющего элемента пришло сообщение. Если сообщение пришло от меню, то это слово равно нулю, т. е.

wNotifyCode = HIWORD(wParam) = 0;

IParam в случае сообщения от меню ВСЕГДА равно NULL!

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

Меню без использования ресурсов

Перед тем, как начать рассмотрение функций, предназначенных для работы с меню, я хотел бы сказать, что являюсь сторонником комбинированного использования меню, определенного в виде ресурса, и функций Win32. Решение об использовании того или иного способа должен принимать программист в соответствии с задачами, которые должна решать разрабатываемая программа. Если в примере я придерживаюсь какого-то одного способа, то читатель Должен понимать, что пример - это всего-навсего демонстрация возможностей Win32, а не призыв всегда и везде делать именно так, как сделано в предлагаемом примере. Еще раз повторю - программист свободен в выборе применяемых технологий.
      Как было сказано выше, меню имеет строгую древовидную структуру, которая начинается с меню первого уровня (оно обычно называется главным меню программы или menubar'ом и располагается сразу под заголовком окна). К этому меню первого уровня могут быть присоединены как конечные элементы меню, так и элементы, выбор которых приводит к появлению так называемых всплывающих (popup) меню, к которым, в свою очередь, присоединяются элементы очередного уровня и т. д. Перед началом создания меню вся его структура должна быть тщательно продумана. Неплохо, если бы программист имел перед глазами графическое представление этого меню. Если все предварительные вопросы решены, то мы готовы приступить к созданию меню.

Итак, для создания меню необходимо выполнить следующие действия:

выбрать подменю самого низкого уровня, которые содержат только конечные элементы меню, и создать их с помощью функций CreateMenu() или CreatePopupMenu() в зависимости от потребностей. Эти функции возвращают хэндл созданного меню. Меню создается пустым;

посредством функции AppendMenuQ добавляем в них требуемые элементы;

создаем меню следующего, более высокого уровня, и добавляем в них требуемые элементы и меню, созданные нами на предыдущем шаге;

повторяем эти шаги до тех пор, пока создание всех подменю не будет закончено;

создаем главное меню программы посредством использования функции CreateMenu();

присоединяем созданные подменю самого высокого уровня к главному меню программы с помощью функции AppendMenuQ;

присоединяем меню к окну посредством использования функции SetMenu();

прорисовываем меню с помощью функции DrawMenuBar().
      Если в ходе программы сложилась такая ситуация, что меню оказалось не присоединенным к окну, перед выходом из программы обязательно уничтожаем его, вызывая функцию DestroyMenu() (присоединенное к окну меню уничтожается автоматически при уничтожении окна).
     Для того чтобы проиллюстрировать сказанное, давайте разберем небольшую программу. Я постарался написать эту программу так, чтобы в ней имелись основные типы элементов меню, и была бы возможность обработать выдаваемые меню сообщения. Чтобы придать программе функциональность, информация о получении сообщения будет выдаваться в строку состояния - небольшую область внизу окна (возможности строки состояния мы будем изучать позже). При запуске у окна возникав меню. Внизу экрана появляется строка состояния, в которой будет отображаться информация о выбранном элементе меню. Основное меню окна состоит из двух всплывающих меню, «File» и «Help», и элемента меню, в котором вместо строки отображается bitmap. В первом всплывающем меню находятся два элемента, «Enable exit» и «Exit», во втором -один элемент, «About», который остается запрещенным в течение всего периода существования окна. Кроме этого, элемент «Exit» при запуске объявляется «серым», т. е. из программы можно выйти только через системное меню. Однако в случае выбора элемента «Enable exit» «Exit» становится обычным, а вместо «Enable exit» возникает «Disable exit». При выборе элемента с bitmap'ом отображается окно сообщений с текстом о том, что выбран именно этот элемент. На этом возможности программы исчерпываются.

Листинг № З. Программа, демонстрирующая возможности по манипулированию меню.

#include <windows.h>
#include <commctrl.h>

const IDM_Enable_Disable = 0;
const IDM_Exit = l;
const IDM_About = 2;
const IDP_File=3;
const IDP_Help = 4;

char* pMessages[]
{
     "Enable or disable exit",
     "Exit from the program",
       "About this program",
       "File operations",
       "Help operations",
       "Menu example",
       "System menu"
};

LRESULT CALLBACK MenuDemoWndProc (HWND, UINT, UINT, LONG);

HWND hStatusWindow;
UINT wid;
HMENU hMenu,hFileMenu,hHelpMenu;
HINSTANCE hinst;

int APIENTRY WinMain (HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow )
{
   HWND hWnd ;
   WNDCLASS WndClass;
   MSG Msg;

   hinst = hinstance;
   /* Registering our window class */
   /* Fill WNDCLASS structure */
   WndClass.style = CS_HREDRAW ¦ CS_VREDRAW;
   WndClass.lpfnWndProc = (WNDPROC) MenuDemoWndProc;
   WndClass.cbClsExtra = 0;
   WndClass.cbWndExtra = 0;
   WndClass.hInstance = hinstance ;
   WndClass.hIcon = Loadlcon (NULL, IDI_APPLICATION);
   WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);
   WndClass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH);
   WndClass.lpszMenuName = NULL;
   WndClass.lpszClassName = "MenuExample";

   if( !RegisterClass(&WndClass))  {
        MessageBox(NULL,"Cannot register class" ."Error" ,MB_OK);
        return 0;

   }

   hWnd = CreateWindow(" MenuExample", "Program No 2",
                                                   WS_OVERLAPPEDWINDOW,
                                                   CW_USEDEFAULT,
                                                   CW_USEDEFAULT,
                                                   CW_USEDEFAULT,
                                                   CW_USEDEFAULT,
                                                   NULL, NULL,
                                                   hInstance.NULL);

    if(!hWnd)
  {
           MessageBox(NULL, "Cannot create window", "Error", MB_OK);
           return 0;
   }
   InitCommonControlsO;
   hStatusWindow = CreateStatusWindow(WS_CHILD ¦ WS_VISIBLE,"Menu sample", hWnd, wId);
   if(!hStatusWindow) {
         MessageBox(NULL, "Cannot create status window", "Error", MB_OK);
         retum 0;
   }
/* Try to create menu */
   AppendMenu( (hFileMenu=CreatePopupMenu()), MF_ENABLED, MF_STRING,
                                 IDM_Enable_Disable, "&Enable exit");
   AppendMenu(hFileMenu, MF_GRAYED ¦ MF_STRING, IDM_Exit,"E&xit");
   AppendMenu((hHelpMenu=CreatePopupMenu()),MF_DISABLED MF_STRING, IDM_About, "&About");
   hMenu = CreateMenu();
   AppendMenu(hMenu, MF_ENABLED ¦ MF_POPUP, (UINT) hFileMenu, "&File");
   AppendMenu(hMenu, MF_ENABLED ¦ MF_POPUP, (UINT) hHelpMenu, "&Help");
   SetMenu(hWnd, hMenu);
/* Show our window */
   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);
   DrawMenuBar(hWnd);


/* Beginning of messages cycle */
   while(GetMessage(&Msg, NULL, 0, 0))  {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
   }
   retum Msg.wParam;
}

LRESULT CALLBACK MenuDemoWndProc (HWND hWnd, UINT Message,
                                                                                     UINT wParam, LONG IParam )
{
   RECT Rect;
   static UINT nFlag = MF_ENABLED;
   char* pContentQ
   {
        "Enable exit",
        "Disable exit"
   };
   static UINT nindex = 0;
   static HBITMAP hBitmap;
   int nDimension;

   switch(Message)  {
        caseWM CREATE:
                   nDimension = GetSystemMetrics(SM_CYMENU);
                   hBitmap = Loadlmage(hlnst, "msdogs.bmp", IMAGE_BITMAP,
                                                              nDimension • 2, nDimension, LR_LOADFROMFILE);
                   AppendMenu(GetMenu(hWnd), MF_BITMAP, IDM_Bitmap, hBitmap);
                   break;
        case WM_COMMAND:
                   switch (wParam)  {
                          case IDM_Enable_Disable:
                                    EnableMenuItem(hFileMenu, IDM_Exit, MF_BYCOMMAND ¦ nFlag);
                                    nFlag = (nFlag == MF_ENABLED) ? MF_GRAYED : MF_ENABLED;
                                    nindex = ( nindex == 0) ? 1 : 0;
                                    ModifyMenu(hFileMenu, IDM_Enable_Disable, MF_BYCOMMAND ¦
                                                                     MF_STRING, IDM_Enable_Disable, pContent[nIndex]);
                                    break;
                          case IDM_Exit:
                                    SendMessage(hWnd, WM_CLOSE, NULL, NULL);
                                    break;
                 }
        case WM_SIZE:
                   SendMessage(hStatusWindow, WM_SIZE, wParam, IParam);
                   GetClientRect(hWnd, &Rect);
                   return 0;
        case WM_MENUSELECT:
// Selection is losted
                  if( ((UINT) HIWORD(wParam) == Oxffff) & ((HMENU) IParam == 0))  {
                           SendMessage(hStatusWindow ,SB_SETTEXT, (WPARAM) 0, (LPARAM) pMessages[5]);
                           return 0;
                   }
                   if ((UINT) HIWORD (wParam) & MF_SYSMENU)
  {
                    SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0, (LPARAM) pMessages[6]);
                           return 0;

                   }
              if ((UINT) HIWORD(wParam) & MF_POPUP)  {
                    SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0,
                                          (LPARAM) pMessages[3 + LOWORD(wParam)]);
                    return 0;
              }

              SendMessage(hStatusWindow, SB_SETTEXT, (WPARAM) 0, (LPARAM) pMessages[LOWORD(wParam)]);
                  return 0;
       case WM_DESTROY:
                  DeleteObject(hBitmap);
                  PostQuitMessage(0);
                  return 0;
       }
   return DefWindowProc(hWnd,Message,wParam, IParam);

}

      Как и в случае нашей первой программы для Windows, давайте рассмотрим эту программу. Естественно, включаем файлы заголовков Win32. Включение файла «commctrl.h» обусловлено вызовом функций для работы со строкой состояния, заголовки которых находятся в этом файле. Далее идут многочисленные описания и определения, объяснять которые я не буду, они сразу же становятся понятными при разборе программы. В функции WinMain() все ясно до момента вызова функции InitCommonControls(). Но и здесь нет ничего страшного. Эта функция вызывается всегда перед использованием библиотеки общих элементов управления, к которым относится и строка состояния. К меню и нашей задаче эта функция имеет весьма далекое отношение. Интерес вызывает фрагмент, который начинается после строки комментария /* Try to create menu */.

Таблица. Битовые флаги, определяющие поведение и вид элемента меню

Флаг Назначение
MF_BITMAP Вместо строки символов в качестве элемента меню
  используется изображение (bitmap)
MF_CHECKED Отмечаемый элемент меню
MF_DISABLED Запрещенный, но не «серый» элемент меню
MF_ENABLED Разрешенный элемент меню
MF_GRAYED Запрещенный «серый» элемент меню
MF_MENUBARBREAK То же, что и следующее, но вертикальные столбцы отделя
  ются вертикальной чертой
MF_MENUBKEAK Очередной элемент меню размещают в новой строке
  (menubar) или в новом столбце
MF_OWNERDRAW За прорисовку элемента отвечает программа, а не Windows
MF_POPUP Выбор элемента влечет за собой появление меню более
  низкого уровня
MF_SEPARATOR Горизонтальная черта
MF_STRING В качестве элемента меню используется строка символов
MF_UNCHECKED Неотмечаемый элемент меню


      Как уже было сказано, сначала мы создаем меню с помощью вызова функции CreatePopupMenu(), которая возвращает нам хэндл созданного меню hFileMenu. В это меню мы с помощью функции AppendMenu() добавляем два элемента, «Enable exit» и «Exit». Функция AppendMenu() заслуживает того, чтобы поговорить о ней подробнее.
      При вызове функции AppendMenu() она получает четыре аргумента. Аргумент первый - хэндл того меню, в которое добавляется элемент. Ничего сложного или интересного в этом вызове нет. Второй аргумент -комбинация битовых флагов, определяющих внешний вид и поведение добавляемого элемента меню. Перечень и назначение флагов приведены в табл.
        Заметим, что некоторые флаги не могут быть установлены одновременно. Например, MF_BITMAP, MF_STRING и MF_POPUP, MFJENABLED, MFJDISABLED и MF_GRAYED.
     Интерпретация третьего параметра зависит от того, установлен ли во втором параметре флаг MF_POPUP, т. е. является ли добавляемый элемент меню конечным элементом или влечет за собой вызов очередного меню. В первом случае - конечный элемент - параметр содержит идентификатор этого элемента. Если же добавляется меню, то параметр содержит хэндл добавляемого меню.
      И последний параметр тоже интерпретируется в зависимости от установленных флагов. Установлен флаг MF_BITMAP - параметр содержит хэндл bitmap'а. Установлен флаг MF_STRING - параметр содержит указатель на строку символов. Установлен MF_OWNERDRAW - параметр содержит информацию, используемую программой при прорисовке элемента.
      Разобравшись с функцией AppendMenu(), мы можем не останавливаться на последующих ее вызовах и остановиться на вызове функции SetMenu().
      Что означает «создать меню»? Это, как и в случае создания окна, означает всего лишь создание и последующее заполнение некоторых структур в памяти. После создания меню не принадлежит никакому окну и до поры до времени бесполезно блуждает в глубинах памяти. Для того чтобы меню могло выполнять свои функции, оно должно быть «закреплено» за одним из окон. Функция SetMenu() и привязывает меню к конкретному окну. Аргументы этой функции очевидны - хэндл закрепляемого меню и хэндл окна, к которому меню прикрепляется. После вызова этой функции указатель на меню включается в структуру окна и может нормально использоваться.
      После отображения и прорисовки окна вызывается функция DrawMenuBar() для прорисовки меню. И у этой функции очевидно наличие одного аргумента - хэндла окна, которому принадлежит прорисовываемое меню.
      К этому моменту мы произвели все действия, требующиеся для создания меню. Далее программа запускает цикл обработки сообщений и начинает посылать сообщения функции окна. Перейдем к рассмотрению оконной функции. Перед этим я прошу читателя не пугаться, увидев обращение к функции SendMessage(). Она только посылает окну (хэндл окна-адресата - первый аргумент) сообщение (второй аргумент) с заданными wParam (третий аргумент) и IParam (четвертый аргумент). В данном случае посылаются сообщения строке состояния для того, чтобы в ней отобразился текст, на который указывает IParam.
      При этом оконная функция самостоятельно обрабатывает только четыре сообщения - WM_COMMAND, WM_MENUSELECT, WM_SIZE и WM_DESTROY. WM_DESTROY мы уже рассматривали. WM_SIZE в этом примере используется для того, чтобы при изменении размеров окна строка состояния могла бы нормально перерисоваться, т. е. сейчас это сообщение нас не интересует. В настоящий момент интерес представляют только сообщения WM_MENUSELECT и WM_COMMAND.
      Начнем с рассмотрения WM_MENUSELECT. У этого сообщения есть одна особенность. Если поле fuFIags, т. е. старшее слово wParam, равно ОхППи при этом поле hMenu (т. е. IParam) равно NULL, то это означает, что меню закрылось (не стало выделенных элементов), так как пользователь нажал Escape или щелкнул мышкой где-нибудь вне меню. В этом случае в строке состояния появляется текст, описывающий назначение программы в целом («Menu example»). При получении сообщения от системного меню в строке состояния возникает «System menu». Во всех остальных случаях текст в строке состояния описывает назначение подсвеченного элемента меню. Непосредственно ход обработки этого сообщения ясен из листинга и особых пояснений не требует.
      Но обработка WM_MENUSELECT - это всего лишь прелюдия к настоящей работе, которая происходит тогда, когда пользователь выбирает конечный элемент меню посредством нажатия клавиши «Enter» или щелкнет левой клавишей мышки на элементе. В этом случае оконная процедура получает сообщение WM_COMMAND. Для того чтобы определить, как реагировать на сообщение, мы должны проанализировать младшее слово wParam, которое хранит идентификатор элемента меню, и в зависимости от его значения предпринимать те или иные действия. В данном случае оконная функция может получать WM_COMMAND только с двумя идентификаторами - IDM_Enab)e_Disable и IDM_Exit. При получении последнего мы осуществляем выход из программы. При обработке первого я демонстрирую использование двух функций -EnableMenuItem() и ModifyMenu().
      При получении WM_COMMAND, младшее слово wParam которого равно IDM_Enable_Disable, производятся следующие действия:
  - с помощью функции EnableMenuItem() запрещается или делается доступным элемент «Exit»;
- с помощью функции ModifyMenuQ изменяется текст элемента, вы-бор*которого приводит к состоянию элемента «Exit».
      Эти функции достаточно показательны и их разбор поможет читателю еще глубже понять функции, работающие с меню.
    Функция EnableMenuItem() позволяет программисту изменять состояние элемента (разрешенный, запрещенный, «серый») меню по своему усмотрению. При вызове функции, ей передаются три аргумента. Первый аргумент - хэндл того меню, которому принадлежит элемент. В нашем случае меняется состояние элемента, находящегося в меню «File», хэндл которого hFileMenu. Второй аргумент определяет тот элемент, состояние которого изменяется, но каким способом происходит определение, а также в какое состояние переходит элемент, зависит от третьего аргумента, который в очередной раз представляет комбинацию битовых флагов.

Таблица. Флаги, используемые при вызове функции EnableMenuItem()

Флаг Значение
MF_BYCOMMAND
MF_BYPOSITION
MF_ENABLED
MF_DISABLED
MF_GRAYED
Изменяемый элемент меню определяется по его идентификатору
Изменяемый элемент меню определяется по его номеру (индексу)в меню
После вызова функции элемент становится разрешенным
После вызова функции элемент становится запрещенным
После вызова функции элемент становится «серым»


      После изменения состояния элемента «Exit» с разрешенного на серое и наоборот, необходимо изменить текст в элементе, от которого зависит это состояние. Это изменение производится посредством вызова функции ModifyMenu(), которой передаются пять аргументов. Первые два аргумента функционально подобны аргументам EnableMenuItem(), т. е. первый аргумент - хэндл меню, которому принадлежит изменяемый элемент, а второй аргумент определяет непосредственно изменяемый элемент. Можно было бы сказать, что и третий аргумент функционально подобен, но он представляет собой комбинацию флагов, во-первых, определяющих элемент, подлежащий изменению (MF_BYCOMMAND или MF_BYPOSITION), а во-вторых, определяющих состояние элемента после изменения (перечень этих флагов в точности соответствует приведенному в табл). Четвертый аргумент указывает или идентификатор измененного элемента, или хэндл нового меню (если, конечно, в третьем аргументе установлен флаг MF_POPUP). И наконец, последний аргумент - новое содержание измененного элемента. В зависимости от того, какой флаг установлен в третьем аргументе (MF_BITMAP, MF_STRING или MF_OWNERDRAW), последний аргумент содержит хэндл bitmap'a, указатель на строку или информацию, используемую при прорисовке элемента.
      Таким образом, с помощью только функций Win32 мы создали меню и изменили его состояние.
      Надеюсь, что при чтении этого раздела и разборе приведенного примера читатель понял технические приемы работы с меню, и теперь может применять полученные знания при разработке собственных программ. Описанными функциями отнюдь не исчерпываются возможности Win32 по управлению меню. Например, в меню можно не добавлять, а вставлять элементы посредством функции InsertMenu(), функция DeleteMenu() удаляет элемент из меню, информацию о меню можно получить с помощью функций GetMenu(), GetMenuString(), GetMenuItemCount() и других.