Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Microsoft Visual C++ и MFC. Программирование для Windows 95 и Windows NT (часть 2)

© Александр Фролов, Григорий Фролов
Том 28, М.: Диалог-МИФИ, 1996, 288 стр.

[Назад] [Содеожание] [Дальше]

Меню

Самый простой и удобный способ создания меню приложения основан на использовании специального ресурса - шаблона меню. Если вы применяете для создания приложения средства MFC AppWizard, то однооконное приложение по умолчанию будет иметь один ресурс меню, а многооконное два.

Для создания и изменения меню приложения следует использовать редактор ресурсов Microsoft Visual C++. С помощью него вы разработаете меню буквально за несколько минут.

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

Чтобы добавить новую строку к меню, достаточно выполнить двойной щелчок левой кнопкой мыши по одному из пустых прямоугольников из точек, которые располагаются в конце главного меню (вы создадите еще одно меню верхнего уровня) и в низу каждого из меню (создается новая строка меню). При этом на экране появится диалоговая панель Menu Item Properties, которую вы должны заполнить (рис. 3.1).

Рис. 3.1. Редактор меню и диалоговая панель Menu Item Properties

Текст, который будет отображаться в строке меню, вы должны ввести в поле Caption. Затем в списке с полем редактирования ID надо выбрать идентификатор для этой строки меню. Если вы не заполните поле ID, редактор ресурсов самостоятельно создаст новый идентификатор на основе имени меню и строки.

В поле Prompt вы можете ввести текстовую строку, которая будет отображаться при выборе данного элемента меню в панели состояния приложения. Редактор ресурсов Microsoft Visual C++ создаст для введенного текста новый строковый ресурс и запишет его в файл ресурсов приложения, присвоив ему тот же идентификатор, что и меню. Так как строки меню и описывающий их текст, имеют одинаковые идентификаторы, то MFC сможет использовать их вместе. При выборе строки меню, MFC просто загружает строковый ресурс с идентичным идентификатором и отображает его в панели состояния.

Остальные переключатели диалоговой панели Menu Item Properties задают различные характеристики строк меню, отвечающие в первую очередь за их внешний вид. Изучите их самостоятельно, используя документацию Microsoft Visual C++.

Также легко можно просмотреть и изменить свойства уже существующих строк меню. Для этого надо выполнить по ним двойной щелчок левой кнопкой мыши. На экране появится уже описанная нами выше диалоговая панель Menu Item Properties, которая уже заполнена текущими параметрами строки меню.

Меню без класса CMenu

Как и для других элементов пользовательского интерфейса, для управления меню в состав библиотеки классов MFC включен специальный класс - класс CMenu. Класс CMenu - один из самых незаметных классов библиотеки MFC. Ваше приложение может активно работать с меню, но все же в исходных текстах вы не найдете ни одного объекта этого класса.

В приложениях, созданных с помощью MFC AppWizard меню создается автоматически вместе с панелью управления и панелью состояния. Для этого достаточно указать при создании шаблона документа XE "шаблон документа" общий идентификатор этих ресурсов:


// Объявляем указатель на шаблон документа
CSingleDocTemplate* pDocTemplate;

// Создаем шаблон документа
pDocTemplate = new CSingleDocTemplate(
   IDR_MAINFRAME,                // Идентификатор меню, панели 
                                 // управления и пиктограммы
   RUNTIME_CLASS(CSingleDoc),    // Класс документа
   RUNTIME_CLASS(CMainFrame),    // Класс главного окна 
   RUNTIME_CLASS(CSingleView));  // Класс окна просмотра

В случае многооконного приложения дополнительно указываются ресурсы, используемые когда все окна просмотра документов закрыты. Все эти ресурсы имеют один и тот же идентификатор. В примере, представленном ниже, это идентификатор IDR_MAINFRAME:


// Создаем главное окно многооконного приложения
CMainFrame* pMainFrame = new CMainFrame;

// Загружаем ресурсы с идентификатором IDR_MAINFRAME,
// в том числе и меню
if(!pMainFrame->LoadFrame(IDR_MAINFRAME))
   return FALSE;

В приложениях MFC AppWizard, имеющих однооконный или многооконный интерфейс, меню создается и изменяется самой библиотекой MFC. Несмотря на это, вы также можете управлять меню. Самый простой способ заключается в обработке команд обновления от меню. Проблеме использования этих команд мы посвятили раздел “Класс CCmdUI”.

Даже когда приложение создано без использования средств MFC AppWizard, процедура создания меню остается очень простой и также может не задействовать объекты класса CMenu напрямую.

В таких приложениях, как правило, создается главное окно на основе класса CFrameWnd. Для этого сначала создается соответствующий объект класса CFrameWnd, а затем вызывается либо метод Create, либо метод LoadFrame, который в свою очередь уже создает само окно вместе с меню.

Метод Create

Метод Create создает и инициализирует окно, связанное с объектом CFrameWnd. В случае успешного завершения метод Create возвращает ненулевое значение, а в противном случае - ноль:


BOOL Create(
   LPCTSTR lpszClassName,               // Класс окна
   LPCTSTR lpszWindowName,              // Имя окна
   DWORD dwStyle = WS_OVERLAPPEDWINDOW, // Тип окна
   const RECT& rect = rectDefault,      // Расположение окна
   CWnd* pParentWnd = NULL,             // Родительское окно
   LPCTSTR lpszMenuName = NULL,         // Меню
   DWORD dwExStyle = 0,                 // Дополнительные 
                                        // характеристики окна
   CCreateContext* pContext = NULL ); // Используется для 
                                    // организации механизма 
                                    // документ/окно просмотра

Обязательно надо указать только два первых параметра метода Create. Первый параметр lpszClassName служит для задания класса окна. В качестве него можно также указать значение NULL, тогда по умолчанию будет использован класс, определенный для окон CFrameWnd. Второй параметр lpszWindowName указывает имя окна - оно будет отображаться в заголовке окна.

Остальные параметры метода необязательные. Если их не указать, будут использованы значения, принятые по умолчанию. Нас, однако, сейчас интересует только параметр lpszMenuName. Через него вы можете указать имя ресурса меню, которое будет создано для данного окна. По умолчанию редактор ресурсов Microsoft Visual C++ присваивает созданным в нем ресурсам, в том числе меню, числовые идентификаторы. Чтобы получить из такого идентификатора значение, совместимое по типу с параметром lpszMenuName, следует использовать макрокоманду MAKEINTRESOURCE.

Следующий пример показывает, как можно создать окно с меню:


CMultiMenuWindow::CMultiMenuWindow()
{ 
   // Создаем окно приложения, соответствующее 
   // данному объекту класса CMultiMenuWindow
   Create(NULL, "Multi Menu Sample", WS_OVERLAPPEDWINDOW,
      rectDefault, NULL, MAKEINTRESOURCE(IDR_MENU));
}

Класс CMultiMenuWindow, конструктор которого представлен выше, наследован от базового класса CFrameWnd. Конструктор CMultiMenuWindow создает окно с заголовком Multi Menu Sample. Это окно имеет меню IDR_MENU. Меню с идентификатором IDR_MENU должно быть определено в файле ресурсов приложения.

Метод LoadFrame

Виртуальный метод LoadFrame позволяет динамически создавать окно, пользуясь информацией из файла ресурсов. В случае успешного завершения метод LoadFrame возвращает ненулевое значение, а в противном случае - ноль:


virtual BOOL LoadFrame(
   UINT nIDResource,
   DWORD dwDefaultStyle = WS_OVERLAPPEDWINDOW|FWS_ADDTOTITLE,
   CWnd* pParentWnd = NULL,
   CCreateContext* pContext = NULL
);

Параметры метода LoadFrame практически идентичны параметрам метода Create, описанного ранее. Исключение составляет первый параметр - nIDResource. Он представляет идентификатор, общий для нескольких ресурсов, используемых при создании окна. К таким ресурсам относятся меню (будет использоваться как меню окна), строковый ресурс (заголовок окна), пиктограмма (отображается в случае минимизации окна) и таблица клавиш акселерации (используется для ускоренного выбора строк меню).

