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

    На этом шаге мы продолжим описание работы программы из предыдущего шага.

    Класс TMenu представляет много функций, которые зеркально отображают функции управления меню Windows API. Но перед тем, как получать доступ к функциям TMenu, вы должны создать объект класса TMenu на основе меню вашего окна. В примере из предыдущего шага это делается с помощью функции SetupWindow() класса основного окна:

void TWndw::SetupWindow()
{
  // Создать объект меню из меню окна.
  menu = new TMenu (HWindow);
  // Инициализировать указатель подменю.
  subMenu = NULL;
  // Инициализировать флаги.
  haveItem = FALSE;
  fitMoreItems = TRUE;
}

    В первой строке данной функции создается объект TMenu из меню, связанного с окном, дескриптор которого содержится в HWindow. (HWindow - это член-данное оконного класса, наследуемый от TWindow.) Вы можете создать объект TMenu и несколькими другими способами. Для этого существуют следующие конструкторы:

    TMenu(TAutoDelete  autoDelete  =  AutoDelete);
    TMenu(HWND wnd,   TAutoDelete autoDelete  = NoAutoDelete);
    TMenu(НМЕNU handle, TAutoDelete  autoDelete  = NoAutoDelete); 
    TMenu(LPCVOID   *menuTemplate); 
    TMenu(HINSTANCE   instance,   TResId  resld);

    Эти конструкторы создают объект TMenu из пустого меню, из меню окна, из существующего меню через дескриптор меню, из шаблона меню и из ресурса приложения, соответственно. Заметим, что три первых конструктора имеют параметр TAutoDelete, определяющий, будет ли удалено соответствующее меню Windows вместе с объектом TMenu. Поскольку этот параметр задан по умолчанию, вам необходимо вспоминать о нем только тогда, когда вы делаете с меню что-нибудь необычное.

    Класс TMenu ObjectWindows предлагает полный набор функций для управления меню и пунктами меню. Большинство из этих функций аналогичны Windows API, за исключением того, что они не требуют в качестве первого аргумента дескриптора меню. Функции-члены TMenu не требуют этого аргумента, поскольку он является членом-данным этого класса. Когда вы вызываете функцию TMenu, она вызывает соответствующую функцию Windows API (если таковая существует) с переданными вами аргументами, а также дескриптором меню.

    Например, посмотрим на функцию из предыдущего примера, которая управляет командой Add Item меню 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;
  }
}

    В первой строке вызывается функция GetSubMenu() класса TMenu, требующая в качестве параметра позицию относительно нуля для меню, которым вы хотите управлять (в этой программе меню File находится в позиции 0, а меню Test - в позиции 1.) Сравните такой вызов GetSubMenu() с вариантом из Windows API:

    GetSubMenu(hMenu, nPos);

    Версия Windows требует от вас задать дескритор главного меню. OWL-версия подставляет этот параметр за вас.

    GetSubMenu() возвращает дескриптор требуемого меню. После получения этого дескриптора функция использует его для создания объекта TMenu, с именем sMenu, из подменю. Затем функция вызывает функцшо GetMenuItemCount() объекта sMenu, которая возвращает число подпунктов в данном пункте меню.

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

    Если это - последний подпункт, который может быть присоединен к заданному пункту меню, функция устанавливает флаг f itMoreItems в FALSE. В любом случае функция устанавливает haveItem в TRUE.


    Замечание. Многие функции меню Windows API используют константы для определения состояния пункта меню. Эти константы перечислены в таблице 1. За более подробной информацией о том, как использовать эти константы, обратитесь к справочнику по Windows.

