Шаг 14.
Библиотека OWL.
Действия с объектами меню (начало)

    На этом шаге мы начнем рассматривать возможные действия с объектами меню.

    ObjectWindows включает в себя три класса меню, которые вы можете использовать в своих программах. Класс ТМеnu представляет собой меню общего вида и содержит большинство функций, необходимых для работы с меню. Класс TSystemMenu порожден из TMenu и является специальным классом, позволяющим работать с системными меню. Наконец, класс TPopupMenu, также порожденный из ТМеnu, является специальным классом всплывающих меню.

    Вы можете создать объект меню различными способами, включая создание пустого объекта ТМеnu или создание объекта ТМеnu с использованием существующего меню. После создания вами объекта меню, соответствующего меню Windows, вы можете использовать функции-члены класса меню для работы с ним так, как в обычной Windows-программе.


    Приложения Windows обычно содержат два типа меню: меню верхнего уровня, которое представляет собой меню, появляющееся в строке меню приложения, и всплывающие меню (или подменю), которые представляют собой меню, появляющиеся при нажатии вами кнопки мыши, курсор которой установлен на каком-либо пункте меню верхнего уровня. Именно всплывающие меню (подменю) содержат команды, которые пользователь выбирает из меню. В любом случае вам необходимо рассматривать каждое меню как отдельный объект, поскольку они действительно таковыми являются. Меню верхнего уровня и все подменю имеют свои собственные дескрипторы.

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

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

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

//Класс основного окна
class TWndw:public TFrameWindow
{
  public:
	 TWndw(TWindow *parent,const char far *title);
  protected:
	 TMenu *menu, *subMenu;
	 BOOL haveItem, fitMoreItems;

	 void SetupWindow();
	 void CleanupWindow();
	 void CmAddItem();
	 void CmAddPopup();
	 void CmDeleteItem();
	 void CmDeletePopup();
	 void CmCheck() ;
	 // Переключатели состояния команд.
	 void CmEnableAddItem (TCommandEnabler &commandEnabler);
	 void CmEnableAddPopup (TCommandEnabler &commandEnabler);
	 void CmEnableDeleteItem (TCommandEnabler &commandEnabler);
	 void CmEnableDeletePopup (TCommandEnabler &commandEnabler);

	 DECLARE_RESPONSE_TABLE (TWndw);
};

DEFINE_RESPONSE_TABLE1 (TWndw, TFrameWindow)
  EV_COMMAND(CM_ADDITEM,CmAddItem),
  EV_COMMAND(CM_ADDPOPUP,CmAddPopup),
  EV_COMMAND(CM_DELETEITEM,CmDeleteItem),
  EV_COMMAND(CM_DELETEPOPUP,CmDeletePopup),
  EV_COMMAND(CM_CHECK,CmCheck),
  EV_COMMAND_ENABLE(CM_ADDITEM,CmEnableAddItem),
  EV_COMMAND_ENABLE(CM_ADDPOPUP,CmEnableAddPopup),
  EV_COMMAND_ENABLE(CM_DELETEITEM,CmEnableDeleteItem),
  EV_COMMAND_ENABLE(CM_DELETEPOPUP,CmEnableDeletePopup),
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::SetupWindow()
// Эта функция  переопределяет функцию SetupWindow()
// класса TFrameWindow и вызывается в промежутке между созданием окна
// и его отображением. SetupWindow() удобное место для завершения
// действий по инициализации, что здесь и продемонстрировано.
void TWndw::SetupWindow()
{
  // Создать объект меню из меню окна.
  menu = new TMenu (HWindow);
  // Инициализировать указатель подменю.
  subMenu = NULL;
  // Инициализировать флаги.
  haveItem = FALSE;
  fitMoreItems = TRUE;
}

// TWndw::CleanupWindow()
// Эта функция  переопределяет функцию CleanupWindow().
// Она выполняет завершающую очистку содержимого окна
// перед закрытием приложения.
void TWndw::CleanupWindow()
{
  delete menu;
  if (subMenu)   delete subMenu;
}