Класс CMenu

Вы можете создать меню и без использования методов Create или LoadFrame. Для этого вы должны будете создать объект класса CMenu XE "CMenu" и вызвать для него несколько методов.

Конструктор класса CMenu

Объект класса CMenu не является меню, он только представляет существующее меню. Вы можете создать объект класса CMenu как локальный, а после использования удалить. На меню это не повлияет:


{
   CMenu myMenu;
}

Метод LoadMenu

После объявления объекта класса CMenu вы можете загрузить меню из ресурсов приложения, воспользовавшись для этой цели методом LoadMenu. В случае успешного завершения метод LoadMenu возвратит ненулевое значение, и нуль в противном случае:


BOOL LoadMenu(LPCTSTR lpszResourceName);
BOOL LoadMenu(UINT nIDResource);

Метод LoadMenu загрузит меню, заданное именем lpszResourceName или идентификатором nIDResource, и свяжет его с соответствующим объектом класса CMenu. Теперь вы можете использовать для управления загруженным меню другие методы класса CMenu.

После того как меню загружено, его можно “подключить” к окну. Для этого следует воспользоваться методом SetMenu входящим в класс CWnd.

Метод SetMenu класса CWnd

В качестве параметра pMenu передайте методу SetMenu указатель на объект класса CMenu, представляющий меню. Если вы желаете просто удалить текущее меню, используемое окном, передайте методу SetMenu в качестве параметра значение NULL:


BOOL SetMenu(CMenu* pMenu);

В случае успешного завершения операции метод SetMenu вернет ненулевое значение. В противном случае SetMenu вернет ноль.

После того, как вы установили меню, вызвав метод SetMenu, и до того, как соответствующий объект CMenu будет удален, надо вызвать метод Detach класса CMenu. Этот метод разорвет связь между меню и соответствующим объектом класса CMenu, после чего последний может быть удален:


HMENU Detach();

Метод Detach возвращает в случае успешного завершения идентификатор меню, а в случае ошибки - значение NULL.

Сразу отметим, что если до установки меню окно уже имело меню, надо удалить его, воспользовавшись методом DestroyMenu класса CMenu. Если с меню, подлежащим удалению, не связан объект класса CMenu, вы можете обратиться к методу Attach:


BOOL Attach(HMENU hMenu);

Для этого создайте объект класса CMenu, а затем вызовите для него метод Attach, указав в качестве параметра hMenu идентификатор меню. Метод Attach возвращает в случае успешного завершения ненулевое значение, а в случае ошибки - ноль.

Чтобы определить идентификатор меню известного окна, можно воспользоваться методом GetMenu, определенным в классе CWnd. Этот метод возвращает указатель на объект типа CMenu:


CMenu* GetMenu() const;

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

Мы продемонстрируем различные методы создания и управления меню в приложении MultiMenu, а сейчас сделаем несколько замечаний относительно остальных методов класса CMenu.

Класс CMenu, наследованный от базового класса CObject, содержит все необходимые методы для создания и управления меню. Используя эти методы, вы можете добавлять к меню новые строки, удалять и изменять их. Специальные методы класса CMenu позволяют выделять отдельные строки меню и даже создавать элементы меню, содержащие не только текст но и изображение.

Класс CCmdUI

В MFC реализован специальный механизм для обновления таких объектов интерфейса пользователя как меню, панели управления и панели состояния. Этот механизм предусматривает передачу приложению команд обновления пользовательского интерфейса (update command user interface). Для обработки этих команд предназначена макрокоманда ON_UPDATE_COMMAND_UI, размещаемая в таблице сообщений класса.

Для каждой строки меню, для каждой кнопки панели управления и для каждого индикатора панели состояния передается отдельное сообщение.

Когда передаются команды обновления интерфейса пользователя? Многое зависит от самого обновляемого объекта.

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

Для кнопок панели управления и индикаторов панели состояния команды обновления передаются в период “бездействия” приложения, когда очередь сообщений приложения пуста.

Меню

В момент, когда пользователь открывает меню, приложению передаются команды обновления. В результате для всех строк меню, для которых в таблице сообщений приложения присутствуют макрокоманды ON_UPDATE_COMMAND_UI, вызываются соответствующие методы-обработчики. Они могут изменить состояние меню - заблокировать отдельные строки меню, выделить их символами · или Ö.

Панели управления и панели состояния

Если очередь сообщений приложения пуста, вызывается метод OnIdle главного класса приложения. В своем приложении вы можете переопределить метод OnIdle и выполнять с его помощью какую-либо фоновую работу.

Метод OnIdle определен в классе CWinApp и по умолчанию выполняет обновление пользовательского интерфейса - передает команды обновления для тех кнопок панелей управления и индикаторов панелей состояния, которые имеют в таблице сообщений приложения макрокоманду ON_UPDATE_COMMAND_UI. Макрокоманда ON_UPDATE_COMMAND_UI вызывает методы-обработчики, которые могут изменить состояние кнопок и индикаторов панелей управления и панелей состояния.

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

Органы диалоговых панелей управления

Не только меню и панели управления обновляются с использованием механизма команд обновления. Точно также можно обновить и состояние кнопок и других органов управления диалоговых панелей.

Макрокоманда ON_UPDATE_COMMAND_UI

Макрокоманда ON_UPDATE_COMMAND_UI предназначена для использования в таблицах сообщений приложения и имеет следующий формат:


ON_UPDATE_COMMAND_UI(id, memberFxn)

Параметр id определяет идентификатор строки меню, кнопки панели управления или индикатора панели состояния, для которых надо обработать команду обновления. Параметр memberFxn задает метод, выполняющий обновление.

Если один и тот же метод вызывается для обработки различных команд обновления, можно использовать другую макрокоманду - ON_UPDATE_COMMAND_UI_RANGE. Она вызывает метод memberFxn для обработки всех команд обновления, идентификаторы которых находятся в промежутке значений от id1 до id2:


ON_UPDATE_COMMAND_UI_RANGE(id1, id2, memberFxn)

Метод - обработчик команд обновления, который вызывается макрокомандами ON_UPDATE_COMMAND_UI и ON_UPDATE_COMMAND_UI_RANGE имеет следующий формат:


afx_msg void memberFxn(CCmdUI* pCmdUI);

Имя метода - обработчика обычно формируется из префикса OnUpdate и названия соответствующего объекта интерфейса пользователя - строки меню, кнопки панели управления или индикатора панели состояния.

В качестве параметра pCmdUI методу передается указатель на объект класса CCmdUI. Этот объект представляет элемент интерфейса пользователя (строку меню, кнопку панели управления…), для которого надо обработать команду обновления. Вызывая методы класса CCmdUI, вы можете легко изменять состояние соответствующего объекта интерфейса пользователя.

Более подробно самые важные методы класса CCmdUI мы рассмотрим в следующем разделе.

MFC ClassWizard и команды обновления

Если приложение подготовлено с использованием MFC AppWizard, то наилучшим способом создания обработчиков команд обновления является использование средств ClassWizard. Процедура создания обработчиков команд обновления от строк меню и кнопок панелей управления практически не отличается от процедуры создания обычных обработчиков командных сообщений.

Запустите ClassWizard. На экране появится диалоговая панель MFC ClassWizard. Выберите из нее страницу Message Maps (рис. 3.2). Теперь из списка Object IDs выберите идентификатор интересующей вас строки меню или кнопки панели управления. В списке Messages появятся две строки - COMMAND и ON_UPDATE_COMMAND_UI.

Строка COMMAND позволяет создать обработчик командных сообщений, а строка ON_UPDATE_COMMAND_UI - обработчик команд обновления. О том как создавать с помощью MFC ClassWizard методы для обработки командных сообщений, мы рассказывали в 24 томе серии “Библиотека системного программиста”, посвященном библиотеке классов MFC.

Чтобы создать обработчик для команды обновления, выберите из списка Messages строку ON_UPDATE_COMMAND_UI, а из списка Class name имя класса к которому будет добавлен новый метод. Нажмите кнопку Add Function. MFC ClassWizard предложит имя для нового метода. Вы можете согласиться с предложением ClassWizard или изменить имя метода по своему усмотрению. В частности, для нескольких разных строк меню или кнопок панели управления вы можете указать один и тот же метод обработчик.