Таблица 1. Флаги, определяющие состояние пунктов меню
Константа Описание
MF_BYCOMMAND Указывает, что параметр позиции - идентификатор пункта
MF_BYPOSITION Указывает, что параметр позиции - позиция пункта меню относительно нуля
MF_BITMAP Указывает, что пункт является растровым изображением
MF_STRING Указывает, что пункт - текстовая строка
MF_CHECKED Указывает, что пункт имеет индикатор состояния
MF_UNCHECKED Указывает, что пункт не имеет индикатора состояния
MF_ENABLED Указывает, что пункт может быть выбран
MF_DISABLED Указывает, что пункт не может быть выбран
MF_GRAYED Указывает, что пункт меню недоступен и выведен серым цветом
MF_MENUBARBREAK Разделяет столбцы меню вертикальной линией
MF_MENUBREAK Начинает новый столбец меню
MF_OWNERDRAW Показывает, что пункт определяется владельцем
MF_POPUP Определяет, что с пунктом связано всплывающее меню
MF_SEPARATOR Разделяет пункты горизонтальной линией

    После добавления пользователем нового подпункта в пункт меню Test можно выполнить команду Delete Item. При выборе пользователем этой команды, управление передается функции CmDeleteItem():

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;
	}
}

    Во многом эта функция напоминает функцию CmAddItem(). CmDeleteItem() вызывает GetSubMenu() для получения дескриптора подменю, создает на его основе объект TMenu и вызывает GetMenuItemCount() для определения количества подпунктов в пункте меню. Отличие в том, что эта функция должна определять, не содержит ли меню больше пяти и меньше десяти пунктов. Если в меню больше пяти пунктов и меньше десяти, то удаление возможно.

    Для удаления пункта вызывается функция DeleteMenu(), которая требует в качестве параметров позицию подпункта и флаг, определяющий, каким образом интерпретируется параметр позиции. Флаг MF_BYPOSITION указывает, что параметром позиции является положение подпункта меню относительно нуля. Флаг MF_BYCOMMAND означает, что параметр позиции - обычный идентификатор меню. Поскольку эта программа использует одинаковые идентификаторы для каждого нового пункта меню, в ней используется флаг MF_BYPOSITION.

    После удаления пункта, функция устанавливает флаг fitMoreItems в TRUE и проверяет, присутствуют ли дополнительные пункты в меню. Если в меню нет больше новых пунктов, функция устанавливает флаг haveItem в FALSE, что блокирует команду Delete Item, при вызове функции переключателя доступности этой команды.

    Добавление нового всплывающего меню (пункта меню) в существующее меню отличается от добавления подпункта в уже имеющийся пункт меню. В примере из предыдущего шага добавление пункта в меню выполняется с помощью функций CmAddPopup():

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();
  }
}

    Эта функция сначала создает новый объект TPopupMenu, сохраняющий указатель на объект в subMenu. Этот объект меню создается динамически, и его нельзя удалить, пока существует функция. После создания функций объекта меню она вызывает AppendMenu (), добавляя в новый пункт меню единственный подпункт. (В пустом меню отсутствует указатель.) Затем функция AppendMenu () объекта главного меню добавляет новый пункт меню в главное меню. В этом случае первым параметром является значение флага MF_POPUP, которое указывает, что пункт - новое всплывающее меню. Остальные параметры - дескриптор нового всплывающего меню (представляет собой целое число без зкака) и текст, представляющий его в строке меню.

    Всякий раз при изменении строки меню вы должны вызвать функцию DrawMenuBar () (унаследованную от TWindow и основывающуюся на функции Windows API с тем же именем) для отображения новой строки меню. Если вы забыли сделать это, произведенные изменения не появятся. После вызова DrawMenuBar () новое меню выводится на экран и готово для работы.

    Вы уже умеете удалять подпункт пункта меню. У вас имеется такая же возможность удалить и пункт меню из строки меню. Функция CmDeletePopup() в примере из предыдущего шага выполняет эту задачу:

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

    Эта функция сначала проверяет указатель subMenu, чтобы убедиться в существовании пункта меню. Убедившись, что пункт меню существует, она вызывает функцию DeleteMenu() главного меню для удаления этого пункта меню. Раньше программа вызывала эту функцию для удаления подпункта меню. Для удаления пункта меню функции DeleteMenu () требуются следующие аргументы: позиция пункта меню относительно нуля в строке меню и флаг MF_POSITION. (Вы также можете использовать флаг MF_BYCOMMAND для удаления пункта меню с помощью его идентификатора.) После удаления этого пункта меню CmDeletePopup() вызывает DrawMenuBar () для отображения новой строки меню и установки указателя subMenu снова в NULL, что блокирует команду Delete Popup, когда вызывается переключатель доступности команды этого пункта меню.

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

    В примере из предыдущего шага функция CmCheck() управляет индикаторами состояния:

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);
	}
}

    Вначале CmCheck() получает состояние пункта через вызов функции GetMenuState(), которая требует в качестве аргументов идентификатор пункта меню или позицию относительно нуля и флаг, определяющий, как интерпретировать аргумент позиции. Как и ранее, флаг MF_BYCOMMAND определяет, что первым параметром является идентификатор пункта меню, в то время как флаг MF_BYPOSITION определяет, что первый параметр - позиция пункта меню. GetMenuState() возвращает целое, содержащее значения флагов, которые представляют текущее состояние пункта.

    CmCheck() по значению определяет, помечен ли пункт. Если не помечен, то ModifyMenu() изменяет текст пункта меню на Uncheck, a CheckMenuItem() добавляет индикатор состояния в пункт меню. Параметрами ModifyMenu() являются идентификатор пункта или позиции относительно нуля, флаги пунктов меню, новый идентификатор пункта меню и новый текст пункта меню. В этом случае идентификатор пункта должен оставаться неизменным, так что второй параметр ID будет иметь то же значение. Заметим, что флагом параметра в этом случае является только MF_STRING, а не MF_STRING | MF_BYCOMMAND, как вы, наверняка., ожидали. Это происходит потому, что многие из функций управления меню принимают в качестве аргумента MF_BYCOMMAND, если вы не поддерживаете ни флаг MF_BYCOMMAND, ни флаг MF_BYPOSITION.

    Если пункт меню уже содержит маркер проверки, функция вызывает ModifyMenu() для изменения текста пункта меню снова на Check и вызывает CheckMenuItem() для удаления маркера.


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

    На предыдущем и этом шагах вы изучили, как с помощью функций ТМеnu управлять вашими меню. Однако, возможно вы заметили, что часто легче управлять изменениями в меню через функции - переключатели доступности, чем отягощать функции - отклики на сообщения. Например, рассмотрим версию CmCheck(), которая основана на функции с тем же именем из примера предыдущего шага:

