Как отобразить контекстное меню?

Автор: Александр Шаргин
Опубликовано: 25.06.2001
Версия текста: 1.0

Создание контекстного меню

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

Вариант 1. Создание меню "на лету"

Меню создаётся с помощью функции CreatePopupMenu. Это очень простая функция, которая не принимает параметров и возвращает хэндл созданного меню. В этом меню изначально нет ни одного пункта. Добавить пункты можно, используя функции AppendMenu и InsertMenuItem. Например:

HMENU hPopupMenu = CreatePopupMenu();
AppendMenu(hPopupMenu, MF_STRING, ID_EDIT_UNDO, "&Undo\tCtrl+Z");       // Undo    Ctrl+Z
AppendMenu(hPopupMenu, MF_SEPARATOR, 0, 0);                             // --------------
AppendMenu(hPopupMenu, MF_STRING, ID_EDIT_CUT, "Cu&t\tCtrl+X");         // Cut     Ctrl+X
AppendMenu(hPopupMenu, MF_STRING, ID_EDIT_COPY, "&Copy\tCtrl+C");       // Copy    Ctrl+C
AppendMenu(hPopupMenu, MF_STRING, ID_EDIT_PASTE, "&Paste\tCtrl+V");     // Paste   Ctrl+V

Этот код создаёт стандартное меню, содержащее пункты Undo/Cut/Copy/Paste.

Вариает 2. Загрузка меню из ресурсов

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

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


Рисунок 1

ПРИМЕЧАНИЕ
В некоторых случаях можно даже не создавать отдельного меню, а "позаимствовать" одно из подменю главного. Например, при щелчке правой кнопкой на панели инструментов или строке состояния уместно отобразить подменю View из главного меню приложения.

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

    HMENU hMenu, hSubMenu;
    hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1));
    hSubMenu = GetSubMenu(hMenu, 0);

Отображение меню

Для отображения контекстного меню в Win32 API предусмотрена функция TrackPopupMenu(Ex). В MFC ей соответствует функция CMenu::TrackPopupMenu. Все эти функции выполняют следующие действия: отображают контекстное меню в заданной точке экрана, запускают собственный цикл сообщений, в котором отслеживают выбор пользователя, а в конце, когда пользователь выбрал какой-то пункт или закрыл меню, уведомляют программу о его выборе.

Рассмотрим прототип функции TrackPopupMenu.

BOOL TrackPopupMenu(
  HMENU hMenu,         // хэндл всплывающего меню
  UINT uFlags,         // опции
  int x,               // горизонтальная позиция
  int y,               // вертикальная позиция
  int nReserved,       // зарезервированный параметр, д. б. 0
  HWND hWnd,           // хэндл окна-владельца меню
  CONST RECT *prcRect  // параметр игнорируется
);

Параметр hMenu определяет всплывающее меню, которое следует отобразить. Параметры x и y задают положение меню на экране, а hWnd - окно, которое будет получать все сообщения от меню (этот параметр не может быть равен NULL). Параметры nReserved и prcRect не используются. Что касается опций, их полный список можно найти в документации. Я хочу обратить ваше внимание только на флаг TPM_RETURNCMD. Если он не задан, программа получит уведомление о выборе пользователя в виде сообщения WM_COMMAND. Если же его задать, функция TrackPopupMenu просто вернёт в программу идентификатор выбранного пользователем пункта меню, не отправляя никаких сообщений.

Уничтожение меню

По окончании работы с меню его следует уничтожить вызовом DestroyMenu. Эта функция получает один параметр - хэндл меню, которое подлежит уничтожению. Обратите внимание, что меню, полученное с помощью GetSubMenu, уничтожать не надо. Оно будет удалено в процессе уничтожения родительского меню, которое вы загрузили с помощью LoadMenu.

WM_CONTEXTMENU

Итак, мы научились отображать контекстное меню. Осталось ответить на последний вопрос: в каком месте программы вызывать рассмотренные выше функции. Первое, что приходит в голову - отобразить меню в ответ на сообщение WM_RBUTTONUP. Но предпочтительнее делать это в обработчике другого сообщения - WM_CONTEXTMENU. У этого сообщения есть целый ряд преимуществ перед WM_RBUTTONUP.

Вот полный пример обработчика WM_CONTEXTMENU.

case WM_CONTEXTMENU:
{
    // Извлекаем координаты курсора мыши из lParam
    POINT pt;
    pt.x = GET_X_LPARAM(lParam);
    pt.y = GET_Y_LPARAM(lParam);

    if(pt.x == -1 && pt.y == -1)
    {
        // Вызов с клавиатуры
        RECT rect;
        GetWindowRect(hWnd, &rect);
        // Выводим меню радом с левым верхним углом окна
        pt.x = rect.left + 5;
        pt.y = rect.top + 5;
    }

    // Загружаем меню из ресурсов
    HMENU hMenu, hPopupMenu;
    hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1));
    hPopupMenu = GetSubMenu(hMenu, 0);
    
    // Отображаем меню
    TrackPopupMenu(hPopupMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hWnd, NULL);

    // Уничтожаем меню
    DestroyMenu(hMenu);
}
В этом примере для извлечения координат курсора мыши используются макросы GET_X_LPARAM и GET_Y_LPARAM. Эти и многие другие полезные макросы описаны в заголовочном файле windowsx.h.

В MFC можно использовать этот код без изменений или переписать его через класс CMenu.

Автоматическое добавление контекстного меню

Если вы программируете с использованием MFC, у вас есть счастливая возможность переложить работу по созданию контекстного меню на среду Visual C++. Для этого откройте окно Project->Add To Project->Components and Controls, а затем выберите компонент Pop-up Menu из папки Visual C++ Components. Появится ещё одно окно, в котором вам предложат выбрать, к какому окну вы хотите добавить контекстное меню, а также идентификатор ресурса меню. Вводите нужные параметры, жмите ОК, и в выбранный класс добавится готовый обработчик OnContextMenu. Кроме этого, в ресурсы вашего приложения добавится новое меню, и вам останется только подредактировать его.


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