// TWndw::CmAddItem()
// Эта функция  отвечает на сообщение CM_ADDITEM,
// добавляя новый пункт во всплывющее меню Test.
void TWndw::CmAddItem()
{
  // Получить дескриптор меню Test.
  HMENU hMENU = menu->GetSubMenu(1);
  // Создать объект TMenu для меню Test.
  TMenu sMenu(hMENU);
  // Получить число пунктов в меню Test.
  int count = sMenu.GetMenuItemCount();
  // Если меню содержит менее 10 пунктов...
  if (count<10)
  {
	  // Добавиь новый пункт в меню.
	  sMenu.AppendMenu(MF_STRING,CM_ITEM_ID,"New Item");
	  // Если это последний пункт, который может быь добавлен...
	  if(count==9) fitMoreItems=FALSE;
	  // Указать, что меню содержит добавленные пункты.
		haveItem=TRUE;
	}
}

// TWndw::CmAddPopup()
// Эта функция  отвечает на команды CM_ADDPOPUP,
// добавляя новое всплывающее меню в строку меню.
void TWndw::CmAddPopup()
{
 // Если новое подменю еще не создано...
 if(!subMenu)
  {
	 // Создать объект TPopupMenu для нового окна.
	 subMenu=new TPopupMenu();
	 // Добавить пункт в новое меню.
	 subMenu->AppendMenu(MF_STRING,CM_ITEM_ID,"New Item");
	 // Добавитьновое меню в строку меню.
	 menu->AppendMenu(MF_POPUP,(UINT)(HMENU)*subMenu,"New Popup");
	 // Отобразить новую сроку меню.
	 DrawMenuBar();
  }
}

// TWndw::CmDeleteItem()
// Эта функция отвечает на команды CM_DELETEITEM,
// удаляя пункт в меню Test.
void TWndw::CmDeleteItem()
{
  // Получить ключ меню Test.
  HMENU hMenu=menu->GetSubMenu(1);
  // Создать объект TMenu для меню Test.
  TMenu sMenu(hMenu);
  // Получить число пунктов в меню Test.
  int count=sMenu.GetMenuItemCount();
  // Если в меню имеются новые пункты...
  if(count>5)
	{
	  // Удалить новый пункт меню.
	  sMenu.DeleteMenu(count-1,MF_BYPOSITION);
	  // Указать, что существует место для еще одного пункта меню.
	  fitMoreItems=TRUE;
	  // Если это последний пункт, который может быть удален...
	  if (count-1==5)
			//...указать, что меню не содержит новых пунктов.
			haveItem=FALSE;
	}
}

// TWndw::CmDeletePopup()
// Эта функция отвечает на команды CM_DELETEPOPUP,
// удаляя новое всплывающее меню, если такое существует.
void TWndw::CmDeletePopup()
{
	// Если существует всплывающее меню...
	if(subMenu)
	{
	// Удалить всплывающее меню.
	menu->DeleteMenu(2,MF_BYPOSITION);
	// Удалить объект TMenu.
	delete subMenu;
	// Перерисовать строку меню.
	DrawMenuBar();
	// Указать, что меню больше не существует.
	subMenu=NULL;
	}
}

// TWndw::CmCheck()
// Эта функция отвечает на команды CM_CHECK,
// помечая или снимая отметку с пункта меню Test/Check.
void TWndw::CmCheck()
 {
	// Получить текущее состояние  пункта меню.
	int state=menu->GetMenuState(CM_CHECK,MF_BYCOMMAND);
	// Если пункт меню не помечен...
	if (state == MF_UNCHECKED)
	{
	  // Изменить на Uncheck.
	  menu->ModifyMenu(CM_CHECK,MF_STRING,CM_CHECK, "&UnCheck");
	  // Добавить индикатор состояния к пункту меню.
	  menu->CheckMenuItem(CM_CHECK,MF_CHECKED);
	}
	else //Иначе, если пункт меню не помечен...
	{
	  // Изменить на Check.
	  menu->ModifyMenu(CM_CHECK,MF_STRING,CM_CHECK, "&Check");
	  // Удалить маркер.
	  menu->CheckMenuItem(CM_CHECK,MF_UNCHECKED);
	}
}

// TWndw::CmEnableAddItem()
// Эта функция является переключателем доступности
// команды для пункта меню Test/Add и определяет, является ли
// пункт меню разрешенным, основываясь на том,
// может ли это меню содержать еще дополнительные пункты.
void TWndw::CmEnableAddItem(TCommandEnabler &commandEnabler)
{
	commandEnabler.Enable(fitMoreItems);
}

// TWndw::CmEnableAddPopup()
// Эта функция является переключателем доступности
// команды для всплывающего меню Test/Add и определяет,
// является ли пункт меню разрешенным, основываясь
// на том, существует ли новое всплывающее меню.
void TWndw::CmEnableAddPopup(TCommandEnabler &commandEnabler)
{
	commandEnabler.Enable(!subMenu);
}

