Проектировщики компьютерных программ изобрели графический интерфейс пользователя ( GUI - ), чтобы облегчить взаимодействие с компьютерными системами и приложениями. Важной частью этого интерфейса является меню.
Хотя никаких стандартов разработки меню не существует, разработчиками приложений при проектировании меню накоплен богатый опыт, позволяющий облегчить работу будущего пользователя. Его необходимо придерживаться, чтобы не вынуждать потенциального пользователя нашего приложения срочно получать второе высшее только для того, чтобы правильно воспользоваться произведенным детищем.
Различные составляющие приложений Windows, включая макет диалогового окна, меню, панели инструментов, изображения, текстовые строки, командные клавиши, управляющие элементы и т.д. рассматриваются как ресурсы. Проектировщик приложения на визуальном уровне указывает среде IDE содержимое текстов, размеры, идентификаторы ресурсов, типы ресурсов, а среда в это время заносит эти сведения в файл ресурсов на специальном языке - описания ресурсов. Ресурсы - это как видимые фишки в казино взамен пока еще невидимых денег. В приложениях ресурсы превращаются в объекты только после их компиляции.
Меню можно создавать заранее, как ресурс, с помощью редактора меню и такие меню называются статическими. Но чаще всего необходимо в приложении обеспечить управление составом меню в зависимости от состояния приложения. Некоторые пункты меню нужно скрыть, некоторые новые - показать, некоторые заблокировать или разблокировать. Такие действия позволяет выполнить MFC -класс CMenu. Более того, с помощью этого класса можно вообще программно создать меню с нуля, задав значения соответствующих свойств класса. Такая технология создания меню программно, без использования ресурсов, называется динамическим меню.
Однако удобнее использовать комбинированный подход: заранее создать необходимый набор ресурсов статическим способом, затем по мере необходимости подгружать в работающее приложение тот или иной ресурс программно, управляя его составом нужным образом. В нашей лабораторной работе технологию динамической загрузки и программного управления меню мы применим ниже применительно к контекстным меню.
Создайте новый проект
Application Type Application Type Dialog Based Resource language:Project style:MFC standard Use of MFC:Use MFC in a static library User Interface Features Main frame styles:System menu Dialog title:MenuAdvanced Features
После создания мастером формы удалите из диалога все элементы управления
Русифицируйте ресурс диалогового окна с идентификатором IDD_MENU_DIALOG
Измените заголовок диалогового окна, указав фамилию, номер и название лабораторной работыЗаменим иконку приложения на любую иную по выбору. Для этого сделайте следующее:
Найдите в панели Resource View узел Icon. В нем хранится ресурс с идентификатором IDR_MAINFRAME. Это стандартное имя для иконки приложения
Вызовите для узла Icon контекстное меню (или выделите его и через категорию Project главного меню) выполните команду Add Resource
В появившемся диалоговом окне выполните кнопку Import
Настройте фильтр на расширение *.ico, перейдите в каталог Icons с готовыми иконками и загрузите ресурс новой иконки
Закройте панель с графическим редактором иконок, удалите ресурс IDR_MAINFRAME, а сгенерированный идентификатор IDI_ICON1 новой иконки переименуйте в IDR_MAINFRAME
Полностью перестройте приложение ( Build/Rebuild Solution ) и убедитесь, что новая иконка появиласьУбедитесь, также, в том, что файл .ico оболочка скопировала в каталог res проекта.
Добавьте к приложению одну кнопку Exit (Выход) с идентификатором ресурса IDC_EXIT и поместите в ее обработчик вместо традиционного OnOK() следующий кодОбработчик кнопки завершения приложения
void CMenuDlg::OnBnClickedExit()
{
PostMessage(WM_QUIT); // Посылаем сообщение напрямую
}
После того, как базовая заготовка создана мастером, нужно добавить и настроить в нее меню.
Откройте панель Resource View, выделите верхний узел дерева ресурсов, идентичный имени проекта, в контекстном меню (или главном меню Project оболочки) выполните опцию Add Resource (Добавить ресурс)
В диалоговом окне Add Resource дважды щелкните по ресурсу Menu
В области редактирования оболочки запустится дизайнер ресурса меню. Первое место в меню подсвечено.

Меню готово для проектирования. При задании пунктов меню вводится текст, который увидят пользователи приложения. Никакие свойства пункта не будут доступны, пока не ввести название пункта в соответствующую позицию (панель Properties будет пустой). Элементы строки меню верхнего уровня (категории) не выполняют никаких функций, кроме как раскрываются, поэтому им не нужно присваивать идентификаторы ресурсов (это же справедливо и для сепараторов).
Пункты меню можно вводить сразу в подсвеченную синим маркером область или вначале активизировать поле редактирования пункта. Горизонтальные разделители ( сепараторы ) вставляются клавишей "минус". По пунктам меню дизайнера можно передвигаться стрелками. При выходе за пределы крайних пунктов автоматически создается пустой шаблон нового пункта, который в окончательном варианте не участвует. Ненужные пункты можно удалить клавишей Delete.
Дизайнер меню поддерживается и главным меню оболочки и своим контекстным меню. Откаты редактирования дизайнер поддерживает не все, поэтому если произвести некоторое ошибочное действие, то восстанавливать первоначальный вариант нужно вручную.
Дизайнер меню автоматически формирует идентификатор ресурса пункта меню по его названию. Идентификаторы должны состоять из знаков подчеркивания, цифр и букв латинского алфавита. Если пункты меню вводить сразу на русском, то идентификаторы дизайнер будет генерировать из цифр, далеких от мнемонического смысла закрепленной за пунктом задачи. Поэтому возможны два варианта:
Сразу вчерне присвоить пунктам названия по-английски для автоматической генерации понятных по смыслу имен идентификаторов, а затем все переписать по-русски. Здесь следует учесть, что автоматическая генерация идентификатора пункта происходит только один раз при первом заполнении названия пункта. При всех последующих изменениях названия первоначально сгенерированное имя идентификатора автоматически уже не изменяется (ну и ладно, изменим вручную).
Писать названия пунктов сразу по-русски, затем редактировать идентификаторы ресурсов этих пунктов. Редактировать идентификаторы и названия пунктов после их создания можно через панель Properties или прямо в дизайнере меню, включив или выключив режим Edit IDs (Редактировать идентификаторы).
Разработайте ресурс меню приложения в соответствии с таблицей| Схема ресурса меню | |||
|---|---|---|---|
ID |
Название | ID |
Название |
| Пусто | File |
Пусто | Help |
ID_FILE_HELLO |
Hello |
ID_HELP_ABOUT |
About |
| Пусто | ---------Separator--------- |
||
ID_FILE_EXIT |
Exit |
||
Исправьте идентификатор ресурса меню с IDR_MENU1 на IDR_MENU. Для этого выделите в панели Resource View ресурс IDR_MENU1 и в панели Properties внесите изменения
Таким образом мы разработали ресурс меню, который дизайнер по визуальному представлению описал в файле ресурсов Menu.rc
Запустите приложение и убедитесь, что данное меню в окне приложения не появилось. Компилятор не знает, что этот ресурс приложению необходим, мы его об этом никак не известилиЧтобы меню появилось, этот ресурс нужно связать с каким-то диалоговым окном. Но поскольку обычно из всех диалоговых окон только для главного создают меню, то привяжем меню к главному окну (тем более, что у нас всего пока одно окно и есть, и то главное!).
В панели Resource View откройте дизайнер диалоговых окон для ресурса IDD_MENU_DIALOG
Выделите только саму форму (нам нужны только ее свойства) и в панели Properties в свойстве Menu установите идентификатор IDR_MENU только что созданного нами меню
Запустите приложение и убедитесь, что меню появилось, раскрывается, но ничего не делаетДля того, чтобы меню что-то делало, необходимо привязaть к его пунктам соответствующий обработчики. Для этого выполните следующие шаги:
Откройте через панель Resource View для нашего меню проектировщик Menu Designer
Выберите элемент меню с названием Hello
Щелкните на нем правой кнопкой мыши и из контекстного меню выберите пункт Add Event Handler (Добавить обработчик события)Появится диалоговое окно, которое нужно настроить как показано на рисунке