Рис. 3.2. MFC ClassWizard

К сожалению, MFC ClassWizard не позволяет назначить один обработчик команд обновления нескольким объектам пользовательского интерфейса с помощью макрокоманды ON_UPDATE_COMMAND_UI_RANGE. Вместо одной макрокоманды ON_UPDATE_COMMAND_UI_RANGE MFC ClassWizard разместит в таблице сообщений необходимое количество макрокоманд ON_UPDATE_COMMAND_UI.

Еще одно неприятное ограничение MFC ClassWizard заключается в том, что он не дает возможности создать обработчики для команд обновления от индикаторов панели состояния. Такие обработчики вы должны будете добавлять к классам приложения вручную.

Методы класса CCmdUI

Важную роль в работе таких объектов интерфейса пользователя, как меню, панели управления и панели состояния, играет класс CCmdUI. Методы этого класса позволяют заблокировать отдельные элементы меню, панелей управления и панелей состояния, отметить их символами Ö или ·.

Класс CCmdUI один из немногих классов библиотеки MFC, который не имеет других базовых классов. Поэтому для объектов данного класса доступно относительно мало методов. Вы можете использовать только методы, определенные в самом классе CCmdUI.

Метод

Описание

Enable

Устанавливает или снимает блокировку

SetCheck

Помечает строку меню символом Ö

SetRadio

Помечает строку меню символом ·

SetText

Устанавливает текст. Обычно используется для изменения текста в индикаторах панели состояния

Метод Enable

Виртуальный метод Enable позволяет установить или снять блокировку с объекта интерфейса пользователя, представленного объектом класса CCmdUI. Метод имеет единственный параметр bOn. Если параметр bOn содержит значение TRUE или не указан совсем, то блокировка снимается. Если параметр bOn содержит значение FALSE, то блокировка устанавливается:


virtual void Enable(BOOL bOn = TRUE);

Заблокированные строки меню и кнопки панелей управления отображаются серым цветом и не могут быть использованы до момента снятия блокировки. В случае блокировки индикатора панели состояния его текст не будет отображаться.

Метод Enable также можно использовать для блокирования органов диалоговых панелей управления.

Метод SetCheck

Виртуальный метод SetCheck можно использовать для изменения состояния строки меню и кнопок панели управления:


virtual void SetCheck(int nCheck = 1);

Если вы используете метод SetCheck для управления меню и задали в качестве параметра nCheck нулевое значение, то соответствующая строка меню выделяется символом Ö, если параметр nCheck не указан или равен 1, то выделение снимается.

В случае использования метода SetCheck для управления кнопкой панели управления, параметр nCheck задает новое состояние кнопки. Если параметр nCheck равен нулю, кнопка переходит в нажатое положение, если параметр nCheck не указан или равен единице - кнопка переходит в отжатое положение, а если параметр nCheck равен 2, кнопка принимает промежуточное состояние.

Вы можете использовать метод SetCheck для управления внешним видом индикаторов панелей состояния. Если параметр nCheck равен нулю, то рамка индикатора изменяется таким образом, что он будет располагается выше общего уровня панели состояния. Если параметр nCheck равен 1, тогда индикатор переходит в нормальное состояние.

Метод Enable также можно использовать для выбора положения переключателей в диалоговых панелей управления.

Метод SetRadio

Виртуальный метод SetRadio, также как метод SetCheck, можно использовать для изменения состояния строки меню и кнопок панели управления:


virtual void SetRadio(BOOL bOn = TRUE);

Если вы используете метод SetRadio для управления меню и задали в качестве параметра bOn значение TRUE, то соответствующая строка меню выделяется символом ·, если параметр nCheck равен FALSE, то выделение снимается.

В случае использования метода SetRadio для управления кнопкой панели управления, параметр bOn задает новое состояние кнопки. Если параметр bOn равен FALSE, кнопка переходит в нажатое положение, если параметр bOn не указан или равен TRUE - кнопка переходит в отжатое положение.

Вы можете использовать метод SetRadio для управления внешним видом индикаторов панелей состояния. Если параметр bOn равен FALSE, рамка индикатора изменяется таким образом, что он будет располагается выше общего уровня панели состояния. Если параметр bOn равен TRUE, тогда индикатор переходит в нормальное состояние.

Метод Enable также можно использовать для выбора положения переключателей в диалоговых панелей управления.

Метод SetText

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


virtual void SetText(LPCTSTR lpszText);

Следует отметить, что при использовании метода SetText для изменения текста в индикаторах панели состояния, вы должны отдельно позаботиться об изменении размера индикатора. Метод SetText не меняет размер индикатора, вы должны сами рассчитать ширину текста и изменить размер индикатора с помощью соответствующего метода. Более подробно об изменении параметров индикаторов панели состояния мы расскажем в разделе “Панель состояния”.

Элементы данных класса CCmdUI

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

Метод

Описание

m_nID

Идентификатор объекта, для которого вызвано сообщение

m_nIndex

Индекс объекта, для которого вызвано сообщение

m_pMenu

Указатель на меню. Если команда обновления передана не от меню, m_pOther содержит значение NULL

m_pOther

Указатель на панель состояния или панель управления для объекта которой выполняется обновление. Если команда обновления передана от меню, m_pOther содержит значение NULL

Ресурсы клавиш акселераторов

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

Для создания и изменения таблиц акселераторов следует использовать редактор ресурсов Microsoft Visual C++. Он позволяет определить соответствие комбинаций клавиш и идентификаторов командных сообщений (рис. 3.3).

Рис. 3.3. Редактор таблицы клавиш ускорения

Для приложений имеющих оконный интерфейс, и созданных с использованием MFC AppWizard, таблица акселераторов создается автоматически. Таблица акселераторов загружается приложением, во время создания главного окна приложения методом LoadFrame. Мы уже рассматривали этот метод, который также используется для загрузки меню и ряда других ресурсов:


// Создаем главное окно многооконного приложения
CMainFrame* pMainFrame = new CMainFrame;

// Загружаем ресурсы с идентификатором IDR_MAINFRAME,
// в том числе и таблицу акселераторов
if(!pMainFrame->LoadFrame(IDR_MAINFRAME))
   return FALSE;

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

Чтобы определить таблицу акселераторов для документов данного типа, надо просто включить ее в файл ресурсов приложения, присвоив ей идентификатор данного типа документов:


CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
   IDR_MULTITYPE,
   RUNTIME_CLASS(CMultiDoc),
   RUNTIME_CLASS(CChildFrame), 
   RUNTIME_CLASS(CMultiView));
AddDocTemplate(pDocTemplate);

Если приложение создается без использования средств MFC AppWizard и модели документ - окно просмотра, вы можете загрузить таблицу акселераторов, с помощью метода LoadAccelTable, входящего в состав класса CFrameWnd:


BOOL LoadAccelTable(LPCTSTR lpszResourceName);

В качестве параметра lpszResourceName следует указать имя ресурса таблицы акселераторов. Если таблица акселераторов вместо строкового имени имеет числовой идентификатор, то вы должны воспользоваться макрокомандой MAKEINTRESOURCE.

Как и многие другие методы классов MFC, метод LoadAccelTable возвращает в случае успешного завершения ненулевое значение и нуль в случае ошибки. Ошибка во время загрузки таблицы акселераторов может случиться, если вы неправильно укажите идентификатор (или имя) ресурса таблицы.

Приложение MultiMenu

Создайте новый проект под названием MultiMenu. В качестве типа приложения выберите из списка Type строку Application. Настройте проект MultiMenu, указав что приложение будет работать с библиотекой классов MFC.

Наберите в редакторе исходный текст приложения и сохраните его в файле MultiMenu.cpp (листинг 3.1). Включите готовый файл MultiMenu.cpp в проект.

Листинг 3.1. Файл MultiMenu.cpp


//============================================================
// Приложение MultiMenu
// (c) Frolov G.V., 1996
// E-mail: frolov@glas.apc.org
//============================================================