// TWndw: :CmEnableDeleteItem()
// Эта функция является переключателем доступности
// команды для пункта меню Test/Delete и определяет,
// является ли пункт меню разрешенным, основываясь на том,
// содержит ли меню пункты, которые можно удалить.
void TWndw::CmEnableDeleteItem(TCommandEnabler &commandEnabler)
{
	commandEnabler.Enable(haveItem);
}

//TWndw::CmEnableDeletePopup()
//Эта функция является переключателем доступности
//команды для всплывающего меню Test/Delete и
//определяет, является ли пункт меню разрешенным,
//основываясь на том, существует ли подменю.
void TWndw::CmEnableDeletePopup(TCommandEnabler &commandEnabler)
{
	commandEnabler.Enable((int)subMenu);
}

void TApp::InitMainWindow()
{
  TFrameWindow *wndw=new TWndw(0,"Действия с объектами меню");
  SetMainWindow(wndw);
}

int OwlMain(int,char *[])
{
  return TApp().Run();
}

    Файл ресурсов:

#define CM_CHECK	106
#define CM_DELETEPOPUP	105
#define CM_DELETEITEM	104
#define CM_ADDPOPUP	103
#define CM_ADDITEM	102
#define CM_EXIT	24310
#define CM_ITEM_ID    100

#ifdef RC_INVOKED
MENU_1 MENU
{
 POPUP "&File"
 {
  MENUITEM "E&xit", CM_EXIT
 }
 POPUP "&Test"
 {
  MENUITEM "&Add Item", CM_ADDITEM
  MENUITEM "Add &Popup", CM_ADDPOPUP
  MENUITEM "&Delete Item", CM_DELETEITEM, INACTIVE
  MENUITEM "Delete P&opup", CM_DELETEPOPUP, INACTIVE
  MENUITEM "&Check", CM_CHECK
 }
}
#endif
Текст этого приложения можно взять здесь.

    Запустив эту программу, вы увидите окно, подобное изображенному на рисунке 1.


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

    Меню File содержит только один пункт - Exit, - который вы можете выбрать для выхода из программы. Меню Test содержит пять команд:

    Если вы выбираете пункт Check, вслед за пунктом появляется маркер, и имя пункта изменяется на Uncheck. Повторный выбор этого пункта возвращает программу в первоначальное состояние. Выбор команды Add Item добавляет подпункты в меню Test. Как показано на рисунке 2, вы можете добавлять до пяти пунктов меню.


Рис.2. Меню с вновь добавленными подпунктами

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

    Вы можете добавить новый пункт в строку меню, выбрав команду Add Popup. Рисунок 3 показывает основное окно с новым пунктом в строке меню. Заметим, что эта команда не только добавляет новое меню, но и обеспечивает его одним пунктом меню. Когда вы создали новый пункт меню, команда Delete Popup становится доступной. Выбирайте эту команду для удаления нового пункта из строки меню.


Рис.3. Новый пункт в строке меню

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