Мастер добавит следующие коды
Добавление прототипа в объявление класса CMenuDlg
// CMenuDlg dialog
class CMenuDlg : public CDialog
{
....................................
public:
afx_msg void OnBnClickedExit();
afx_msg void OnFileHello();
};
Регистрация обработчика в таблице сообщений класса CMenuDlg BEGIN_MESSAGE_MAP(CMenuDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_EXIT, OnBnClickedExit) ON_COMMAND(ID_FILE_HELLO, OnFileHello) END_MESSAGE_MAP()
Обработчик команды-сообщения, поступившей от
идентификатора ID_FILE_HELLO ресурса пункта меню, включенного в класс CMenuDlg
void CMenuDlg::OnFileHello()
{
// Здесь компилятор автоматически соединяет
// строковые константы в одну строку !!!
MessageBox("Это поле текста!\n"
"Выбрана пиктограмма MB_ICONEXCLAMATION\n"
"Для переноса строк используется\n"
"управляющий символ \\n - экранирует его \\\n\n"
"Привет студентам от Снеткова В.М.!!!"
, "Это заголовок окна"
, MB_OK | MB_ICONEXCLAMATION
// запятая спереди элементов списка позволяет
// удалить строку без синтаксических нарушений
);
}
Постройте приложение и выполните пункт меню Hello
Получится результат

Таким же образом создайте обработчик для опции About категории Help, настроив мастер так

При настройке мастера приложения мы отказались от создания окна About, поэтому остались без автоматически сгенерированного меню About, диалогового ресурса IDD_ABOUTBOX и поддерживающего класса CAboutDlg. Восполним этот пробел позже, а сейчас создадим заготовку кода обработчика.
Создайте обработчик и заполните его следующим кодомЗаготовка кода обработчика команды-сообщения,
Запоступившей от идентификатора ID_HELP_ABOUT ресурса пункта меню, включенного в класс CMenuDlg
void CMenuDlg::OnHelpAbout()
{
//CAboutBox dlgAbout; // Создаем на стеке локальный объект About с
// отредактированными заранее настройками ресурса
//dlgAbout.DoModal(); // Отображаем локальный About
}
Свяжите ранее созданный обработчик завершения приложения с пунктом меню
Заполните обработчик, включив в него вызов обработчика кнопки ExitОбработчик команды-сообщения, поступившей от идентификатора ID_FILE_EXIT ресурса пункта меню,
включенного в класс CMenuDlg
void CMenuDlg::OnFileExit()
{
OnBnClickedExit(); // Вызов обработчика кнопки
}
Такой прием вызова одного обработчика через другой не всегда хорош, поскольку сильно связывает код. Если программист захочет избавиться от вызывающего обработчика, ему понадобятся дополнительные действия.
Восполним пробел того, что мы не указали создание диалогового окна AboutBox в настройках мастера. Для этого используем материал, рассмотренный в лабораторной работе " Lab7 - Dialogs ".
Выберите вкладку Resource View, выделите узел Dialogs и через контекстное меню или главное меню оболочки выполните опцию Add Resourcе
Появится окно Add Resource

Выделите в нем пиктограмму Dialog и щелкните на кнопке New. Оболочка создаст заготовку диалогового окна
Присвойте идентификатору ресурса окна имя IDD_ABOUTBOX
Удалите из заготовки кнопку Cancel. Измените сгенерированный заголовок окна " Dialog " на "Сведения об авторах"
Добавьте ресурс Icon через окно Add Resource, но только иконку загрузите внешнюю, нажав клавишу Import
Откроется окно