// Включаемые файлы для MFC
#include <afxwin.h>
#include <afxext.h>
#include <afxcmn.h>

// Включаемый файл для ресурсов приложения и идентификаторов
#include "resource.h"

//============================================================
// Класс CMultiMenuApp - главный класс приложения 
//============================================================
class CMultiMenuApp : public CWinApp
{
public:
   // Мы будем переопределять метод InitInstance,
   // предназначенный для инициализации приложения
   virtual BOOL InitInstance();
};
 
// Создаем объект приложение класса CMultiMenuApp
CMultiMenuApp MultiMenuApp;
 
//============================================================
// Класс CMultiMenuWindow - представляет главное окно 
//============================================================
class CMultiMenuWindow : public CFrameWnd
{

protected:  
   // Панель состояния
   CStatusBar  m_wndStatusBar;   
   
   // Флаг управляет строкой Prosess меню Mission
   BOOL  bEnable; 

   // Флаг управляет строкой Construction меню Mission
   BOOL  bRadio;  

   // Флаг управляет строкой Restrict меню Menu
   int   nCheck;  
       
protected:
   // Метод для создания окна приложения и панели состояния
   afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

   // Методы для обработки командных сообщений 
   // от меню приложения
   afx_msg void CMultiMenuWindow::OnDisable();
   afx_msg void CMultiMenuWindow::OnCommand();
   afx_msg void CMultiMenuWindow::OnExit();
   afx_msg void CMultiMenuWindow::OnConstruct();
   afx_msg void CMultiMenuWindow::OnRestrictMenu();
   afx_msg void CMultiMenuWindow::OnFullMenu();

   // Методы для обновления меню
   afx_msg void OnUpdateProcess(CCmdUI* pCmdUI);
   afx_msg void OnUpdateConstruct(CCmdUI* pCmdUI);
   afx_msg void OnUpdateDisable(CCmdUI* pCmdUI);
   
public:
   // Конструктор класса CMultiMenuWindow
   CMultiMenuWindow();

   // Макрокоманда необходима, так как класс 
   // CMultiMenuWindow обрабатывает сообщения
   DECLARE_MESSAGE_MAP()    
}; 

//============================================================
// Таблица сообщений класса CMultiMenuWindow
//============================================================
BEGIN_MESSAGE_MAP(CMultiMenuWindow, CFrameWnd)
   
   // Макрокоманда вызывает метод OnCreate
   ON_WM_CREATE()

   // Макрокоманда вызывает метод OnContextMenu
   ON_WM_CONTEXTMENU()

   // Макрокоманды для обработки командных сообщений 
   ON_COMMAND(ID_MENU_DISABLE, OnDisable)
   ON_COMMAND(ID_MISSION_CONSTRUCT, OnConstruct)
   ON_COMMAND(ID_FILE_EXIT, OnExit)
   ON_COMMAND(ID_MISSION_PROCESS, OnCommand)
   
   ON_COMMAND(ID_MENU_RESTRICT, OnRestrictMenu)
   ON_COMMAND(ID_MENU_FULL, OnFullMenu)

   // Обработчики сообщений ON_UPDATE_COMMAND_UI 
   ON_UPDATE_COMMAND_UI(ID_MISSION_PROCESS, OnUpdateProcess)
   ON_UPDATE_COMMAND_UI(ID_MISSION_CONSTRUCT, 
                           OnUpdateConstruct)
   ON_UPDATE_COMMAND_UI(ID_MENU_DISABLE, OnUpdateDisable)

END_MESSAGE_MAP()

// Индикатор панели управления
UINT indicator = ID_SEPARATOR;

//============================================================
// Метод InitInstance класса CMultiMenuApp
// Создает главное окно приложения и отображает его на экране
//============================================================
BOOL CMultiMenuApp::InitInstance()
{
   m_pMainWnd = new CMultiMenuWindow();
   m_pMainWnd -> ShowWindow(m_nCmdShow);
   m_pMainWnd -> UpdateWindow();

   return TRUE;
}

//============================================================
// Конструктор класса CMultiMenuWindow
//============================================================
CMultiMenuWindow::CMultiMenuWindow()
{ 
   // Создаем окно приложения, соответствующее 
   // данному объекту класса CMultiMenuWindow
   Create(NULL, "Multi Menu Sample", WS_OVERLAPPEDWINDOW,
      rectDefault, NULL, MAKEINTRESOURCE(IDR_FULL_MENU));

   // Загружаем таблицу клавиш акселерации
   LoadAccelTable(MAKEINTRESOURCE(IDR_ACCELERATOR));

   // Инициализируем флаги
   bEnable = TRUE;         
   bRadio = TRUE;         
   nCheck = 0;
}

//============================================================
// Метод OnCreate класса CMultiMenuWindow
// Вызывается во время создания окна приложения
//============================================================
int CMultiMenuWindow::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   // Вызываем метод OnCreate базового класса
   if(CFrameWnd::OnCreate(lpCreateStruct) == -1)
      return -1;
   
   // Создаем панель состояния 
   if(!m_wndStatusBar.Create(this))
   {
      // Ошибка при создании панели состояния
      TRACE0("Failed to create status bar\n");
      return -1;   
   }

   // Отображаем индикаторы панели состояния
   if(!m_wndStatusBar.SetIndicators(&indicator,1))
   {
      // Ошибка при установке индикатора
      TRACE0("Failed to set indicators\n");
      return -1;   
   }
   
   return 0;
}

//============================================================
// Метод OnDisable класса CMultiMenuWindow
// Изменяем состояние флагов bEnable и nCheck 
//============================================================
void CMultiMenuWindow::OnDisable()
{
   // Меняем значение bEnable с TRUE на FALSE и наоборот
   bEnable = !bEnable;
   
   // Меняем значение bEnable с 1 на 0 и наоборот
   nCheck = (nCheck == 1) ? 0 : 1; 
}

//============================================================
// Метод OnRestrictMenu класса CMultiMenuWindow
// Изменяем меню приложения с IDR_FULL_MENU на 
// IDR_RESTRICT_MENU
//============================================================
void CMultiMenuWindow::OnRestrictMenu()
{
   CMenu menuOld;       // текущее меню
   CMenu menuRestrict;  // новое меню
   CMenu* pMenu;

   // Получаем указатель на текущее меню 
   pMenu = this->GetMenu();
   
   // Связываем меню с объектом menuOld
   menuOld.Attach(pMenu->m_hMenu);

   // Удаляем меню 
   menuOld.DestroyMenu();
   
   // Загружаем меню IDR_RESTRICT_MENU
   menuRestrict.LoadMenu(IDR_RESTRICT_MENU);

   // Устанавливаем загруженное меню 
   SetMenu(&menuRestrict);

   // Разрываем связь меню с объектом menuRestrict
   menuRestrict.Detach();
}

//============================================================
// Метод OnFullMenu класса CMultiMenuWindow
// Изменяем меню приложения с IDR_RESTRICT_MENU на 
// IDR_FULL_MENU 
//============================================================
void CMultiMenuWindow::OnFullMenu()
{
   CMenu menuOld;
   CMenu menuRestrict;
   CMenu* pMenu;

   pMenu = this->GetMenu();
   menuOld.Attach(pMenu->m_hMenu);
   menuOld.DestroyMenu();

   menuRestrict.LoadMenu(IDR_FULL_MENU);
   SetMenu(&menuRestrict);
   menuRestrict.Detach();
}

//============================================================
// Метод OnCommand класса CMultiMenuWindow
//============================================================
void CMultiMenuWindow::OnCommand()
{
   MessageBox("Command not implemented");
}

//============================================================
// Метод OnConstruct класса CMultiMenuWindow
// Изменяем состояние флага bRadio
//============================================================
void CMultiMenuWindow::OnConstruct()
{
   // Меняем значение bRadio с TRUE на FALSE и наоборот
   bRadio = !bRadio;
}

//============================================================
// Метод OnExit класса CMultiMenuWindow
//============================================================
void CMultiMenuWindow::OnExit()
{
   // Завершаем приложение
   DestroyWindow();
   return;
}