#define CM_CHECK	106
#define CM_DELETEPOPUP	105
#define CM_DELETEITEM	104
#define CM_ADDPOPUP	103
#define CM_ADDITEM	102
#define CM_EXIT	24310
#define CM_ITEM_ID    100

    В примере можно видеть, что класс главного окна содержит - кроме конструктора и функции SetupWindow() - функцию CleanupWindow() и функции откликов на сообщения меню:

	 void SetupWindow();
	 void CleanupWindow();
	 void CmAddItem();
	 void CmAddPopup();
	 void CmDeleteItem();
	 void CmDeletePopup();
	 void CmCheck() ;

    Функция CleanupWindow() выполняет действия, противоположные SetupWindow(). Вообще, объекты или данные, созданные в SetupWindow(), удаляются в CleanupWindow(), которую OWL вызывает немедленно перед вызовом деструктора класса.

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

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

    OWL предоставляет механизм быстрого и простого управления поведением экземпляров класса TFrameWindow. Этот механизм называется переключением активности команд. Суть его в том, что вы обеспечиваете переключатель доступности команды для каждого пункта меню, который хотите разрешить или запретить.

    Первым шагом в выполнении этого процесса является объявление переключателей доступности команд в классе вашего окна:

	 void CmEnableAddItem (TCommandEnabler &commandEnabler);
	 void CmEnableAddPopup (TCommandEnabler &commandEnabler);
	 void CmEnableDeleteItem (TCommandEnabler &commandEnabler);
	 void CmEnableDeletePopup (TCommandEnabler &commandEnabler);

    Эти четыре переключателя доступности команд находятся в приведенном примере. Каждый переключатель доступности команд имеет в качестве единственного параметра ссылку на объект TCommandEnabler. TCommandEnabler представляет собой абстрактный класс, определенный в TWindow. Этот класс содержит функцию Enable(), которая разрешает или запрещает пункты меню, и две виртуальные функции, SetText() и SetCheck(), которые управляют текстовой строкой пункта меню или проверяют состояние (то есть включают или выключают индикатор состояния). Для создания такого класса TFrameWindow наследует класс TMenuItemEnabler из TCommandEnabler, переопределяя пустые виртуальные функции SetText() и SetCheck().

    Что касается функций переключателя доступности команд из приведенного примера, по их именам можно судить, какими пунктами меню они управляют. Например, функция CmEnableAddItem() является переключателем доступности команды Add Item меню Test. Вы можете назвать ваш переключатель доступности так, как вам будет угодно, но лучше всего условиться, что имя начинается с CmEnable, за которым следует имя команды пункта из строчных и прописных букв.

    После того, как вы объявили переключатели доступности команд, вы должны добавить их в таблицу откликов класса. Для этого используется макрос EV_COMMAND_ENABLE. Таблица откликов из приведенного примера показывает, как это выполняется:

DEFINE_RESPONSE_TABLE1 (TWndw, TFrameWindow)
  EV_COMMAND(CM_ADDITEM,CmAddItem),
  EV_COMMAND(CM_ADDPOPUP,CmAddPopup),
  EV_COMMAND(CM_DELETEITEM,CmDeleteItem),
  EV_COMMAND(CM_DELETEPOPUP,CmDeletePopup),
  EV_COMMAND(CM_CHECK,CmCheck),
  EV_COMMAND_ENABLE(CM_ADDITEM,CmEnableAddItem),
  EV_COMMAND_ENABLE(CM_ADDPOPUP,CmEnableAddPopup),
  EV_COMMAND_ENABLE(CM_DELETEITEM,CmEnableDeleteItem),
  EV_COMMAND_ENABLE(CM_DELETEPOPUP,CmEnableDeletePopup),
END_RESPONSE_TABLE;

    Подобно созданию макросов EV_COMMAND, вы пишете EV_COMMAND_ENABLE в таблицу откликов со следующими за его идентификатором пункта меню и соответствующим именем функции. Теперь, однако, вы включаете не функции ответа на сообщения команд, а функции - переключатели доступности команд.

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

void TWndw::CmEnableAddItem(TCommandEnabler &commandEnabler)
{
    commandEnabler.Enable(fitMoreItems);
}

    Переключатели доступности команд обычно короткие и делают не многим более, чем просто вызов объекта TCommandEnabler (в данном случае commandEnabler), разрешающего или запрещающего пункт меню. Здесь можно, как вы увидите позже, изменить текст пункта меню или переключить маркер пункта.

    Функция Enable() имеет в качестве единственного аргумента булево значение, которое определяет, следует ли разрешить (TRUE) или блокировать (FALSE) выполнение пункта меню. Для использования преимуществ механизма разрешения команды вам необходим способ отслеживания состояния пунктов меню, которые были вызваны. Можно выполнить это, как показано в предыдущем фрагменте программы, используя булевы флажки, которые вы устанавливаете в других функциях вашей программы.

    Теперь, когда переключатели доступности команд находятся на своем месте, вы можете просто установить булев флаг пункта меню и позволить переключателю доступности команды управлять остальным автоматически. В приведенном примере класс главного окна использует два таких флага: haveItem и fitMoreItems, которые определяют, содержит ли меню Test новые подпункты.

    Класс также содержит два указателя на объект TMenu - menu и subMenu. Первый указатель осуществляет доступ к объекту TМеnu, представляющему главное меню, которое появляется при первом запуске программы. Второй указатель выполняет доступ к новому пункту меню, когда оно появляется в строке меню. Указатель subMenu также действует как булево значение в функциях - переключателях доступности команд, связанных с новым всплывающим меню. Это означает, что когда он равен NULL, подменю не существует.

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




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