в котором настройте фильтр на *.ico и загрузите нужную иконку. Измените идентификатор ресурса иконки на IDI_ICONABOUT в панели свойств, предварительно выделив ресурс. Удостоверьтесь, что файл с иконкой оболочка скопировала к себе в каталог res приложения
Добавьте элемент управления Picture Control. Измените значение его свойства Type на Icon, а в свойстве Image выберите IDI_ICONABOUT. Другая же иконка имеет стандартное имя IDR_MAINFRAME, которое закреплено за иконкой приложения
Наполните заготовку краткими сведениями о приложении (лаб. работа №8 и т.д.) и его разработчиках, используя элементы управления Static Text. При редактировании диалогового ресурса периодически проверяйте дизайн кнопкой Test Dialog панели инструментов Dialog Editor. Размеры элементов управления контролируйте через панель состояния дизайнера. Для выравнивания элементов пользуйтесь инструментами на панели инструментов или опциями меню Format оболочкиРесурс должен выглядеть примерно так
Выберите только что сконструированное окно в режиме дизайна и через контекстное меню (или главное меню
Подключите вначале файла MenuDlg.h заголовочный файл AboutBox.hПодключение сведений о классе CAboutBox в файл описания класса CMenuDlg
// MenuDlg.h : header file
//
#pragma once
#include "AboutBox.h"
// CMenuDlg dialog
class CMenuDlg : public CDialog
{
.....................................
};
Раскомментируйте обработчик About главного меню приложенияОбработчик команды-сообщения, поступившей от идентификатора mID_HELP_ABOUT ресурса пункта меню,
включенного в класс CMenuDlg
void CMenuDlg::OnHelpAbout()
{
CAboutBox dlgAbout; // Создаем на стеке локальный объект About с
// отредактированными заранее настройками ресурса
dlgAbout.DoModal(); // Отображаем локальный About
}
Запустите приложение и убедитесь в правильности выполненной работы
Обратите внимание, что объект dlgAbout создан локально на стеке внутри обработчика и не будет виден из других функций приложения. Измените, код обработчика так, чтобы объект диалогового окна About создавался на куче всякий раз при входе в обработчик и уничтожался при выходе из негоСоздание объекта About динамически на куче
void CMenuDlg::OnHelpAbout()
{
// CAboutBox dlgAbout; // Создаем на стеке локальный объект About с
// отредактированными заранее настройками ресурса
// dlgAbout.DoModal(); // Отображаем локальный About
CAboutBox *dlgAbout; // Создаем на стеке локальный указатель на объект About
dlgAbout = new CAboutBox; // Создаем на куче объект
dlgAbout->DoModal(); // Отображаем объект About
delete dlgAbout; // Уничтожаем объект
}
Постройте приложение и убедитесь, что все работаетДля создания меню динамическим способом, или управлением уже существующим меню, предназначен MFC -класс CMenu. Если в каком-то из окон приложения меню уже существует, то получить указатель на этот объект можно с помощью унаследованного этим окном от класса CWnd метода CWnd::GetMenu() с помощью кода
CMenu *ptrMenu = GetMenu();
Этот указатель ptrMenu на созданное приложением меню должен быть всегда локальным в пределах функции, чтобы дать возможность системе самой освободить ею же созданный объект меню. После того, как указатель на существующее меню окна получен, можно получить указатели на отдельные подменю (категории) этого меню уже с помощью метода класса CMenu, например
CMenu *ptrSubMenu = ptrMenu->GetSubMenu(nPos); где nPos (int nPos) - индекс категории всего мен
Единственным параметром этой функции является целое , которое означает индекс подменю первого уровня. Нумерация индексов начинается с нуля.
Приведем список наиболее часто используемый функций класса CMenu
Наиболее часто используемые функции класса Cmenu | |
|---|---|
| Функция | Описание |
AppendMenu() |
Добавляет новый пункт в конец уже существующего или нового всплывающего меню. В передаваемых аргументах указывается, создавать новый пункт в категориях или добавлять новый пункт в раскрывающийся список опций |
CheckMenuItem() |
Устанавливает или сбрасывает флажок пункта меню (используется редко). Обычно вместо этой функции используют обработчик сообщений UPDATE_COMMAND_UI. |
GetSubMenu() |
Возвращает указатель на подменю по индексу категории (индексируется с нуля) |
GetMenuItemID() |
Запрашивает идентификатор команды пункта меню. |
GetMenuString() |
Запрашивает текст метки пункта меню (внешнее наименование пункта) |
EnableMenuItem() |
Блокирует и разблокирует пункты меню. Обычно вместо этой функции используется обработчик сообщения UPDATE_COMMAND_UI. |
InsertMenu() |
Вставляет новый пункт в определенную позицию уже существующего меню. |
RemoveMenu() |
Удаляет определенный пункт меню. |
DestroyMenu() |
Удаляет из памяти динамически созданный на куче объект меню. |
TrackPopupMenu() |
Выводит соответствующий раскрывающийся список опций как контекстное меню. |
Контекстное меню отображается в том случае, когда пользователь, выбрав объект, щелкает на нем правой кнопкой мыши. Именно за правой кнопкой мыши Microsoft рекомендует закреплять вызов контекстного меню. При клавиатурном вызове для новых 104-клавишных клавиатур типа Microsoft Natural keyboard контекстное меню вызывается закрепленной за этим клавишей " Applications " (на этой клавише, расположенной внизу перед правой клавишей Ctrl, изображен курсор мыши поверх открытого контекстного меню). На случай, если у пользователя не окажется такой клавиши, клавиатурный вызов контекстного меню следует определить hift+F10 ".
Обработчик для вызова контекстного меню можно закрепить за любым из двух сообщений Windows:
WM_RBUTTONDOWN - генерируется системой при нажатии правой кнопки мыши.WM_CONTEXTMENU - генерируется системой при отпускании правой кнопки мыши или нажатии клавиатурной кнопки " Applications " вызова контекстного меню, которая есть на новых клавиатурах Windows.Контекстные меню могут ссылаться на раскрывающиеся списки опций существующего меню или создаваться динамически, загружая нужный ресурс из заранее созданных меню.
Для испытания механизма вызова контекстного меню разделим клиентскую область на четыре отдельные зоны, за каждой из которых закрепим вызов своего контекстного меню.
Чтобы все шло согласованно, установите размер главного диалогового окна приложения 320х210 пикселовСоздадим функцию, которая нарисует на форме один цветной прямоугольник, имитирующий контекстную зону. Для этого:
Выделите в панели Для окончательного ввода аргументов не забывайте каждый раз нажимать кнопку Add мастера.
Мастер создаст объявление и пустую заготовку определения нашей функции
Объявление функции ShowRectangle()
// CMenuDlg dialog
class CMenuDlg : public CDialog
{
....................................
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClickedExit();
afx_msg void OnFileHello();
afx_msg void OnHelpAbout();
afx_msg void OnFileExit();
private:
// Рисуем прямоугольник на контексте
void ShowRectangle(int * color, CRect rect);
};
Заготовка определения функции ShowRectangle()
// Рисуем прямоугольник на контексте
void CMenuDlg::ShowRectangle(int * color, CRect rect)
{
}
Через аргументы функции будем передавать цвет кисти для заливки прямоугольника и координаты места рисования прямоугольника на контексте формы.
Заполните код функцииФункция ShowRectangle()
// Рисуем прямоугольник на контексте
void CMenuDlg::ShowRectangle(int * color, CRect rect)
{
// для мнемонического смысла
const int RED = color[0], GREEN = color[1], BLUE = color[2];
// Содать контекст для текущего окна
CClientDC dc(this);
// Создать новое перо
CPen MyNewPen;
MyNewPen.CreatePen(PS_SOLID, 1, RGB(RED, GREEN, BLUE));
CPen *pOriginalPen = dc.SelectObject(MyNewPen);
// Новая кисть
CBrush brush(HS_DIAGCROSS, RGB(RED, GREEN, BLUE));
dc.SelectObject(brush);
// Нарисовать прямоугольник
dc.Rectangle(rect);
// Вернуть установку первоначального пера
dc.SelectObject(pOriginalPen);
// Отсоединить кисть
dc.SelectStockObject(GRAY_BRUSH);
}
Создайте вручную без использования мастера массив из четырeх переменных класса CRect для хранения координат прямоугольниковОни нам понадобятся в обработчике сообщения WM_CONTEXTMENU для того, чтобы определять, в область какого прямоугольника мы попали и в зависимости от этого вызывать соответствующее меню.
Поместите создание массива в описание класса CMenuDlg в раздел private, чтобы он был виден во всех функциях-членах класса
Получится код
Массив для хранения координат прямоугольников
class CMenuDlg : public CDialog
{
.............................................
private:
// Рисуем прямоугольник на контексте
void ShowRectangle(int * color, CRect rect);
// Переменные для хранения координат прямоугольников
// в порядке слева-направо сверху-вниз
CRect rectCoord[4];
};
Создайте еще одну функцию, которая будет рисовать четыре разноцветных прямоугольника, имитирующих разные зоны чувствительности экрана
Заполните код функцииКод функции рисования всех прямоугольников
// Рисует все прямоугольники
void CMenuDlg::ShowAll(void)
{
// Рисуем прямоугольники
const int BORDER = 8, PODVAL = 50; // Отступ от краев в пикселах
CRect clientRect, currentRect;
// Определяем координаты клиентской области главного окна
GetClientRect(clientRect);
int width = (clientRect.Width() - 2 * BORDER) / 2; // ширина прямоугольника
int height = (clientRect.Height() - BORDER - PODVAL) / 2; // высота
int x1, y1; // Левый верний угол
int x2, y2; // Правый нижний угол
x1 = y1 = BORDER; x2 = x1 + width; y2 = y1 + height;
currentRect.SetRect(x1, y1, x2, y2);
rectCoord[0] = currentRect;// Сохраним координаты первого прямоугольника
int color[] = {255, 0, 0}; // Цвет первого прямоугольника
ShowRectangle(color, currentRect); // рисуем первый прямоугольник
x1 = x2+1; x2 = x1 + width; // y1, y2 - прежние
currentRect.SetRect(x1, y1, x2, y2);
rectCoord[1] = currentRect; // Сохраним координаты второго прямоугольника
color[0] = 0; color[1] = 255; // color[2] - прежний
ShowRectangle(color, currentRect); // рисуем второй прямоугольник
x1 = BORDER; x2 = x1 + width; y1 = y2+1; y2 = y1 + height;
currentRect.SetRect(x1, y1, x2, y2);
rectCoord[2] = currentRect; // Сохраним координаты третьего прямоугольника
color[1] = 0; color[2] = 255; // color[0] - прежний
ShowRectangle(color, currentRect); // рисуем третий прямоугольник
x1 = x2+1; x2 = x1 + width; // y1, y2 - прежние
currentRect.SetRect(x1, y1, x2, y2);
rectCoord[3] = currentRect;// Сохраним координаты четвертого прямоугольника
color[2] = 0; // color[0], color[1] - прежние
ShowRectangle(color, currentRect); // рисуем четвертый прямоугольник
}
Поместите вызов функции в автоматически вызываемую функцию перерисовки экрана, которая является обработчиком сообщения WM_PAINT операционной системы
Это сообщение посылается системой активному приложению всякий раз, когда экран разрушается и требует перерисовки.
Вызов функции перерисовки прямоугольников в обработчике OnPaint()
void CMenuDlg::OnPaint()
{
ShowAll(); // рисовать прямоугольники
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0);
.................................
}
else
{
CDialog::OnPaint();
}
}
Запустите приложение и должно получиться примерно следующее
Создадим обработчик сообщения WM_CONTEXTMENU. Для этого:
Выделите класс диалогового окна, которое будет принимать и обрабатывать это сообщение
В нашем случае это главное диалоговое окно приложения, имеющее ресурс IDD_MENU_DIALOG и поддерживающий этот ресурс класс CMenuDlg.
В панели Properties установите режим Messages и найдите интересующее нас сообщение WM_CONTEXTMENU. Через находящееся справа поле добавьте обработчик
Оболочка сгенерирует код в нескольких местах приложения
Объявление обработчика в классе CMenuDlg
class CMenuDlg : public CDialog
{
....................................................
CRect rectCoord[4];
// Рисует все прямоугольники
void ShowAll(void);
public:
afx_msg void OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/);
};
Заготовка определения обработчика в классе CMenuDlg
void CMenuDlg::OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/)
{
}
Регистрация сообщения в таблице сообщений класса CMenuDlg BEGIN_MESSAGE_MAP(CMenuDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_EXIT, OnBnClickedExit) ON_COMMAND(ID_FILE_HELLO, OnFileHello) ON_COMMAND(ID_HELP_ABOUT, OnHelpAbout) ON_COMMAND(ID_FILE_EXIT, OnFileExit) ON_WM_CONTEXTMENU() END_MESSAGE_MAP()
Наша задача на данном этапе - организовать и проверить в принципе работоспособность обработчика. А дальше мы настроим его на вызов контекстных меню. Для этого:
Разблокируйте аргументы и заполните обработчик отладочным кодомОтладочный код обработчика сообщения WM_CONTEXTMENU
void CMenuDlg::OnContextMenu(CWnd* pWnd, CPoint point)
{
CPoint myPoint = point; // сохраним, чтобы не портить основные
ScreenToClient(myPoint); // переведем экранные координаты
// курсора в клиентские для окна
int numRect = 0; // Номер прямоугольника
for(int i = 0; i < 4; i++){
if(rectCoord[i].PtInRect(myPoint)){
// Точка внутри прямоугольника
numRect = i + 1;
break;
}
}
if(numRect){
char str[2]; // Для цифра и '\0'
itoa(numRect, str, 10); // Преобразование цифры в строку
CString string = "Попали в прямоугольник ";
string += str;
MessageBox(string);
}
else{
MessageBeep((WORD) - 1); // Звукнуть
MessageBox("Никуда не попали");
}
}
Постройте приложение, пощелкайте правой кнопкой мыши и убедитесь, что все работает как задумано
Программа всегда должна работать как задумано. Если она работает хуже, чем задумано, - это плохая программа; если она работает лучше, чем задумано, - это еще более плохая программа, она просто опасна (как улыбающийся тайный враг!).
Построение отладочных кодов (что мы здесь чуть-чуть и попробовали) - типичный, иногда просто необходимый прием программирования. Иногда построение испытательных тестов стоит дороже, чем сама испытуемая программа.
Мы хотим, чтобы при правом щелчке на соответствующих областях чувствительности вызывались разные всплывающие контекстные меню. Прежде, чем писать обработчик вызова меню, сами меню нужно заготовить как ресурсы приложения.
Для разнообразия, в двух верхних прямоугольниках не будем создавать новые ресурсы, а возьмем уже готовый ресурс главного меню. А для двух нижних прямоугольников создадим самостоятельные ресурсы меню. Для этого выполните следующее:
Откройте панель Resource View и на любом ресурсе или узле дерева ресурсов приложения вызовите окно Add Resource

