Шаг 13.
Библиотека OWL.
Создание главного меню

    На этом шаге мы рассмотрим создание простого меню.

    Одной из отличительных черт Windows-программы является строка меню. Почти все приложения Windows используют меню, что подразумевает определенные правила их построения. Следуя этим правилам, пользователь отыскивает известные ему команды в ожидаемом месте независимо от того, какое приложение активно в данный момент. Для работы с основным меню служит OWL-класс TМenu.

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

    Для управления стандартной строкой меню без поддержки специальных режимов вам необходимо только создать меню, используя Resource Workshop или другой редактор ресурсов, добавить функции, отвечающие на сообщения в вашем классе окна, и макрос EV_COMMAND в вашу таблицу откликов. Затем после загрузки меню в вашей программе, оно само о себе позаботится, и всякий раз, когда пользователь выберет какой-нибудь пункт меню, будет вызвана соответствующая функция обработки командного сообщения меню.

    Работу с меню начнем с создания нового проекта, который будет объединять в себе все файлы, в совокупности составляющие приложение. Для этого выберем пункт меню Project | New Project. В появившемся окне в поле Project Path and Name указываем имя проекта и путь доступа к нему. В поле Target Type выбираем Application (.exe), Platform устанавливаем в Win32 и включаем библиотеку BWCC.


Рис.1. Задание начальных условий

    Проект содержит файлы *.cpp, *.rc и *.def. Файл *.def нам не понадобится, поэтому мы его удаляем (щелкаем правой клавишей мыши на имени этого файла и выбираем пункт Delete node).


Рис.2. Структура проекта

    Меню мы будем создавать с помощью редактора ресурсов Resource Workshop. Для этого щелкаем на имени файла *.rc правой кнопкой мыши и в появившемся контекстно-зависимом меню выбираем пункт View | Edit Resource:


Рис.3. Контекстно-зависимое меню

    В появившемся окне Resource Workshop выбираем пункт меню Resource | New и в поле Resource type - MENU, затем нажимаем кнопку ОК:


Рис.4. Выбор типа ресурса

    В поле Item text задается название пункта меню, а в Item Id - имя константы для данного пункта (оно обычно формируется из префикса CM_ и следующего за ним имени сообщения, определяемого пунктом меню). Для добавления нового подпункта меню используется Menu | New menu item. Для разделения пунктов меню горизонтальной чертой в поле Item type устанавливаем Separator:


Рис.5. Создание меню


    Пример. Создадим один пункт меню File, содержащий четыре подпункта New, Open, Save и Exit, причем последний отделен от остальных горизонтальной чертой. Ниже приведен текст файла ресурсов pr13_1.rc.
#define CM_FILEEXIT	24310
#define CM_FILESAVE	103
#define CM_FILEOPEN	101
#define CM_FILENEW	102

#ifdef RC_INVOKED
MENU_1 MENU
{
 POPUP "&File"
 {
  MENUITEM "&New...", CM_FILENEW
  MENUITEM "&Open...", CM_FILEOPEN
  MENUITEM "&Save...", CM_FILESAVE
  MENUITEM SEPARATOR
  MENUITEM "&Exit", CM_FILEEXIT
 }
}
#endif 

    Символ & перед буквой в строке текста пункта меню указывает, что пункт меню может быть выбран с помощью этой буквы. При отображении текста пункта меню, эта буква подчеркивается.

    Для просмотра и редактирования файла ресурсов, вызываем контекстно-зависимое меню (щелкаем правой кнопкой по имени файла ресурсов) и выбираем View | Text Edit. В исходном тексте удалим строку:

    #define MENU_1 1

    Необходимо также добавить директивы:

    #ifdef RC_INVOKED
    #endif

    Директива #ifdef RC_INVOKED позволяет вам расположить все константы идентификаторов команд в вашем файле ресурсов и включать файл ресурсов в любой модуль, в котором необходимо использовать эти константы. При компиляции исходной С++-программы константа RC_INVOKED имеет значение false, и в текущий файл включаются только пункты, определенные до #ifdef RC_INVOKED. Однако, когда вызывается компилятор ресурсов для компиляции файлов ресурсов, RC_INVOKED имеет значение true, и это означает, что как константы до директивы, так и действующий код ресурсов будут включены в файл. Рассматривая все константы как ресурсы и помещая их только в файл ресурсов, вы можете быть уверены, что все константы определены корректно во всех файлах, включенных в проект.

    Установим идентификатор для команды Exit в 24310, чтобы OWL автоматически управляла процессом закрытия приложения.

    Приведем текст программы, демонстрирующей созданное меню.