void TWndw::CmCheck()
{
  // Проверить текущее состояние пункта меню. 
  if (!checked)
    // Изменить текст пункта меню на "Unchecked". 
    strcpy(checkStr, "&Uncheck");
  else
    // Изменяем текст пункта меню на "Checked". 
    strcpy(checkStr,   "&Check");
  // Инвертировать  значения  флага  проверки. 
  checked =!checked;
}

    В данной версии CmCheck(), функция не отвечает за осуществление действительных изменений в пунктах меню. Наоборот, она просто устанавливает флаги и строки пункта меню, предоставляя произвести действительные изменения меню соответствующему переключателю доступности команды. Флаг, который указывает на состояние маркера пункта меню, избавляет функцию от необходимости вызова другой функции для определения состояния пункта меню. Как результат, данная версия CmCheck() более простая и короткая.

    Действительные изменения меню выполняются функцией - переключателем доступности команды пункта.меню:

void  TWndw::CmEnableCheck (TCommandEnabler   &commandEnabler) 
{
  // Сначала  изменить   текст!   В  противном
  // случае  маркер  будет  стерт.
  commandEnabler.SetText(checkStr);
  // Добавить или удалить маркер 
  // на основании значения флага. 
  if   (checked)
    commandEnabler.SetCheck(commandEnabler.Checked); 
  else
    commandEnabler.SetCheck(commandEnabler.Unchecked);
}

    Здесь переключатель доступности команды сначала вызывает функцию SetText() объекта TCommandEnable, которая изменяет текстовую строку пункта меню, передаваемую в качестве единственного аргумента функции. Затем функция проверяет флаг checked и вызывает функцию SetCheck() для добавления или удаления маркера, основываясь на флаге состояния. Функция SetCheck () имеет единственный параметр - один из флагов, перечисленных в классе TCommandEnable. Это такие флаги, как Unchecked, Checked и Identerminate.


    Замечание. При изменении как текстовой строки пункта меню, так и состояния маркера, вы должны изменить сначала текстовую строку. Если вы изменяете текстовую строку после установки маркера, маркер не появится в меню, поскольку функция SetText() запишет на его место другую информацию.

    На следующем шаге мы рассмотрим модификацию системного меню.




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