и нажмите кнопку New. Измените имя созданного ресурса на внятное IDR_MENUCONTEX3
Аналогичным образом создайте ресурс IDR_MENUCONTEX4
Это будут заготовки для третьей и четвертой областей чувствительности.
Отредактируйте оба ресурса как показано на рисунках IDR_MENUCONTEX3 |
IDR_MENUCONTEX4 |
|---|---|
![]() |
![]() |
Измените код обработчика WM_CONTEXTMENU на следующийОкончательный код обработчика сообщения WM_CONTEXTMENU
void CMenuDlg::OnContextMenu(CWnd* pWnd, CPoint point)
{
CPoint myPoint = point; // сохраним, чтобы не портить основные
ScreenToClient(myPoint); // переведем экранные координаты
// курсора в клиентские для окна
int numRect = 0; // Номер прямоугольника
for(int i = 0; i < 4; i++){
if(rectCoord[i].PtInRect(myPoint)){
// Точка внутри прямоугольника
numRect = i + 1;
break;
}
}
CMenu *pMainMenu, *pContextMenu, *pHeap = NULL; // Объявить указатели на меню
pMainMenu = GetMenu(); // Получить указатель на существующее
// главное меню IDR_MENU
switch(numRect){ // Куда попали, что делать
case 1:
pContextMenu = pMainMenu->GetSubMenu(0);// Первое подменю IDR_MENU
break;
case 2:
pContextMenu = pMainMenu->GetSubMenu(1);// Второе подменю IDR_MENU
break;
case 3:
pHeap = new CMenu(); // Создать экземпляр на куче
pHeap->LoadMenu(IDR_MENUCONTEX3); // Закачать ресурс
pContextMenu = pHeap->GetSubMenu(0);// Выбрать уровень подменю
break;
case 4:
pHeap = new CMenu(); // Создать экземпляр на куче
pHeap->LoadMenu(IDR_MENUCONTEX4); // Закачать ресурс
pContextMenu = pHeap->GetSubMenu(0);// Выбрать уровень подменю
break;
}
if(!numRect){ // Промазали
MessageBeep((WORD) - 1); // Звукнуть
MessageBox("Никуда не попали");
}
else // Отобразить меню как контекстное в расположении курсора мыши
pContextMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON
,point.x
,point.y
,pWnd
,NULL);
if(pHeap){
pHeap->DestroyMenu(); // Вернуть память операционной системе
pHeap = NULL; // Поставить затычку на всякий случай
}
}
Функция TrackPopupMenu() имеет пять параметров, пятый параметр по умолчанию равен нулю, но мы его поставили (просто так!).
TPM_CENTERALIGN, TPM_LEFTALIGN, TPM_RIGHTALIGN) и (TPM_LEFTBUTTON, TPM_RIGHTBUTTON ). Первый флажок позиционирует верхний край всплывающего меню на казатель мыши. Второй флажок задает, какой кнопкой мыши будут выбираться темы во всплывающем меню. Мы задали, что меню у указателя мыши будет появляться верхним левым углом, а выбор темы выполнится по левой кнопке.PtInRect() ).this ).NULL и мы его могли бы вообще не указывать (просто так указали, от нечего делать). При таком пустом значении установлено, что если пользователь щелкнет кнопкой мыши вне поля всплывающего меню, оно закроется.
Постройте приложение и убедитесь, что все работает как надоДобавим тестовый код в приложение, показывающий, что темы действительно выбираются. Ранее мы закрепляли обработчики - за каждой темой свой отдельный обработчик. Давайте теперь для всех тем двух новых ресурсов IDR_MENUCONTEX3 и IDR_MENUCONTEX4 закрепим один обработчик. При этом почти все изменения придется выполнить вручную, потому, что мастер способен создавать только отдельные обработчики.
Выполните следующие пошаговые инструкции:
Создайте обработчик для первой темы контекстного меню
Настройте окно мастера так

Мастер добавит в приложение код в трех местах:
Объявление обработчика в классе CMenuDlg
class CMenuDlg : public CDialog
{
....................................................
CRect rectCoord[4];
// Рисует все прямоугольники
void ShowAll(void);
public:
afx_msg void OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/);
afx_msg void OnGroupMenu();
};
Заготовка определения обработчика в классе CMenuDlg
class CMenuDlg : public CDialog
{
....................................................
CRect rectCoord[4];
// Рисует все прямоугольники
void ShowAll(void);
public:
afx_msg void OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/);
afx_msg void OnGroupMenu();
};
Регистрация сообщения в таблице сообщений класса CMenuDlg BEGIN_MESSAGE_MAP(CMenuDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_EXIT, OnBnClickedExit) ON_COMMAND(ID_FILE_HELLO, OnFileHello) ON_COMMAND(ID_HELP_ABOUT, OnHelpAbout) ON_COMMAND(ID_FILE_EXIT, OnFileExit) ON_WM_CONTEXTMENU() ON_COMMAND(ID_32781, OnGroupMenu) END_MESSAGE_MAP()
В таблицу сообщений мастер поместил цифровой идентификатор пункта темы , потому что мы сразу задавали наименования пунктов по-русски.
Включите редактор меню в режим
IDR_MENUCONTEX3 IDR_MENUCONTEX4 

