Практикум прикладного программирования на MFC и C++ в среде VS.NET

Меню в приложениях MFC

Разбить на страницы
Показывать лекцию целиком
Файлы к данной лабораторной работе, Вы можете скачать здесь.

Цель работы

  • Создать меню для приложения
  • Добавить меню в главное диалоговое окно приложения
  • Вызывать функции приложения из меню
  • Создать контекстное (всплывающее) меню
  • Создать комбинированный обработчик (вручную)
  • Задать акселераторы (ускоряющие, горячие клавиши) быстрого вызова опций меню
  • Проектировщики компьютерных программ изобрели графический интерфейс пользователя ( GUI - Graphical User Interface ), чтобы облегчить взаимодействие с компьютерными системами и приложениями. Важной частью этого интерфейса является меню.

    Стили меню

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

    Меню как ресурс

    Различные составляющие приложений 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: Menu Вкладка Advanced 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

    Восполним пробел того, что мы не указали создание диалогового окна AboutBox в настройках мастера. Для этого используем материал, рассмотренный в лабораторной работе " Lab7 - Dialogs ".

    Создание макета диалогового окна About

  • Выберите вкладку 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) - индекс категории всего мен

    Единственным параметром этой функции является целое 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

    Создадим обработчик сообщения 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 - Graphical User Interface ), чтобы облегчить взаимодействие с компьютерными системами и приложениями. Важной частью этого интерфейса является меню.

    Стили меню

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

    Меню как ресурс

    Различные составляющие приложений 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: Menu Вкладка Advanced 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

    Восполним пробел того, что мы не указали создание диалогового окна AboutBox в настройках мастера. Для этого используем материал, рассмотренный в лабораторной работе " Lab7 - Dialogs ".

    Создание макета диалогового окна About

  • Выберите вкладку 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) - индекс категории всего мен

    Единственным параметром этой функции является целое 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

    Создадим обработчик сообщения 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.

  • Постройте приложение, проверьте его работоспособность и сдайте работу преподавателю
  • Вернуться к учебному плану