//============================================================
// Метод OnUpdateProcess класса CMultiMenuWindow
//============================================================
void CMultiMenuWindow::OnUpdateProcess(CCmdUI* pCmdUI)
{
   // Блокируем или разблокируем строку Process меню Mission
   pCmdUI->Enable(bEnable); 
}

//============================================================
// Метод OnUpdateConstruct класса CMultiMenuWindow
//============================================================
void CMultiMenuWindow::OnUpdateConstruct(CCmdUI* pCmdUI)
{
   // Устанавливаем или снимаем пометку 
   // строки Construction меню Mission
   pCmdUI->SetRadio(bRadio);
}

//============================================================
// Метод OnUpdateDisable класса CMultiMenuWindow
//============================================================
void CMultiMenuWindow::OnUpdateDisable(CCmdUI* pCmdUI)
{
   // Устанавливаем или удаляем пометку 
   // у строки Disable меню Menu
   pCmdUI->SetCheck(nCheck);
}

Создайте новый файл ресурсов и включите его в проект под именем MultiMenu.rc. Включите в него два меню, присвоив им идентификаторы IDR_RESTRICT_MENU и IDR_FULL_MENU.

Введите строки этих меню в соответствии с представленным нами файлом ресурсов (листинг 3.2). Для всех строк меню введите их описания. Они будут записаны в файл ресурсов как строковые ресурсы, имеющие одинаковые идентификаторы со строками меню.

Добавьте в файл ресурсов строку Ready, выбрав для нее идентификатор AFX_IDS_IDLEMESSAGE. Эта строка будет отображаться в панели состояния во время “бездействия” приложения.

Включите в файл ресурсов таблицу акселераторов, состоящую из трех команд: ID_MENU_DISABLE, ID_MISSION_PROCESS и ID_FILE_EXIT. Присвойте им комбинации клавиш <Ctrl+D>, <Ctrl+P> и <Ctrl+E> соответственно.

Листинг 3.2. Файл MultiMenu.rc


//Microsoft Developer Studio generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
//////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"

//////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

//////////////////////////////////////////////////////////////
// Russian resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS)
#ifdef _WIN32
LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT
#pragma code_page(1251)
#endif //_WIN32

#ifdef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE DISCARDABLE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE DISCARDABLE 
BEGIN
    "#include ""afxres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE DISCARDABLE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

//////////////////////////////////////////////////////////////
//
// Menu
//

IDR_RESTRICT_MENU MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "P&rocess\tCtrl+P",  ID_MISSION_PROCESS
        MENUITEM SEPARATOR
        MENUITEM "E&xit\tCtrl+E",     ID_FILE_EXIT
    END
    POPUP "&Menu"
    BEGIN
        MENUITEM "Full",              ID_MENU_FULL
        MENUITEM "Disa&ble\tCtrl+D",  ID_MENU_DISABLE
    END
    POPUP "&Help", HELP
    BEGIN
        MENUITEM "Help index",        ID_HELP_HELPINDEX
        MENUITEM SEPARATOR
        MENUITEM "System info",       ID_HELP_SYSTEMINFO
    END
END

IDR_FULL_MENU MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM SEPARATOR
        MENUITEM "E&xit\tCtrl+E",     ID_FILE_EXIT
    END
    POPUP "&Menu"
    BEGIN
        MENUITEM "Restrict",          ID_MENU_RESTRICT
        MENUITEM "Disa&ble\tCtrl+D",  ID_MENU_DISABLE
    END
    POPUP "M&ission"
    BEGIN
        MENUITEM "P&rocess\tCtrl+P",  ID_MISSION_PROCESS
        MENUITEM SEPARATOR
        MENUITEM "Construction",      ID_MISSION_CONSTRUCT
    END
    POPUP "&Help", HELP
    BEGIN
        MENUITEM "Help index",        ID_HELP_HELPINDEX
        MENUITEM "Context help",      ID_HELP_CONTEXTHELP
        MENUITEM SEPARATOR
        MENUITEM "System info",       ID_HELP_SYSTEMINFO
    END
END

//////////////////////////////////////////////////////////////
//
// Accelerator
//

IDR_ACCELERATOR ACCELERATORS DISCARDABLE 
BEGIN
    "D",   ID_MENU_DISABLE,    VIRTKEY, CONTROL, NOINVERT
    "E",   ID_FILE_EXIT,       VIRTKEY, CONTROL, NOINVERT
    "P",   ID_MISSION_PROCESS, VIRTKEY, CONTROL, NOINVERT
END

//////////////////////////////////////////////////////////////
//
// String Table
//

STRINGTABLE DISCARDABLE 
BEGIN
    AFX_IDS_IDLEMESSAGE     "Ready"
END

STRINGTABLE DISCARDABLE 
BEGIN
    ID_FILE_EXIT            "Exit application"
    ID_MISSION_PROCESS      "Process"
    ID_HELP_HELPINDEX       "Open help index"
    ID_HELP_CONTEXTHELP     "Context help"
END

STRINGTABLE DISCARDABLE 
BEGIN
    ID_HELP_SYSTEMINFO      "Display system info"
    ID_MISSION_CONSTRUCT    "Construct"
    ID_MENU_RESTRICT        "Restrict menu"
    ID_MENU_FULL            "Display full menu"
    ID_MENU_DISABLE         "Disable command Process from menu Mission"
END

#endif    // Russian resources
//////////////////////////////////////////////////////////////

#ifndef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//

//////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

Идентификаторы ресурсов приложения MultiMenu определены в файле resource.h. Этот файл создается автоматически редактором ресурсов Microsoft Visual C++. Исходный текст этого файла представлен в листинге 3.3.

Листинг 3.3. Файл resource.h


//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by MultiMenu.rc
//
#define IDR_RESTRICT_MENU               106
#define IDR_FULL_MENU                   107
#define IDR_ACCELERATOR                 108
#define ID_FILE_EXIT                    40009
#define ID_MISSION_PROCESS              40013
#define ID_HELP_HELPINDEX               40014
#define ID_HELP_CONTEXTHELP             40015
#define ID_HELP_SYSTEMINFO              40016
#define ID_MISSION_CONSTRUCT            40017
#define ID_MENU_RESTRICT                40019
#define ID_MENU_FULL                    40020
#define ID_MENU_DISABLE                 40025

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        110
#define _APS_NEXT_COMMAND_VALUE         40027
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

Постройте приложение MultiMenu и запустите его. На экране появится окно приложения с главным меню и панелью состояния (рис. 3.4).

В панели состояния расположен один индикатор. В нем отображается подсказка о выбранной строке меню приложения или системного меню, а если приложение “бездействует” - строка Ready.

Рис. 3.4. Приложение MultiMenu

Сразу после запуска приложения MultiMenu используется меню с идентификатором IDR_FULL_MENU. Если вы выберите из меню Menu строку Restrict, то меню приложения будет заменено на меню IDR_RESTRICT_MENU. Это сокращенный вариант меню IDR_FULL_MENU, в котором отсутствуют некоторые строки, а строка Process перенесена из меню Mission в меню File.

Различные строки меню IDR_FULL_MENU и IDR_RESTRICT_MENU иллюстрируют режимы отображения строк меню. Так, при выборе из меню Menu строки Disable, блокируется строка Process в меню Mission. Около строки Disable при этом отображается символ Ö (рис. 3.3). Чтобы снять блокировку, выберите строку Disable из меню Menu еще раз. Символ Ö также исчезнет.

При выборе строки Process из меню Mission на экране появляется сообщение. Когда строка Process заблокирована, выбрать ее невозможно.

Если вы выберите из меню Mission строку Construction, то она будет выделена символом ·. Повторный выбор этой строки снимает с нее выделение.

Чтобы завершить работу приложения MultiMenu, можно выбрать из меню File строку Exit или выбрать из системного меню приложения строку Close. Все остальные строки меню приложения не работают и заблокированы.

Для ускорения выбора некоторых команд из меню приложения можно воспользоваться клавишами акселерации. В программе определены три такие комбинации.

Комбинация клавиш