Реконструируйте таблицу сообщений так, чтобы все идентификаторы ссылались на один и тот же обработчикМодификация таблицы сообщений класса CMenuDlg вручную BEGIN_MESSAGE_MAP(CMenuDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_EXIT, OnBnClickedExit) ON_COMMAND(ID_FILE_HELLO, OnFileHello) ON_COMMAND(ID_HELP_ABOUT, OnHelpAbout) ON_COMMAND(ID_FILE_EXIT, OnFileExit) ON_WM_CONTEXTMENU() // IDR_MENUCONTEX3 ON_COMMAND(ID_32781, OnGroupMenu) // Высоцкий ON_COMMAND(ID_32782, OnGroupMenu) // Лещенко ON_COMMAND(ID_32783, OnGroupMenu) // Пахмутова ON_COMMAND(ID_32784, OnGroupMenu) // Ротару ON_COMMAND(ID_32785, OnGroupMenu) // Цой-ой // IDR_MENUCONTEX4 ON_COMMAND(ID_32786, OnGroupMenu) // Яблочко ON_COMMAND(ID_32787, OnGroupMenu) // Семечко ON_COMMAND(ID_32788, OnGroupMenu) // Коробочка ON_COMMAND(ID_32789, OnGroupMenu) // Танго ON_COMMAND(ID_32790, OnGroupMenu) // Вальс ON_COMMAND(ID_32791, OnGroupMenu) // Буги-Вуги ON_COMMAND(ID_32792, OnGroupMenu) // Летка-Енька END_MESSAGE_MAP()
Добавьте код обработчикаКод обработчика OnGroupMenu()
void CMenuDlg::OnGroupMenu()
{
MessageBox("Выбрали пункт контекстного меню");
}
Постройте приложение и убедитесь, что все работаетПунктам меню можно назначить командные клавиши (String Table. Каждый элемент таблицы содержит идентификатор объекта и комбинацию клавиш. Комбинации клавиш должны быть уникальны в пределах всего приложения.
После определения акселераторов можно связать какую-либо функцию с идентификатором объекта. Можно дать записям командных клавиш тот же самый идентификатор, что и у соответствующего пункта меню. После определения всех сочетаний клавиш нужно добавить эти сочетания в меню, чтобы пользователи знали о возможности быстрого вызова.
К сожалению MFC, поскольку предполагается, что диалоговые окна не должны иметь меню. Поэтому только потренируемся добавлять информацию о горячих клавишах в меню. Для этого дополните строки меню своими комбинациями, отделив их от названий символом табуляции \t.
Постройте приложение, проверьте его работоспособность и сдайте работу преподавателю
Проектировщики компьютерных программ изобрели графический интерфейс пользователя ( GUI - ), чтобы облегчить взаимодействие с компьютерными системами и приложениями. Важной частью этого интерфейса является меню.
Хотя никаких стандартов разработки меню не существует, разработчиками приложений при проектировании меню накоплен богатый опыт, позволяющий облегчить работу будущего пользователя. Его необходимо придерживаться, чтобы не вынуждать потенциального пользователя нашего приложения срочно получать второе высшее только для того, чтобы правильно воспользоваться произведенным детищем.
Различные составляющие приложений Windows, включая макет диалогового окна, меню, панели инструментов, изображения, текстовые строки, командные клавиши, управляющие элементы и т.д. рассматриваются как ресурсы. Проектировщик приложения на визуальном уровне указывает среде IDE содержимое текстов, размеры, идентификаторы ресурсов, типы ресурсов, а среда в это время заносит эти сведения в файл ресурсов на специальном языке - описания ресурсов. Ресурсы - это как видимые фишки в казино взамен пока еще невидимых денег. В приложениях ресурсы превращаются в объекты только после их компиляции.
Меню можно создавать заранее, как ресурс, с помощью редактора меню и такие меню называются статическими. Но чаще всего необходимо в приложении обеспечить управление составом меню в зависимости от состояния приложения. Некоторые пункты меню нужно скрыть, некоторые новые - показать, некоторые заблокировать или разблокировать. Такие действия позволяет выполнить MFC -класс CMenu. Более того, с помощью этого класса можно вообще программно создать меню с нуля, задав значения соответствующих свойств класса. Такая технология создания меню программно, без использования ресурсов, называется динамическим меню.
Однако удобнее использовать комбинированный подход: заранее создать необходимый набор ресурсов статическим способом, затем по мере необходимости подгружать в работающее приложение тот или иной ресурс программно, управляя его составом нужным образом. В нашей лабораторной работе технологию динамической загрузки и программного управления меню мы применим ниже применительно к контекстным меню.
Создайте новый проект
Application Type Application Type Dialog Based Resource language:Project style:MFC standard Use of MFC:Use MFC in a static library User Interface Features Main frame styles:System menu Dialog title:MenuAdvanced Features
После создания мастером формы удалите из диалога все элементы управления
Русифицируйте ресурс диалогового окна с идентификатором IDD_MENU_DIALOG
Измените заголовок диалогового окна, указав фамилию, номер и название лабораторной работыЗаменим иконку приложения на любую иную по выбору. Для этого сделайте следующее:
Найдите в панели Resource View узел Icon. В нем хранится ресурс с идентификатором IDR_MAINFRAME. Это стандартное имя для иконки приложения
Вызовите для узла Icon контекстное меню (или выделите его и через категорию Project главного меню) выполните команду Add Resource
В появившемся диалоговом окне выполните кнопку Import
Настройте фильтр на расширение *.ico, перейдите в каталог Icons с готовыми иконками и загрузите ресурс новой иконки
Закройте панель с графическим редактором иконок, удалите ресурс IDR_MAINFRAME, а сгенерированный идентификатор IDI_ICON1 новой иконки переименуйте в IDR_MAINFRAME
Полностью перестройте приложение ( Build/Rebuild Solution ) и убедитесь, что новая иконка появиласьУбедитесь, также, в том, что файл .ico оболочка скопировала в каталог res проекта.
Добавьте к приложению одну кнопку Exit (Выход) с идентификатором ресурса IDC_EXIT и поместите в ее обработчик вместо традиционного OnOK() следующий кодОбработчик кнопки завершения приложения
void CMenuDlg::OnBnClickedExit()
{
PostMessage(WM_QUIT); // Посылаем сообщение напрямую
}
После того, как базовая заготовка создана мастером, нужно добавить и настроить в нее меню.
Откройте панель Resource View, выделите верхний узел дерева ресурсов, идентичный имени проекта, в контекстном меню (или главном меню Project оболочки) выполните опцию Add Resource (Добавить ресурс)
В диалоговом окне Add Resource дважды щелкните по ресурсу Menu
В области редактирования оболочки запустится дизайнер ресурса меню. Первое место в меню подсвечено.

Меню готово для проектирования. При задании пунктов меню вводится текст, который увидят пользователи приложения. Никакие свойства пункта не будут доступны, пока не ввести название пункта в соответствующую позицию (панель Properties будет пустой). Элементы строки меню верхнего уровня (категории) не выполняют никаких функций, кроме как раскрываются, поэтому им не нужно присваивать идентификаторы ресурсов (это же справедливо и для сепараторов).
Пункты меню можно вводить сразу в подсвеченную синим маркером область или вначале активизировать поле редактирования пункта. Горизонтальные разделители ( сепараторы ) вставляются клавишей "минус". По пунктам меню дизайнера можно передвигаться стрелками. При выходе за пределы крайних пунктов автоматически создается пустой шаблон нового пункта, который в окончательном варианте не участвует. Ненужные пункты можно удалить клавишей Delete.
Дизайнер меню поддерживается и главным меню оболочки и своим контекстным меню. Откаты редактирования дизайнер поддерживает не все, поэтому если произвести некоторое ошибочное действие, то восстанавливать первоначальный вариант нужно вручную.
Дизайнер меню автоматически формирует идентификатор ресурса пункта меню по его названию. Идентификаторы должны состоять из знаков подчеркивания, цифр и букв латинского алфавита. Если пункты меню вводить сразу на русском, то идентификаторы дизайнер будет генерировать из цифр, далеких от мнемонического смысла закрепленной за пунктом задачи. Поэтому возможны два варианта:
Сразу вчерне присвоить пунктам названия по-английски для автоматической генерации понятных по смыслу имен идентификаторов, а затем все переписать по-русски. Здесь следует учесть, что автоматическая генерация идентификатора пункта происходит только один раз при первом заполнении названия пункта. При всех последующих изменениях названия первоначально сгенерированное имя идентификатора автоматически уже не изменяется (ну и ладно, изменим вручную).
Писать названия пунктов сразу по-русски, затем редактировать идентификаторы ресурсов этих пунктов. Редактировать идентификаторы и названия пунктов после их создания можно через панель Properties или прямо в дизайнере меню, включив или выключив режим Edit IDs (Редактировать идентификаторы).
Разработайте ресурс меню приложения в соответствии с таблицей| Схема ресурса меню | |||
|---|---|---|---|
ID |
Название | ID |
Название |
| Пусто | File |
Пусто | Help |
ID_FILE_HELLO |
Hello |
ID_HELP_ABOUT |
About |
| Пусто | ---------Separator--------- |
||
ID_FILE_EXIT |
Exit |
||
Исправьте идентификатор ресурса меню с IDR_MENU1 на IDR_MENU. Для этого выделите в панели Resource View ресурс IDR_MENU1 и в панели Properties внесите изменения
Таким образом мы разработали ресурс меню, который дизайнер по визуальному представлению описал в файле ресурсов Menu.rc
Запустите приложение и убедитесь, что данное меню в окне приложения не появилось. Компилятор не знает, что этот ресурс приложению необходим, мы его об этом никак не известилиЧтобы меню появилось, этот ресурс нужно связать с каким-то диалоговым окном. Но поскольку обычно из всех диалоговых окон только для главного создают меню, то привяжем меню к главному окну (тем более, что у нас всего пока одно окно и есть, и то главное!).
В панели Resource View откройте дизайнер диалоговых окон для ресурса IDD_MENU_DIALOG
Выделите только саму форму (нам нужны только ее свойства) и в панели Properties в свойстве Menu установите идентификатор IDR_MENU только что созданного нами меню
Запустите приложение и убедитесь, что меню появилось, раскрывается, но ничего не делаетДля того, чтобы меню что-то делало, необходимо привязaть к его пунктам соответствующий обработчики. Для этого выполните следующие шаги:
Откройте через панель Resource View для нашего меню проектировщик Menu Designer
Выберите элемент меню с названием Hello
Щелкните на нем правой кнопкой мыши и из контекстного меню выберите пункт Add Event Handler (Добавить обработчик события)Появится диалоговое окно, которое нужно настроить как показано на рисунке

Мастер добавит следующие коды
Добавление прототипа в объявление класса CMenuDlg
// CMenuDlg dialog
class CMenuDlg : public CDialog
{
....................................
public:
afx_msg void OnBnClickedExit();
afx_msg void OnFileHello();
};
Регистрация обработчика в таблице сообщений класса CMenuDlg BEGIN_MESSAGE_MAP(CMenuDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_EXIT, OnBnClickedExit) ON_COMMAND(ID_FILE_HELLO, OnFileHello) END_MESSAGE_MAP()
Обработчик команды-сообщения, поступившей от
идентификатора ID_FILE_HELLO ресурса пункта меню, включенного в класс CMenuDlg
void CMenuDlg::OnFileHello()
{
// Здесь компилятор автоматически соединяет
// строковые константы в одну строку !!!
MessageBox("Это поле текста!\n"
"Выбрана пиктограмма MB_ICONEXCLAMATION\n"
"Для переноса строк используется\n"
"управляющий символ \\n - экранирует его \\\n\n"
"Привет студентам от Снеткова В.М.!!!"
, "Это заголовок окна"
, MB_OK | MB_ICONEXCLAMATION
// запятая спереди элементов списка позволяет
// удалить строку без синтаксических нарушений
);
}
Постройте приложение и выполните пункт меню Hello
Получится результат

Таким же образом создайте обработчик для опции About категории Help, настроив мастер так

При настройке мастера приложения мы отказались от создания окна About, поэтому остались без автоматически сгенерированного меню About, диалогового ресурса IDD_ABOUTBOX и поддерживающего класса CAboutDlg. Восполним этот пробел позже, а сейчас создадим заготовку кода обработчика.
Создайте обработчик и заполните его следующим кодомЗаготовка кода обработчика команды-сообщения,
Запоступившей от идентификатора ID_HELP_ABOUT ресурса пункта меню, включенного в класс CMenuDlg
void CMenuDlg::OnHelpAbout()
{
//CAboutBox dlgAbout; // Создаем на стеке локальный объект About с
// отредактированными заранее настройками ресурса
//dlgAbout.DoModal(); // Отображаем локальный About
}
Свяжите ранее созданный обработчик завершения приложения с пунктом меню
Заполните обработчик, включив в него вызов обработчика кнопки ExitОбработчик команды-сообщения, поступившей от идентификатора ID_FILE_EXIT ресурса пункта меню,
включенного в класс CMenuDlg
void CMenuDlg::OnFileExit()
{
OnBnClickedExit(); // Вызов обработчика кнопки
}
Такой прием вызова одного обработчика через другой не всегда хорош, поскольку сильно связывает код. Если программист захочет избавиться от вызывающего обработчика, ему понадобятся дополнительные действия.
Восполним пробел того, что мы не указали создание диалогового окна AboutBox в настройках мастера. Для этого используем материал, рассмотренный в лабораторной работе " Lab7 - Dialogs ".
Выберите вкладку Resource View, выделите узел Dialogs и через контекстное меню или главное меню оболочки выполните опцию Add Resourcе
Появится окно Add Resource

Выделите в нем пиктограмму Dialog и щелкните на кнопке New. Оболочка создаст заготовку диалогового окна
Присвойте идентификатору ресурса окна имя IDD_ABOUTBOX
Удалите из заготовки кнопку Cancel. Измените сгенерированный заголовок окна " Dialog " на "Сведения об авторах"
Добавьте ресурс Icon через окно Add Resource, но только иконку загрузите внешнюю, нажав клавишу Import
Откроется окно

в котором настройте фильтр на *.ico и загрузите нужную иконку. Измените идентификатор ресурса иконки на IDI_ICONABOUT в панели свойств, предварительно выделив ресурс. Удостоверьтесь, что файл с иконкой оболочка скопировала к себе в каталог res приложения
Добавьте элемент управления Picture Control. Измените значение его свойства Type на Icon, а в свойстве Image выберите IDI_ICONABOUT. Другая же иконка имеет стандартное имя IDR_MAINFRAME, которое закреплено за иконкой приложения
Наполните заготовку краткими сведениями о приложении (лаб. работа №8 и т.д.) и его разработчиках, используя элементы управления Static Text. При редактировании диалогового ресурса периодически проверяйте дизайн кнопкой Test Dialog панели инструментов Dialog Editor. Размеры элементов управления контролируйте через панель состояния дизайнера. Для выравнивания элементов пользуйтесь инструментами на панели инструментов или опциями меню Format оболочкиРесурс должен выглядеть примерно так
Выберите только что сконструированное окно в режиме дизайна и через контекстное меню (или главное меню
Подключите вначале файла MenuDlg.h заголовочный файл AboutBox.hПодключение сведений о классе CAboutBox в файл описания класса CMenuDlg
// MenuDlg.h : header file
//
#pragma once
#include "AboutBox.h"
// CMenuDlg dialog
class CMenuDlg : public CDialog
{
.....................................
};
Раскомментируйте обработчик About главного меню приложенияОбработчик команды-сообщения, поступившей от идентификатора mID_HELP_ABOUT ресурса пункта меню,
включенного в класс CMenuDlg
void CMenuDlg::OnHelpAbout()
{
CAboutBox dlgAbout; // Создаем на стеке локальный объект About с
// отредактированными заранее настройками ресурса
dlgAbout.DoModal(); // Отображаем локальный About
}
Запустите приложение и убедитесь в правильности выполненной работы
Обратите внимание, что объект dlgAbout создан локально на стеке внутри обработчика и не будет виден из других функций приложения. Измените, код обработчика так, чтобы объект диалогового окна About создавался на куче всякий раз при входе в обработчик и уничтожался при выходе из негоСоздание объекта About динамически на куче
void CMenuDlg::OnHelpAbout()
{
// CAboutBox dlgAbout; // Создаем на стеке локальный объект About с
// отредактированными заранее настройками ресурса
// dlgAbout.DoModal(); // Отображаем локальный About
CAboutBox *dlgAbout; // Создаем на стеке локальный указатель на объект About
dlgAbout = new CAboutBox; // Создаем на куче объект
dlgAbout->DoModal(); // Отображаем объект About
delete dlgAbout; // Уничтожаем объект
}
Постройте приложение и убедитесь, что все работаетДля создания меню динамическим способом, или управлением уже существующим меню, предназначен MFC -класс CMenu. Если в каком-то из окон приложения меню уже существует, то получить указатель на этот объект можно с помощью унаследованного этим окном от класса CWnd метода CWnd::GetMenu() с помощью кода
CMenu *ptrMenu = GetMenu();
Этот указатель ptrMenu на созданное приложением меню должен быть всегда локальным в пределах функции, чтобы дать возможность системе самой освободить ею же созданный объект меню. После того, как указатель на существующее меню окна получен, можно получить указатели на отдельные подменю (категории) этого меню уже с помощью метода класса CMenu, например
CMenu *ptrSubMenu = ptrMenu->GetSubMenu(nPos); где nPos (int nPos) - индекс категории всего мен
Единственным параметром этой функции является целое , которое означает индекс подменю первого уровня. Нумерация индексов начинается с нуля.
Приведем список наиболее часто используемый функций класса CMenu
Наиболее часто используемые функции класса Cmenu | |
|---|---|
| Функция | Описание |
AppendMenu() |
Добавляет новый пункт в конец уже существующего или нового всплывающего меню. В передаваемых аргументах указывается, создавать новый пункт в категориях или добавлять новый пункт в раскрывающийся список опций |
CheckMenuItem() |
Устанавливает или сбрасывает флажок пункта меню (используется редко). Обычно вместо этой функции используют обработчик сообщений UPDATE_COMMAND_UI. |
GetSubMenu() |
Возвращает указатель на подменю по индексу категории (индексируется с нуля) |
GetMenuItemID() |
Запрашивает идентификатор команды пункта меню. |
GetMenuString() |
Запрашивает текст метки пункта меню (внешнее наименование пункта) |
EnableMenuItem() |
Блокирует и разблокирует пункты меню. Обычно вместо этой функции используется обработчик сообщения UPDATE_COMMAND_UI. |
InsertMenu() |
Вставляет новый пункт в определенную позицию уже существующего меню. |
RemoveMenu() |
Удаляет определенный пункт меню. |
DestroyMenu() |
Удаляет из памяти динамически созданный на куче объект меню. |
TrackPopupMenu() |
Выводит соответствующий раскрывающийся список опций как контекстное меню. |
Контекстное меню отображается в том случае, когда пользователь, выбрав объект, щелкает на нем правой кнопкой мыши. Именно за правой кнопкой мыши Microsoft рекомендует закреплять вызов контекстного меню. При клавиатурном вызове для новых 104-клавишных клавиатур типа Microsoft Natural keyboard контекстное меню вызывается закрепленной за этим клавишей " Applications " (на этой клавише, расположенной внизу перед правой клавишей Ctrl, изображен курсор мыши поверх открытого контекстного меню). На случай, если у пользователя не окажется такой клавиши, клавиатурный вызов контекстного меню следует определить hift+F10 ".
Обработчик для вызова контекстного меню можно закрепить за любым из двух сообщений Windows:
WM_RBUTTONDOWN - генерируется системой при нажатии правой кнопки мыши.WM_CONTEXTMENU - генерируется системой при отпускании правой кнопки мыши или нажатии клавиатурной кнопки " Applications " вызова контекстного меню, которая есть на новых клавиатурах Windows.Контекстные меню могут ссылаться на раскрывающиеся списки опций существующего меню или создаваться динамически, загружая нужный ресурс из заранее созданных меню.
Для испытания механизма вызова контекстного меню разделим клиентскую область на четыре отдельные зоны, за каждой из которых закрепим вызов своего контекстного меню.
Чтобы все шло согласованно, установите размер главного диалогового окна приложения 320х210 пикселовСоздадим функцию, которая нарисует на форме один цветной прямоугольник, имитирующий контекстную зону. Для этого:
Выделите в панели Для окончательного ввода аргументов не забывайте каждый раз нажимать кнопку Add мастера.
Мастер создаст объявление и пустую заготовку определения нашей функции
Объявление функции ShowRectangle()
// CMenuDlg dialog
class CMenuDlg : public CDialog
{
....................................
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClickedExit();
afx_msg void OnFileHello();
afx_msg void OnHelpAbout();
afx_msg void OnFileExit();
private:
// Рисуем прямоугольник на контексте
void ShowRectangle(int * color, CRect rect);
};
Заготовка определения функции ShowRectangle()
// Рисуем прямоугольник на контексте
void CMenuDlg::ShowRectangle(int * color, CRect rect)
{
}
Через аргументы функции будем передавать цвет кисти для заливки прямоугольника и координаты места рисования прямоугольника на контексте формы.
Заполните код функцииФункция ShowRectangle()
// Рисуем прямоугольник на контексте
void CMenuDlg::ShowRectangle(int * color, CRect rect)
{
// для мнемонического смысла
const int RED = color[0], GREEN = color[1], BLUE = color[2];
// Содать контекст для текущего окна
CClientDC dc(this);
// Создать новое перо
CPen MyNewPen;
MyNewPen.CreatePen(PS_SOLID, 1, RGB(RED, GREEN, BLUE));
CPen *pOriginalPen = dc.SelectObject(MyNewPen);
// Новая кисть
CBrush brush(HS_DIAGCROSS, RGB(RED, GREEN, BLUE));
dc.SelectObject(brush);
// Нарисовать прямоугольник
dc.Rectangle(rect);
// Вернуть установку первоначального пера
dc.SelectObject(pOriginalPen);
// Отсоединить кисть
dc.SelectStockObject(GRAY_BRUSH);
}
Создайте вручную без использования мастера массив из четырeх переменных класса CRect для хранения координат прямоугольниковОни нам понадобятся в обработчике сообщения WM_CONTEXTMENU для того, чтобы определять, в область какого прямоугольника мы попали и в зависимости от этого вызывать соответствующее меню.
Поместите создание массива в описание класса CMenuDlg в раздел private, чтобы он был виден во всех функциях-членах класса
Получится код
Массив для хранения координат прямоугольников
class CMenuDlg : public CDialog
{
.............................................
private:
// Рисуем прямоугольник на контексте
void ShowRectangle(int * color, CRect rect);
// Переменные для хранения координат прямоугольников
// в порядке слева-направо сверху-вниз
CRect rectCoord[4];
};
Создайте еще одну функцию, которая будет рисовать четыре разноцветных прямоугольника, имитирующих разные зоны чувствительности экрана
Заполните код функцииКод функции рисования всех прямоугольников
// Рисует все прямоугольники
void CMenuDlg::ShowAll(void)
{
// Рисуем прямоугольники
const int BORDER = 8, PODVAL = 50; // Отступ от краев в пикселах
CRect clientRect, currentRect;
// Определяем координаты клиентской области главного окна
GetClientRect(clientRect);
int width = (clientRect.Width() - 2 * BORDER) / 2; // ширина прямоугольника
int height = (clientRect.Height() - BORDER - PODVAL) / 2; // высота
int x1, y1; // Левый верний угол
int x2, y2; // Правый нижний угол
x1 = y1 = BORDER; x2 = x1 + width; y2 = y1 + height;
currentRect.SetRect(x1, y1, x2, y2);
rectCoord[0] = currentRect;// Сохраним координаты первого прямоугольника
int color[] = {255, 0, 0}; // Цвет первого прямоугольника
ShowRectangle(color, currentRect); // рисуем первый прямоугольник
x1 = x2+1; x2 = x1 + width; // y1, y2 - прежние
currentRect.SetRect(x1, y1, x2, y2);
rectCoord[1] = currentRect; // Сохраним координаты второго прямоугольника
color[0] = 0; color[1] = 255; // color[2] - прежний
ShowRectangle(color, currentRect); // рисуем второй прямоугольник
x1 = BORDER; x2 = x1 + width; y1 = y2+1; y2 = y1 + height;
currentRect.SetRect(x1, y1, x2, y2);
rectCoord[2] = currentRect; // Сохраним координаты третьего прямоугольника
color[1] = 0; color[2] = 255; // color[0] - прежний
ShowRectangle(color, currentRect); // рисуем третий прямоугольник
x1 = x2+1; x2 = x1 + width; // y1, y2 - прежние
currentRect.SetRect(x1, y1, x2, y2);
rectCoord[3] = currentRect;// Сохраним координаты четвертого прямоугольника
color[2] = 0; // color[0], color[1] - прежние
ShowRectangle(color, currentRect); // рисуем четвертый прямоугольник
}
Поместите вызов функции в автоматически вызываемую функцию перерисовки экрана, которая является обработчиком сообщения WM_PAINT операционной системы
Это сообщение посылается системой активному приложению всякий раз, когда экран разрушается и требует перерисовки.
Вызов функции перерисовки прямоугольников в обработчике OnPaint()
void CMenuDlg::OnPaint()
{
ShowAll(); // рисовать прямоугольники
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0);
.................................
}
else
{
CDialog::OnPaint();
}
}
Запустите приложение и должно получиться примерно следующее
Создадим обработчик сообщения WM_CONTEXTMENU. Для этого:
Выделите класс диалогового окна, которое будет принимать и обрабатывать это сообщение
В нашем случае это главное диалоговое окно приложения, имеющее ресурс IDD_MENU_DIALOG и поддерживающий этот ресурс класс CMenuDlg.
В панели Properties установите режим Messages и найдите интересующее нас сообщение WM_CONTEXTMENU. Через находящееся справа поле добавьте обработчик
Оболочка сгенерирует код в нескольких местах приложения
Объявление обработчика в классе CMenuDlg
class CMenuDlg : public CDialog
{
....................................................
CRect rectCoord[4];
// Рисует все прямоугольники
void ShowAll(void);
public:
afx_msg void OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/);
};
Заготовка определения обработчика в классе CMenuDlg
void CMenuDlg::OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/)
{
}
Регистрация сообщения в таблице сообщений класса CMenuDlg BEGIN_MESSAGE_MAP(CMenuDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_EXIT, OnBnClickedExit) ON_COMMAND(ID_FILE_HELLO, OnFileHello) ON_COMMAND(ID_HELP_ABOUT, OnHelpAbout) ON_COMMAND(ID_FILE_EXIT, OnFileExit) ON_WM_CONTEXTMENU() END_MESSAGE_MAP()
Наша задача на данном этапе - организовать и проверить в принципе работоспособность обработчика. А дальше мы настроим его на вызов контекстных меню. Для этого:
Разблокируйте аргументы и заполните обработчик отладочным кодомОтладочный код обработчика сообщения WM_CONTEXTMENU
void CMenuDlg::OnContextMenu(CWnd* pWnd, CPoint point)
{
CPoint myPoint = point; // сохраним, чтобы не портить основные
ScreenToClient(myPoint); // переведем экранные координаты
// курсора в клиентские для окна
int numRect = 0; // Номер прямоугольника
for(int i = 0; i < 4; i++){
if(rectCoord[i].PtInRect(myPoint)){
// Точка внутри прямоугольника
numRect = i + 1;
break;
}
}
if(numRect){
char str[2]; // Для цифра и '\0'
itoa(numRect, str, 10); // Преобразование цифры в строку
CString string = "Попали в прямоугольник ";
string += str;
MessageBox(string);
}
else{
MessageBeep((WORD) - 1); // Звукнуть
MessageBox("Никуда не попали");
}
}
Постройте приложение, пощелкайте правой кнопкой мыши и убедитесь, что все работает как задумано
Программа всегда должна работать как задумано. Если она работает хуже, чем задумано, - это плохая программа; если она работает лучше, чем задумано, - это еще более плохая программа, она просто опасна (как улыбающийся тайный враг!).
Построение отладочных кодов (что мы здесь чуть-чуть и попробовали) - типичный, иногда просто необходимый прием программирования. Иногда построение испытательных тестов стоит дороже, чем сама испытуемая программа.
Мы хотим, чтобы при правом щелчке на соответствующих областях чувствительности вызывались разные всплывающие контекстные меню. Прежде, чем писать обработчик вызова меню, сами меню нужно заготовить как ресурсы приложения.
Для разнообразия, в двух верхних прямоугольниках не будем создавать новые ресурсы, а возьмем уже готовый ресурс главного меню. А для двух нижних прямоугольников создадим самостоятельные ресурсы меню. Для этого выполните следующее:
Откройте панель Resource View и на любом ресурсе или узле дерева ресурсов приложения вызовите окно Add Resource