#include <owl\applicat.h>
#include <owl\framewin.h>
#include "pr13_1.rc"

//Класс приложения
class TApp: public TApplication
{
  public:
	 TApp():TApplication(){}
	 void InitMainWindow();
};

//Класс основного окна
class TWndw:public TFrameWindow
{
  public:
	 TWndw(TWindow *parent, const char far *title);
  protected:
	 void EvLButtonDown(UINT,TPoint &point);
	 BOOL CanClose();
	 void CmFileNew();
	 void CmFileOpen();
	 void CmFileSave();
	 DECLARE_RESPONSE_TABLE (TWndw);
};

DEFINE_RESPONSE_TABLE1 (TWndw, TFrameWindow)
	 EV_COMMAND(CM_FILENEW, CmFileNew),
	 EV_COMMAND(CM_FILEOPEN, CmFileOpen),
	 EV_COMMAND(CM_FILESAVE, CmFileSave),
END_RESPONSE_TABLE;


TWndw::TWndw(TWindow *parent,const char far *title):
		  TFrameWindow(parent,title)
{
	// Добавить меню к главному окну.
	AssignMenu("MENU_1");
	// Определить расположение и размеры окна.
	Attr.X=50;
	Attr.Y=50;
	Attr.W=GetSystemMetrics(SM_CXSCREEN)/4;
	Attr.H=GetSystemMetrics(SM_CXSCREEN)/5;
}

// TWndw::CmFileNew()
// Эта функция  отвечает на сообщение CM_FILENEW,
// которое генерируется при выполнении команды File New.
void TWndw::CmFileNew()
{
  MessageBox("Выполнен пункт File | New", "Сообщение", MB_OK);
}

// TWndw::CmFileOpen()
// Эта функция  отвечает на сообщение CM_FILEOPEN,
// которое генерируется при выполнении команды File Open.
void TWndw::CmFileOpen()
{
  MessageBox("Выполнен пункт File | Open", "Сообщение", MB_OK);
}

// TWndw::CmFileSave()
// Эта функция  отвечает на сообщение CM_FILESAVE,
// которое генерируется при выполнении команды File Save.
void TWndw::CmFileSave()
{
  MessageBox("Выполнен пункт File | Save", "Сообщение", MB_OK);
}

BOOL TWndw::CanClose()
{
  int result=MessageBox("Вы действительно хотите закрыть окно?","Закрытие",
					 MB_YESNO | MB_ICONQUESTION);
  if (result==IDYES) return TRUE;
  else return FALSE;
}

void TApp::InitMainWindow()
{
  TFrameWindow *wndw=new TWndw(0,"Первый пример меню");
  SetMainWindow(wndw);
}

int OwlMain(int,char *[])
{
  return TApp().Run();
}
Текст этого приложения можно взять здесь.

    Резельтат работы приложения изображен на рисунке 6:


Рис.6. Результат работы приложения

    Наряду с заголовочными файлами библиотеки OWL подключаем созданный нами файл ресурсов pr13_1.rc.

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

#define CM_FILEEXIT	24310
#define CM_FILESAVE	103
#define CM_FILEOPEN	101
#define CM_FILENEW	102

    Имя константы обычно формируется из префикса СМ_ (сокращение от Command Message) и следующего за ним имени сообщения, определяемого пунктом меню, для которого задается константа. Часто оно представляет собой имя меню, сопровождаемое именем пункта меню.

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