Соответствующая строка меню

<Ctrl+D>

Строка Disable из меню Menu

<Ctrl+P>

Строка Process из меню Mission или из меню File (для сокращенного варианта меню)

<Ctrl+E>

Строка Exit из меню File

Как работает приложение MultiMenu

В приложении MultiMenu определены два класса - главный класс приложения CStateApp и класс главного окна приложения CStateWindow.

Главный класс приложения CMultiMenuApp

Главный класс приложения CMultiMenuApp наследуется от базового класса CWinApp. Объект MultiMenuApp класса CMultiMenuApp объявлен как глобальный и создается сразу после запуска приложения.

В класс CMultiMenuApp входит только метод InitInstance. Он создает главное окно приложения, представленное классом CMultiMenuWindow, наследованным от класса CFrameWnd XE "CFrameWnd" .

Класс главного окна приложения CMultiMenuWindow

Класс CMultiMenuWindow управляет главным окном приложения, создает меню, загружает панель управления а также обрабатывает сообщения, в том числе командные сообщения и команды обновления от меню.

Фактически все методы, определенные в классе CMultiMenuWindow, можно условно разделить на три группы. В первую группу попал только один метод OnCreate. Он обрабатывает сообщение WM_CREATE, поступающее в момент создания окна приложения. Вторая группа состоит из шести методов - OnDisable, OnCommand, OnExit, OnConstruct, OnRestrictMenu и OnFullMenu. Эти методы используются для обработки командных сообщений от меню приложения. И, наконец, третья группа методов включает три метода - OnUpdateProcess, OnUpdateConstruct и OnUpdateDisable, которые обрабатывают команды обновления от трех различных строк меню приложения.

В состав класса также входит несколько элементов данных. Это флаги bEnable, bRadio и nCheck, управляющие характеристиками трех строк меню, а также объект m_wndStatusBar класса CStatusBar, представляющий панель состояния нашего приложения.

Рассмотрим отдельные методы класса CMultiMenuWindow более подробно.

Конструктор класса CMultiMenuWindow

Конструктор класса CMultiMenuWindow используется для создания главного окна приложения, подключения меню, загрузки таблицы акселераторов и инициализации флагов.

Для создания окна приложения вызывается метод Create класса CFrameWnd. Обратите внимание, что метод Create создает окно с меню, которое имеет идентификатор IDR_MENU:


Create(NULL, "Status Bar Sample", WS_OVERLAPPEDWINDOW,
      rectDefault, NULL, MAKEINTRESOURCE(IDR_MENU)); 

Затем выполняется загрузка таблицы акселераторов IDR_ACCELERATOR с помощью метода LoadAccelTable класса CFrameWnd:


LoadAccelTable(MAKEINTRESOURCE(IDR_ACCELERATOR));

И, в конце, конструктор устанавливает значение флагов bEnable, bRadio и nCheck. Флагам bEnable и bRadio присваивается значение TRUE, а флагу nCheck - нулевое значение.

Таблица сообщений класса CMultiMenuWindow

Таблица сообщений класса CMultiMenuWindow обрабатывает командные сообщения и команды обновления от меню приложения, а также содержит макрокоманду ON_WM_CREATE.

Макрокоманда ON_WM_CREATE вызывает метод OnCreate во время создания окна:


ON_WM_CREATE()

Для обработки командных сообщений от меню приложения в таблицу сообщений класса CMultiMenuWindow включены шесть макрокоманд ON_COMMAND. Они вызывают обработчики OnDisable, OnConstruct, OnCommand, OnFullMenu, OnRestrictMenu и OnMenuExit:


ON_COMMAND(ID_MENU_DISABLE, OnDisable)
ON_COMMAND(ID_MISSION_CONSTRUCT, OnConstruct)
ON_COMMAND(ID_FILE_EXIT, OnExit)
ON_COMMAND(ID_MISSION_PROCESS, OnCommand)
ON_COMMAND(ID_MENU_RESTRICT, OnRestrictMenu)
ON_COMMAND(ID_MENU_FULL, OnFullMenu)

Для обработки команд обновления в таблицу сообщений класса включены три макрокоманды ON_UPDATE_COMMAND_UI. Они вызывают методы OnUpdateProcess, OnUpdateConstruct и OnUpdateDisable:


ON_UPDATE_COMMAND_UI(ID_MISSION_PROCESS, OnUpdateProcess)
ON_UPDATE_COMMAND_UI(ID_MISSION_CONSTRUCT, OnUpdateConstruct)
ON_UPDATE_COMMAND_UI(ID_MENU_DISABLE, OnUpdateDisable)

Соответствие методов, вызываемых макрокомандами ON_COMMAND и ON_UPDATE_COMMAND_UI, строкам меню приложения вы можете посмотреть в файле ресурсов приложения, представленном в листинге 3.3.

Метод OnCreate класса CMultiMenuWindow

Метод OnCreate класса CMultiMenuWindow сначала вызывает метод OnCreate базового класса CFrameWnd, чтобы создать главное окно приложения:


if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
   return -1;

Затем мы создаем панель состояния, указывая в качестве ее родительского окна главное окно приложения. Для этого мы вызываем метод Create объекта m_wndStatusBar, представляющего панель состояния, передавая ему в качестве параметра значение this. В данном случае это означает, что окно приложения является родительским окном для панели состояния:


if(!m_wndStatusBar.Create(this))
{
   // Ошибка при создании панели состояния
   TRACE0("Failed to create status bar\n");
   return -1;   
}

После того, как панель состояния создана, отображаем на ней единственный индикатор, вызывая метод SetIndicators. В качестве первого параметра передаем методу SetIndicators идентификатор этого единственного индикатора панели состояния, записанный в переменной indicator. Второй параметр метода SetIndicators, равен единице. Он определяет, что индикатор в панели состояния будет только один:


if(!m_wndStatusBar.SetIndicators(&indicator,1))
{
   // Ошибка при установке индикатора
   TRACE0("Failed to set indicators\n");
   return -1;   
}

Более подробно о принципах устройства панелей состояния мы расскажем в отдельном разделе, который носит название “Панель состояния”.

Метод OnDisable класса CMultiMenuWindow

Когда пользователь выбирает из меню Menu строку Disable или нажимает комбинацию клавиш <Ctrl+D>, приложению поступает командное сообщение, которое имеет идентификатор ID_MENU_DISABLE. Для обработки этого сообщения вызывается метод OnDisable класса CMultiMenuWindow. Этот метод изменяет состояние флагов bEnable и nCheck.

Значение флага bEnable изменяется с TRUE на FALSE и наоборот, а значение флага bEnable с 1 на 0 и наоборот:


bEnable = !bEnable;
nCheck = (nCheck == 1) ? 0 : 1; 

Сам метод OnDisable не меняет состояния строк меню приложения, но изменение флагов bEnable и nCheck фиксируется обработчиком команд обновления меню.

Так, флаг bEnable управляет блокировкой строки Process меню Mission (для полного варианта меню) и строки Process меню File (для укороченного варианта меню). Флаг bEnable проверяется методом OnUpdateProcess, который является обработчиком команд обновления от этих строк меню.

Флаг nCheck управляет отображением символа Ö около строки Disable меню Menu. Флаг nCheck проверяется методом OnUpdateDisable, который является обработчиком команд обновления от этой строки меню.

Метод OnCommand класса CMultiMenuWindow

Когда пользователь выбирает строку Process из меню File (для укороченного варианта меню) или из меню Mission (для полного варианта меню), или просто нажимает комбинацию клавиш <Ctrl+P>, приложению поступает командное сообщение, которое имеет идентификатор ID_MISSION_PROCESS. Для обработки этого сообщения вызывается метод CMultiMenuWindow класса CMultiMenuWindow. Данный метод отображает на экране сообщение Command not implemented.

Метод OnConstruct класса CMultiMenuWindow

Когда пользователь выбирает из меню Mission строку Construction, приложению поступает командное сообщение с идентификатором ID_MISSION_CONSTRUCT. Для обработки этого сообщения вызывается метод OnConstruct класса CMultiMenuWindow. Метод OnConstruct изменяет состояние флага bRadio, меняя значение bRadio с TRUE на FALSE и наоборот:


bRadio = !bRadio;

Флаг bRadio управляет отображением символа · около строки Construction меню Mission. Флаг bRadio проверяется методом OnUpdateConstruct, который является обработчиком команд обновления от этой строки меню.

Методы OnRestrictMenu и OnFullMenu класса CMultiMenuWindow

Приложение MultiMenu имеет два меню, полное и укороченное. Вы можете выбирать, какое меню будет использоваться в данный момент, с помощью строки Restrict и Full меню Menu. Если в данный момент используется полный вариант меню, то чтобы заменить его укороченным вариантом, следует выбрать из меню Menu строку Restrict. Для обратной замены меню с укороченного варианта на полный, надо выбрать из меню Menu строку Full.

При выборе строк Restrict и Full приложению передаются командные сообщения с идентификаторами IDR_RESTRICT_MENU и IDR_FULL_MENU, соответственно. Для их обработки вызываются методы OnRestrictMenu и OnFullMenu. По сути, методы OnRestrictMenu и OnFullMenu практически идентичны. Отличие между ними заключается только в том, что метод OnRestrictMenu заменяет текущее меню укороченным вариантом меню (идентификатор меню IDR_RESTRICT_MENU), а метод OnFullMenu меняет текущее меню на полный вариант меню (идентификатор меню IDR_FULL_MENU).

Метод OnRestrictMenu работает следующим образом. Сначала он получает указатель на текущее меню окна приложения. Указатель на объект класса CMenu, представляющий это меню, записывается во временную переменную pMenu:


pMenu = this->GetMenu();

Затем текущее меню удаляется, для чего вызывается метод DestroyMenu:


pMenu->DestroyMenu();

Теперь загружается ресурс нового меню, имеющего идентификатор IDR_RESTRICT_MENU (или IDR_FULL_MENU для метода OnFullMenu):


CMenu menuRestrict;  // Новое меню
menuRestrict.LoadMenu(IDR_RESTRICT_MENU);

Загруженное меню подключается к окну приложения - вызывается метод SetMenu класса окна. В качестве параметра ему передается указатель на объект menuRestrict, представляющий новое меню:


SetMenu(&menuRestrict);

И, наконец, вызывается метод Detach, отпускающий меню в “свободное плавание”, то есть отсоединяющее его от объекта menuRestrict класса CMenu:


menuRestrict.Detach();
Метод OnUpdateProcess класса CMultiMenuWindow

Команды обновления от строк Process меню File и Mission передаются для обработки методу OnUpdateProcess класса CMultiMenuWindow. Этот метод блокирует или снимает блокировку со строки Process в зависимости от значения флага bEnable:


pCmdUI->Enable(bEnable); 
Метод OnUpdateConstruct класса CMultiMenuWindow

Команда обновления от строки Construction меню Mission передается для обработки методу OnUpdateConstruct класса CMultiMenuWindow. Этот метод устанавливает или снимаем отметку · со строки Construction в зависимости от значения флага bRadio:


pCmdUI->SetRadio(bRadio);
Метод OnUpdateDisable класса CMultiMenuWindow

Команда обновления от строки Disable меню Menu передается для обработки методу OnUpdateDisable класса CMultiMenuWindow. Этот метод устанавливает или снимаем отметку Ö со строки Disable, в зависимости от значения флага nCheck:


pCmdUI->SetCheck(nCheck);
Метод OnMenuExit класса CMultiMenuWindow

Пользователь может завершить приложение, выбрав из меню File строку Exit. В этом случае приложению передается командное сообщение с идентификатором ID_FILE_EXIT. Соответствующая макрокоманда ON_COMMAND из таблицы сообщений класса CStateWindow вызывает для обработки этого сообщения метод OnMenuExit:


ON_COMMAND(ID_WORK_EXIT, OnMenuExit)

Метод OnMenuExit завершает работу приложения, для чего вызывает метод DestroyWindow, определенный в классе CWnd, для главного окна приложения:


void CMultiMenuWindow::OnExit()
{
   // Завершаем приложение
   DestroyWindow();
   return;
}

Component Gallery и контекстное меню

Новые операционные системы Windows 95 и Windows NT версии 4.0 и приложения, разработанные для них, значительно шире используют правую кнопку мыши, чем ранние версии Windows. Обычно при нажатии правой кнопки мыши на экране появляется временное меню, внешний вид которого зависит от выбранного объекта.

Современные приложения используют правую клавишу мыши для вывода контекстного меню. В диалоговой панели Component Gallery расположен компонент Pop-up Menu. Он позволяет подключить контекстное меню к любому окну приложения.

Если вы желаете подключить контекстное меню к вашему приложению, выберите в диалоговой Component Gallery панели компонент Pop-up Menu и нажмите кнопку Insert. На экране появится диалоговая панель Pop-up Menu. В списке Add pop-up menu to перечислены классы проекта, представляющие окна и диалоговые панели. К одному из них вы можете добавить контекстное меню. По умолчанию к проекту добавляется новое меню, состоящее из трех строк, которому присваивается идентификатор, состоящий из префикса CG_IDR_POPUP_, названия приложения и части названия класса окна, к которому добавлено меню.

Далее мы опишем добавление компонента Pop-up Menu к приложению Multi, рассмотренному в разделе “Приложение Multi”.

Загрузите в Microsoft Visual C++ проект Multi, откройте диалоговую панель Component Gallery, выберите компонент Pop-up Menu и нажмите кнопку Insert. На экране появится диалоговая панель Pop-up Menu (рис. 3.5). Выберите из списка Add pop-up menu to класс CMultiView.

По умолчанию к проекту добавляется новое меню, состоящее из трех строк, которому присваивается идентификатор CG_IDR_POPUP_MULTI_VIEW.

Рис. 3.5. Диалоговая панель Pop-up Menu

Название идентификатора контекстного меню отображается в поле Menu resource ID диалоговой панели Pop-up Menu. Вы можете заменить его по своему усмотрению.

Нажмите кнопку OK. Диалоговая панель Pop-up Menu закроется. В исходных текстах приложения будут выполнены все необходимые изменения, а к ресурсам добавиться новое меню с идентификатором CG_IDR_POPUP_MULTI_VIEW.

Редактор ресурсов Microsoft Visual C++ позволяет изменять шаблон контекстного меню по вашему усмотрению. Из него можно удалить строки, добавленные Component Gallery по умолчанию, и вставить строки нужные вам.

Когда вы построите проект и запустите приложение, то при нажатии на правую кнопку мыши будет открываться контекстное меню окна над которым расположен указатель мыши.

Рис. 3.6. Контекстное меню, которое использует компонент Pop-up Menu

В файл ресурсов будет добавлено определение контекстного меню CG_IDR_POPUP_MULTI_VIEW. Как видите, оно не отличается от меню, которые вы создавали или использовали ранее, за исключением того, что соответствующее меню верхнего уровня обозначено строкой _POPUP_ (рис. 3.6). Эта строка не будет отображаться в контекстном меню.

Если в приложении имеется несколько окон, то вы можете добавить к каждому окну свое контекстное меню. Для этого вставьте в проект компонент Pop-up Menu несколько раз, указывая в поле Add pop-up menu to различные классы окон. Конечно, каждое вставленное в проект меню может состоять из различного набора строк.

Например, если у вас многооконное приложение, то вы можете вставить компонент Pop-up Menu для главного окна приложения и для окна просмотра. Тогда если вы нажмете правую кнопку мыши в то время, когда указатель мыши находится в окне просмотра, то отображается одно контекстное меню, а если вы нажмете правую кнопку мыши когда ее указатель расположен вне окна просмотра - отображается другое контекстное меню.

Класс CMultiView

Все изменения в программном коде приложения Multi, выполненные при вставке в него компонента Pop-up Menu, происходят только в классе окна, к которому добавляется контекстное меню. Компонент Pop-up Menu добавляет макрокоманду ON_WM_CONTEXTMENU к таблице сообщений класса CMultiView, а также встсавляет в класс CMultiView методы OnContextMenu и PreTranslateMessage.