и нажмите кнопку New. Измените имя созданного ресурса на внятное IDR_MENUCONTEX3
Аналогичным образом создайте ресурс IDR_MENUCONTEX4
Это будут заготовки для третьей и четвертой областей чувствительности.
Отредактируйте оба ресурса как показано на рисунках IDR_MENUCONTEX3 |
IDR_MENUCONTEX4 |
|---|---|
![]() |
![]() |
Измените код обработчика WM_CONTEXTMENU на следующийОкончательный код обработчика сообщения WM_CONTEXTMENU
void CMenuDlg::OnContextMenu(CWnd* pWnd, CPoint point)
{
CPoint myPoint = point; // сохраним, чтобы не портить основные
ScreenToClient(myPoint); // переведем экранные координаты
// курсора в клиентские для окна
int numRect = 0; // Номер прямоугольника
for(int i = 0; i < 4; i++){
if(rectCoord[i].PtInRect(myPoint)){
// Точка внутри прямоугольника
numRect = i + 1;
break;
}
}
CMenu *pMainMenu, *pContextMenu, *pHeap = NULL; // Объявить указатели на меню
pMainMenu = GetMenu(); // Получить указатель на существующее
// главное меню IDR_MENU
switch(numRect){ // Куда попали, что делать
case 1:
pContextMenu = pMainMenu->GetSubMenu(0);// Первое подменю IDR_MENU
break;
case 2:
pContextMenu = pMainMenu->GetSubMenu(1);// Второе подменю IDR_MENU
break;
case 3:
pHeap = new CMenu(); // Создать экземпляр на куче
pHeap->LoadMenu(IDR_MENUCONTEX3); // Закачать ресурс
pContextMenu = pHeap->GetSubMenu(0);// Выбрать уровень подменю
break;
case 4:
pHeap = new CMenu(); // Создать экземпляр на куче
pHeap->LoadMenu(IDR_MENUCONTEX4); // Закачать ресурс
pContextMenu = pHeap->GetSubMenu(0);// Выбрать уровень подменю
break;
}
if(!numRect){ // Промазали
MessageBeep((WORD) - 1); // Звукнуть
MessageBox("Никуда не попали");
}
else // Отобразить меню как контекстное в расположении курсора мыши
pContextMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON
,point.x
,point.y
,pWnd
,NULL);
if(pHeap){
pHeap->DestroyMenu(); // Вернуть память операционной системе
pHeap = NULL; // Поставить затычку на всякий случай
}
}
Функция TrackPopupMenu() имеет пять параметров, пятый параметр по умолчанию равен нулю, но мы его поставили (просто так!).
TPM_CENTERALIGN, TPM_LEFTALIGN, TPM_RIGHTALIGN) и (TPM_LEFTBUTTON, TPM_RIGHTBUTTON ). Первый флажок позиционирует верхний край всплывающего меню на казатель мыши. Второй флажок задает, какой кнопкой мыши будут выбираться темы во всплывающем меню. Мы задали, что меню у указателя мыши будет появляться верхним левым углом, а выбор темы выполнится по левой кнопке.PtInRect() ).this ).NULL и мы его могли бы вообще не указывать (просто так указали, от нечего делать). При таком пустом значении установлено, что если пользователь щелкнет кнопкой мыши вне поля всплывающего меню, оно закроется.
Постройте приложение и убедитесь, что все работает как надоДобавим тестовый код в приложение, показывающий, что темы действительно выбираются. Ранее мы закрепляли обработчики - за каждой темой свой отдельный обработчик. Давайте теперь для всех тем двух новых ресурсов IDR_MENUCONTEX3 и IDR_MENUCONTEX4 закрепим один обработчик. При этом почти все изменения придется выполнить вручную, потому, что мастер способен создавать только отдельные обработчики.
Выполните следующие пошаговые инструкции:
Создайте обработчик для первой темы контекстного меню
Настройте окно мастера так