class TWndw:public TFrameWindow
{
  public:
	 TWndw(TWindow *parent,const char far *title);
  protected:
	 void EvLButtonDown(UINT,TPoint &point);
	 BOOL CanClose();
	 void CmFileNew();
	 void CmFileOpen();
	 void CmFileSave();
	 DECLARE_RESPONSE_TABLE (TWndw);
};

    Теперь у нас есть три функции отклика на сообщения, по одной на каждый пункт меню. Нам не нужна функция для выбора из строки меню - в данном случае File, - поскольку Windows при нажатии пользователем клавиши мыши, курсор которой установлен на имени меню, автоматически отображает соответствующее меню. Исключением из данного правила является пункт строки меню, который не имеет соответствующего всплывающего меню, сам является командой, но это встречается крайне редко. Не нужно также определять отвечающую на сообщение функцию для команды Exit, поскольку OWL управляет ею автоматически, если ее идентификатор установлен в 24310. Функции отклика на сообщение меню могут быть названы как угодно, но в обычной практике их названия происходят от имен сообщений, на которые отвечают. Обычно создание имени функции осуществляется удалением символа подчеркивания из идентификатора команды и записью имени функции прописными и строчными буквами. Например, CM_FILENEW становится CmFileNew().

    Связь функций ответов на сообщения с сообщениями, которые они представляют, осуществляется с помощью добавления макроса EV_COMMMD в таблицу откликов оконного класса, как это выполнено в примере:

DEFINE_RESPONSE_TABLE1 (TWndw, TFrameWindow)
	 EV_COMMAND(CM_FILENEW, CmFileNew),
	 EV_COMMAND(CM_FILEOPEN, CmFileOpen),
	 EV_COMMAND(CM_FILESAVE, CmFileSave),
END_RESPONSE_TABLE;

    Как мы уже говорили, OWL имеет заранее определенные макросы таблицы откликов для каждого стандартного сообщения Windows. Для использования их необходимо включить соответствующий макрос как точку входа в таблицу (например, EV_WM_LBUTTONDOWN). Однако OWL не знает, какая команда включается в меню. Говоря прямо, в данном случае проблемой является создание элементов таблицы откликов, соответствующих сообщениям создаваемого меню. В этом случае можно воспользоваться макросом EV_COMMAND:

    EV_COMMAND (CM_FILENEW, CmFileNew),

    Макрос EV_COMMAND содержит в своих круглых скобках идентификатор сообщения-команды меню и имя функции, реагирующей на эту команду. В показанном примере макрос EV_COMMAND сообщает OWL, что сообщение-команда CM_FILENEW должно быть переправлено функции CmFileNew(). Создав в таблице отклика по одному EV_COMMAND-входу каждого пункта меню, вы сможете реагировать на команды, которые пользователь выбирает из созданного меню.

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

TWndw::TWndw(TWindow *parent,const char far *title):
		  TFrameWindow(parent,title)
{
	// Добавить меню к главному окну.
	AssignMenu("MENU_1");
	// Определить расположение и размеры окна.
	Attr.X=50;
	Attr.Y=50;
	Attr.W=GetSystemMetrics(SM_CXSCREEN)/4;
	Attr.H=GetSystemMetrics(SM_CXSCREEN)/5;
}

    Для загрузки меню вызывается функция AssignMenu() вашего окна, которую оно наследует из TWindow. Эта функция требует в качестве единственного параметра имя меню или идентификатор его ресурса. AssignMenu() вызывает функцию Windows API с таким же именем, которая добавляет меню в ваше основное окно. После вызова AssignMenu() остается только отобразить окно; строка меню окна отображается автоматически.

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

void TWndw::CmFileNew()
{
  MessageBox("Выполнен пункт File | New", "Сообщение", MB_OK);
}

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

    На следующем шаге мы рассмотрим действия с объектами меню.




Предыдущий шаг Содержание Следующий шаг