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

    На этом шаге мы рассмотрим создание и использование контекстно-зависимого меню.

    Существует возможность создания контекстно-зависимого меню, благодаря одной функции TrackPopupMenu() Windows API, версия которой включена в класс TMenu OWL. Следующий пример программы представляет собой версию примера из шага 14, которая позволяет вам выводить меню Test в любом месте окна посредством нажатия правой кнопки мыши.

#include <owl\applicat.h>
#include <owl\framewin.h>
#include <owl\menu.h>
#include "pr17_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 EvRButtonDown(UINT, TPoint &point);
	 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_WM_RBUTTONDOWN,

  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::EvRButtonDown()
// Эта функция реагирует на команды WM_RBUTTONDOWN,
// создавая контекстно-зависимое меню по координатам
// курсора мыши.
void TWndw::EvRButtonDown(UINT, TPoint &point)
{
  // Получить дескриптор меню Test.
  HMENU hMenu = menu->GetSubMenu(1);
  // Создать объект TPopupMenu  меню Test.
  TPopupMenu(hMenu).TrackPopupMenu(0, point.x, point.y, 0, HWindow, 0);
}

// TWndw::CmAddItem()
// Эта функция  отвечает на сообщение CM_ADDITEM,
// добавляя новый пункт в контекстно-зависимое меню.
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_ADDITEM       201
#define CM_ADDPOPUP      202
#define CM_DELETEITEM    203
#define CM_DELETEPOPUP   204
#define CM_CHECK         205
#define  CM_ITEM_ID      100
#define CM_EXIT        24310

#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. Результат работы приложения

    Место, в котором появится ваше меню, зависит, конечно, от места, в котором вы нажмете кнопку мыши. Вы можете использовать эту плавающую версию меню Test так же, как и первоначальную. Собственно это плавающее меню и есть первоначальное, только оно отображается в различных местах.

    Программа создает плавающее меню, когда вызывается функция ответа на сообщение EvRButtonDown():

void TWndw::EvRButtonDown(UINT, TPoint &point)
{
  // Получить дескриптор меню Test.
  HMENU hMenu = menu->GetSubMenu(1);
  // Создать объект TPopupMenu  меню Test.
  TPopupMenu(hMenu).TrackPopupMenu(0, point.x, point.y, 0, HWindow, 0);
}

    Перед тем, как EvRButtonDown() сможет создать контекстно-зависимое меню, она должна получить дескриптор пункта меню для копирования. Функция выполняет это путем вызова функции GetSubMenu() главного меню. После получения дескриптора пункта меню Test, вызов функции TrackPopupMenu() класса Tmenu отображает контекстно-зависимое меню в соответствующем месте и следит за выбором пользователя. Первый параметр TrackPopupMenu () не используется и всегда равен нулю. Второй и третий параметры представляют собой координаты х и у, по которым выводится контекстно-зависимое меню. Четвертый параметр - зарезервированное слово, которое должно быть установлено в нуль, а пятый параметр является дескриптором окна, которое будет получать сообщения от контекстно-зависимого меню.

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

    Со следующего шага мы начнем знакомиться с диалоговыми окнами.




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