Мастер добавит в приложение код в трех местах:
Объявление обработчика в классе CMenuDlg
class CMenuDlg : public CDialog
{
....................................................
CRect rectCoord[4];
// Рисует все прямоугольники
void ShowAll(void);
public:
afx_msg void OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/);
afx_msg void OnGroupMenu();
};
Заготовка определения обработчика в классе CMenuDlg
class CMenuDlg : public CDialog
{
....................................................
CRect rectCoord[4];
// Рисует все прямоугольники
void ShowAll(void);
public:
afx_msg void OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/);
afx_msg void OnGroupMenu();
};
Регистрация сообщения в таблице сообщений класса CMenuDlg BEGIN_MESSAGE_MAP(CMenuDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_EXIT, OnBnClickedExit) ON_COMMAND(ID_FILE_HELLO, OnFileHello) ON_COMMAND(ID_HELP_ABOUT, OnHelpAbout) ON_COMMAND(ID_FILE_EXIT, OnFileExit) ON_WM_CONTEXTMENU() ON_COMMAND(ID_32781, OnGroupMenu) END_MESSAGE_MAP()
В таблицу сообщений мастер поместил цифровой идентификатор пункта темы , потому что мы сразу задавали наименования пунктов по-русски.
Включите редактор меню в режим
IDR_MENUCONTEX3 IDR_MENUCONTEX4 