В определении класса CMultiView добавляется только метод-обработчик OnContextMenu. Все остальные элементы класса не изменяются. После добавления к проекту Pop-up Menu класс CMultiView, определенный в файле MultiView.h будет выглядеть следующим образом:


class CMultiView : public CView
{
protected:
   // CG: Метод OnContextMenu добавлен компонентом Pop-up Menu
   afx_msg void OnContextMenu(CWnd*, CPoint point);

   CMultiView();
   DECLARE_DYNCREATE(CMultiView)

// Attributes
public:
   virtual BOOL PreTranslateMessage(MSG* pMsg);
   CMultiDoc* GetDocument();

// Operations
public:

// Overrides
   //{{AFX_VIRTUAL(CMultiView)
public:
   virtual void OnDraw(CDC* pDC);
   virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
   virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
   virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
   virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
   //}}AFX_VIRTUAL

// Implementation
public:
   virtual ~CMultiView();
#ifdef _DEBUG
   virtual void AssertValid() const;
   virtual void Dump(CDumpContext& dc) const;
#endif

protected:
   //{{AFX_MSG(CMultiView)
   //}}AFX_MSG
   DECLARE_MESSAGE_MAP()
};

Остальные классы приложения остаются без изменения.

Таблица сообщений класса CMultiView

При добавлении контекстного меню к окну класса CMultiView, в таблицу сообщений класса CMultiView добавляется новая макрокоманда ON_WM_CONTEXTMENU:


//////////////////////////////////////////////////////////////
// Таблица сообщений класса CMultiView

// Объекты класса CMultiView создаются динамически
IMPLEMENT_DYNCREATE(CMultiView, CView)

// Таблица сообщений класса CMultiView. В нее добавлена
// макрокоманда ON_WM_CONTEXTMENU
BEGIN_MESSAGE_MAP(CMultiView, CView)
   ON_WM_CONTEXTMENU()
   //{{AFX_MSG_MAP(CMultiView)
   //}}AFX_MSG_MAP
   // Стандартные команды
   ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
   ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
   ON_COMMAND(ID_FILE_PRINT_PREVIEW,CView::OnFilePrintPreview)
END_MESSAGE_MAP()
Метод OnContextMenu класса CMultiView

Когда пользователь нажимает правую кнопку мыши в окне, макрокоманда ON_WM_CONTEXTMENU вызывает метод-обработчик OnContextMenu из класса этого окна. Методу OnContextMenu передаются два параметра:


afx_msg void OnContextMenu(CWnd* pWnd, CPoint pos);

Параметр pWnd содержит указатель на объект класса CWnd. Он представляет окно, в котором находился указатель мыши, когда была нажата правая кнопка мыши. Это может быть окно класса к которому принадлежит таблица сообщений или его дочернее окно.

Параметр pos, представляющий объект класса CPoint, содержит координаты указателя мыши, зафиксированные в момент нажатия правой кнопки мыши.

Реализация метода OnContextMenu добавляется в файле MultiView.cpp:


//////////////////////////////////////////////////////////////
// Метод OnContextMenu класса CMultiView 
// CG: Метод OnContextMenu добавлен компонентом Pop-up Menu
void CMultiView::OnContextMenu(CWnd*, CPoint point)
{
   // Объект menu будет представлять контекстное меню
   CMenu menu;
   // Загружаем меню CG_IDR_POPUP_MULTI_VIEW
   VERIFY(menu.LoadMenu(CG_IDR_POPUP_MULTI_VIEW));

   // Получаем указатель на всплывающее меню 
   CMenu* pPopup = menu.GetSubMenu(0);
   ASSERT(pPopup != NULL);

   // Получаем указатель на объект CWnd, представляющий окно
   //   для которого надо отобразить контекстное меню 
   CWnd* pWndPopupOwner = this;
   while (pWndPopupOwner->GetStyle() & WS_CHILD)
      pWndPopupOwner = pWndPopupOwner->GetParent();

   // Отображаем контекстное меню 
   pPopup->TrackPopupMenu(
      TPM_LEFTALIGN | TPM_RIGHTBUTTON, 
      point.x, point.y,
      pWndPopupOwner);
}

Для вывода контекстного меню на экран используется метод TrackPopupMenu, входящий в класс CMenu. Контекстное меню можно открыть в любом месте экрана. Вне зависимости от расположения меню, все командные сообщения от него передаются одному определенному окну.

Параметры метода TrackPopupMenu задают расположение контекстного меню и выбирают для него окно, в которое будут передаваться командные сообщения:


BOOL TrackPopupMenu( 
   UINT nFlags, 
   int x, 
   int y, 
   CWnd* pWnd, 
   LPCRECT lpRect = 0 
);

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

Если в качестве nFlags указан атрибут TPM_CENTERALIGN, то контекстное меню отображается по центру относительно координаты, указанной параметром x. Если в параметре nFlags установлен атрибут TPM_LEFTALIGN, то параметр x определяет координату левой стороны меню, а если установлен атрибут TPM_RIGHTALIGN - правой.

Кроме атрибутов TPM_CENTERALIGN, TPM_LEFTALIGN или TPM_RIGHTALIGN в параметре nFlags можно установить атрибут TPM_LEFTBUTTON или TPM_RIGHTBUTTON. Атрибут TPM_LEFTBUTTON говорит, что выбор из меню осуществляется нажатием левой, а TPM_RIGHTBUTTON - правой кнопкой мыши.

Назначение параметра x зависит от атрибутов, установленных в параметре nFlags. Параметр y во всех случаях указывает расположение верхней стороны меню. Координаты x и y указываются в экранных координатах.

Параметр pWnd должен содержать указатель на объект класса CWnd, представляющий окно, в которое будут передаваться все командные сообщения от контекстного меню.

Контекстное меню закрывается, если вы нажмете на кнопку мыши вне меню. Параметр lpRect позволяет указать прямоугольник, внутри которого нажатие на кнопку мыши не будет вызывать закрытия меню. Если этот параметр равен NULL, меню закрывается, если пользователь нажал кнопку мыши в любом месте экрана вне меню.

В случае успешного завершения, метод TrackPopupMenu ненулевое значение, а в противном случае нуль.

Метод PreTranslateMessage класса CMultiView

Кроме добавления новой макрокоманды к таблице сообщений класса CMultiView и соответствующего метода-обработчика OnContextMenu, компонент Pop-up Menu добавляет метод PreTranslateMessage к классу CMultiView.

В него записывается программный код, который обнаруживает нажатие комбинации клавиш <Shift+F10> или специальной клавиши на клавиатуре с дополнительными клавишами Windows 95 и напрямую вызывает метод OnContextMenu:


//////////////////////////////////////////////////////////////
// Метод PreTranslateMessage класса CMultiView

BOOL CMultiView::PreTranslateMessage(MSG* pMsg)
{
   // CG: Следующий блок добавлен компонентом Pop-up Menu 
   {
   // Если нажата комбинация клавиш 
      if((((pMsg->message == WM_KEYDOWN || 
               pMsg->message == WM_SYSKEYDOWN) && 
           (pMsg->wParam == VK_F10) && 
           (GetKeyState(VK_SHIFT) & ~1)) != 0) ||
           // it's Shift+F10 OR Natural keyboard key
              (pMsg->message == WM_CONTEXTMENU))
      {
   // Определяем экранные координаты клиентской части окна
         CRect rect;
         GetClientRect(rect);
         ClientToScreen(rect);
   // Записываем в объект point класса CPoint координаты 
   // левого верхнего угла клиентской части окна, добавляя 
   // к нему смещения в 5 пикселов по горизонтали и вертикали
         CPoint point = rect.TopLeft();
         point.Offset(5, 5);
   // Отображаем контекстное меню в позиции point
         OnContextMenu(NULL, point);

   // Возвращаем значение TRUE, так как сообщение обработано
         return TRUE;
      }
   }

   // Вызываем метод PreTranslateMessage базового класса CView
   return CView::PreTranslateMessage(pMsg);
}
[Назад] [Содеожание] [Дальше]