Стоит Windows 8 Pro, Visual Studio 2010 Express Edition . |
Меню в приложениях MFC
Создание ресурсов-заготовок контекстных меню
Мы хотим, чтобы при правом щелчке на соответствующих областях чувствительности вызывались разные всплывающие контекстные меню. Прежде, чем писать обработчик вызова меню, сами меню нужно заготовить как ресурсы приложения.
Для разнообразия, в двух верхних прямоугольниках не будем создавать новые ресурсы, а возьмем уже готовый ресурс главного меню. А для двух нижних прямоугольников создадим самостоятельные ресурсы меню. Для этого выполните следующее:
-
Откройте панель Resource View и на любом ресурсе или узле дерева ресурсов приложения вызовите окно Add Resource
и нажмите кнопку New. Измените имя созданного ресурса на внятное 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 закрепим один обработчик. При этом почти все изменения придется выполнить вручную, потому, что мастер способен создавать только отдельные обработчики.
Выполните следующие пошаговые инструкции:
- Создайте обработчик для первой темы контекстного меню IDR_MENUCONTEX3. Для этого откройте русурс и из контекстного меню первого пункта "Высоцкий" выполните Add Event Handler
-
Настройте окно мастера так
Мастер добавит в приложение код в трех местах:
Объявление обработчика в классе 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()
В таблицу сообщений мастер поместил цифровой идентификатор пункта темы , потому что мы сразу задавали наименования пунктов по-русски.
- Включите редактор меню в режим Edit IDs и увидите номерные идентификаторы
-
Реконструируйте таблицу сообщений так, чтобы все идентификаторы ссылались на один и тот же обработчик
Модификация таблицы сообщений класса 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.
- Постройте приложение, проверьте его работоспособность и сдайте работу преподавателю