Реконструируйте таблицу сообщений так, чтобы все идентификаторы ссылались на один и тот же обработчикМодификация таблицы сообщений класса CMenuDlg вручную BEGIN_MESSAGE_MAP(CMenuDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_BN_CLICKED(IDC_EXIT, OnBnClickedExit) ON_COMMAND(ID_FILE_HELLO, OnFileHello) ON_COMMAND(ID_HELP_ABOUT, OnHelpAbout) ON_COMMAND(ID_FILE_EXIT, OnFileExit) ON_WM_CONTEXTMENU() // IDR_MENUCONTEX3 ON_COMMAND(ID_32781, OnGroupMenu) // Высоцкий ON_COMMAND(ID_32782, OnGroupMenu) // Лещенко ON_COMMAND(ID_32783, OnGroupMenu) // Пахмутова ON_COMMAND(ID_32784, OnGroupMenu) // Ротару ON_COMMAND(ID_32785, OnGroupMenu) // Цой-ой // IDR_MENUCONTEX4 ON_COMMAND(ID_32786, OnGroupMenu) // Яблочко ON_COMMAND(ID_32787, OnGroupMenu) // Семечко ON_COMMAND(ID_32788, OnGroupMenu) // Коробочка ON_COMMAND(ID_32789, OnGroupMenu) // Танго ON_COMMAND(ID_32790, OnGroupMenu) // Вальс ON_COMMAND(ID_32791, OnGroupMenu) // Буги-Вуги ON_COMMAND(ID_32792, OnGroupMenu) // Летка-Енька END_MESSAGE_MAP()
Добавьте код обработчикаКод обработчика OnGroupMenu()
void CMenuDlg::OnGroupMenu()
{
MessageBox("Выбрали пункт контекстного меню");
}
Постройте приложение и убедитесь, что все работаетПунктам меню можно назначить командные клавиши (String Table. Каждый элемент таблицы содержит идентификатор объекта и комбинацию клавиш. Комбинации клавиш должны быть уникальны в пределах всего приложения.
После определения акселераторов можно связать какую-либо функцию с идентификатором объекта. Можно дать записям командных клавиш тот же самый идентификатор, что и у соответствующего пункта меню. После определения всех сочетаний клавиш нужно добавить эти сочетания в меню, чтобы пользователи знали о возможности быстрого вызова.
К сожалению MFC, поскольку предполагается, что диалоговые окна не должны иметь меню. Поэтому только потренируемся добавлять информацию о горячих клавишах в меню. Для этого дополните строки меню своими комбинациями, отделив их от названий символом табуляции \t.
Постройте приложение, проверьте его работоспособность и сдайте работу преподавателю
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.