Шаг 16.
Библиотека OWL.
Модификация системного меню

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

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

#include <owl\applicat.h>
#include <owl\framewin.h>
#include <owl\menu.h>
#include "pr16_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, *sysMenu;
	 BOOL itemPresent, reverted;

	 void SetupWindow();
	 void CleanupWindow();
	 void CmAddItem();
	 void CmDeleteItem();
	 void CmRevert();
	 void EvSysCommand(UINT, TPoint&);
	 // Переключатели состояния команд.
	 void CmEnableAddItem (TCommandEnabler &commandEnabler);
	 void CmEnableDeleteItem (TCommandEnabler &commandEnabler);
	 void CmEnableRevert (TCommandEnabler &commandEnabler);

	 DECLARE_RESPONSE_TABLE (TWndw);
};

DEFINE_RESPONSE_TABLE1 (TWndw, TFrameWindow)
  EV_COMMAND(CM_ADDITEM,CmAddItem),
  EV_COMMAND(CM_DELETEITEM,CmDeleteItem),
  EV_COMMAND(CM_REVERT,CmRevert),
  EV_WM_SYSCOMMAND,
  EV_COMMAND_ENABLE(CM_ADDITEM,CmEnableAddItem),
  EV_COMMAND_ENABLE(CM_DELETEITEM,CmEnableDeleteItem),
  EV_COMMAND_ENABLE(CM_REVERT,CmEnableRevert),
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()
{
  // Создать объект меню из меню окна.
  sysMenu = new TSystemMenu (HWindow, FALSE);
  // Указать, что новый пункт меню отсутствует.
  itemPresent = FALSE;
  // Указать, что системное меню не восстановлено.
  reverted = FALSE;
}

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

// TWndw::EvSysCommand()
// Эта функция обрабатывет команду WM_SYSCOMMAND
// проверяет, выбрал ли пользователь новый пункт
// меню, который имеет идентификатор CM_NEWITEM.
void TWndw::EvSysCommand (UINT cmdType, TPoint&)
{
  // Если пользователь выбрал новый пункт
  // системного меню...
  if (cmdType == CM_NEWITEM)
	 // Изобразить окно сообщений.
	 MessageBox("Обработка ссобщения CM_NEWITEM", "Сообщение", MB_OK);
	 // Иначе возвратить сообщение в Windows.
  else DefaultProcessing();
}

// TWndw::CmAddItem()
// Эта функция  отвечает на сообщение CM_ADDITEM,
// добавляя новый пункт в системное меню.
void TWndw::CmAddItem()
{
  // Добавить разделитель и новый пункт.
  sysMenu->AppendMenu(MF_SEPARATOR,0,0);
  sysMenu->AppendMenu(MF_STRING, CM_NEWITEM, "C&lick Me");
  // Указать, что в меню присутствует новый пункт.
  itemPresent = TRUE;
}

// TWndw::CmDeleteItem()
// Эта функция отвечает на команды CM_DELETEITEM,
// удаляя пункт из системного меню.
void TWndw::CmDeleteItem()
{
  // Удалить разделитель и новый пункт.
  sysMenu->DeleteMenu(sysMenu->GetMenuItemCount()-1, MF_BYPOSITION);
  sysMenu->DeleteMenu(sysMenu->GetMenuItemCount()-1, MF_BYPOSITION);
  // Указать, что новый пункт в меню не существует.
  itemPresent = FALSE;
}

// TWndw::CmRevert()
// Эта функция обрабатывает команду CM_REVERT,
// возвращая системное меню к первоначальному виду.
void TWndw::CmRevert()
{
	// Удалить старый объект меню.
	delete sysMenu;
	// Создать объект меню системного меню окна,
	// заданного по умолчанию.
	sysMenu = new TSystemMenu (HWindow, TRUE);
	// Указать, что это меню - обычное системное меню.
	reverted = TRUE;
}

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

void TWndw::CmEnableDeleteItem(TCommandEnabler &commandEnabler)
{
	commandEnabler.Enable(itemPresent && !reverted);
}

void TWndw::CmEnableRevert(TCommandEnabler &commandEnabler)
{
	commandEnabler.Enable(!reverted);
}

void TApp::InitMainWindow()
{
  TFrameWindow *wndw=new TWndw(0,"Модификация системного меню");
  SetMainWindow(wndw);
}

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

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

#define CM_ADDITEM       200
#define CM_DELETEITEM    202
#define CM_REVERT        203
#define CM_NEWITEM       300
#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 "&Delete Item", CM_DELETEITEM, INACTIVE
  MENUITEM "&Revert", CM_REVERT
 }
}
#endif
Текст этого приложения можно взять здесь.

    Когда вы загрузите программу, представленную в этом примере, вы увидите окно, показанное на рисунке 1.


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

    Это окно имеет меню File, содержащее только команду Exit и меню Test, содержащее команды по добавлению и удалению новых команд системного меню, а также возвращения системного меню к первоначальному виду. Перед испытанием меню Test активизируйте системное меню. Вы увидите, что обычное меню содержит определенное количество стандартных пунктов, которое может отличаться в различных операционных системах. Когда установлено, что системное меню не изменено, выберите команду Add Item меню Test. Теперь, вызвав системное меню, вы увидите меню, изображенное на рисунке 2, которое содержит новую команду Click Me.


Рис.2. Измененное системное меню

    Выберите команду Click Me, и появится окно сообщений, показанное на рисунке 3, говорящее, что ваша команда принята.


Рис.3. Результат выбора команды Click Me

    Для удаления команды Click Me из системного меню выберите команду Delete Item меню Test; для восстановления первоначального вида системного меню выберите команду Revert меню Test. После восстановления меню оно не может быть более модифицировано.

    Процесс изменения системного меню начинается с функции-члена SetupWindow() главного окна:

void TWndw::SetupWindow()
{
  // Создать объект меню из меню окна.
  sysMenu = new TSystemMenu (HWindow, FALSE);
  // Указать, что новый пункт меню отсутствует.
  itemPresent = FALSE;
  // Указать, что системное меню не восстановлено.
  reverted = FALSE;
}

    Эта функция сначала создает объект TSystemMenu из системного меню приложения. Конструктор TSystemMenu, который имеет доступ к системному меню через вызов функции GetSystemMenu() Windows API, требует в качестве аргументов дескриптор окна, содержащего системное меню, и булево значение, определяющее, хотите ли вы вернуть системное меню в его первоначальное, немодифицированное состояние. Поскольку программа будет изменять системное меню в других функциях, булев аргумент в данном вызове конструктора TSystemMenu имеет значение FALSE. Заметим, что функция new возвращает указатель на объект TSystemMenu, который хранится в члене-данном sysMenu главного окна. Создавая объект TSystemMenu динамически, программа обеспечивает доступ к нему после выхода из SetupWindow().

    После создания объекта TSystemMenu, SetupWindow() устанавливает флаг itemPresent в FALSE, указывая, что команды Click Me не находятся в данный момент в системном меню, а также устанавливает флаг reverted в FALSE, указывая, что текущее системное меню может быть модифицировано.

    После того, как программа получила указатель на объект системного меню, она может добавить или удалить пункты меню обычным способом, путем вызова функции AppendMenu() и DeleteMenu() объекта меню. Например, когда пользователь выбирает команду Add Item меню Test, выполняется функция ответа на сообщение CmAddItem():

void TWndw::CmAddItem()
{
  // Добавить разделитель и новый пункт.
  sysMenu->AppendMenu(MF_SEPARATOR,0,0);
  sysMenu->AppendMenu(MF_STRING, CM_NEWITEM, "C&lick Me");
  // Указать, что в меню присутствует новый пункт.
  itemPresent = TRUE;
}

    Эта функция сначала добавляет разделитель снстемного меню, отделяющего команды системного меню от новых команд, размещаемых в меню. Затем при повторном вызове AppendMenu() формируется команда Click Me. Наконец, флажок itemPresent устанавливается в TRUE, что показывает, что команда Click Me присутствует в системном меню.

    Заметим, что CmAddItem() не проверяет itemPresent. Так каким же образом предотвращается попытка пользователя добавить пулкт дважды? Когда пункт Click Me присутствует в системном меню, разблокировщик команды CmEnableAddItem() затеняет команду Add Item. Поскольку команда затенена, пользователь не может выбрать ее, когда пункт Click Me уже присутствует в меню. Отсюда следует, что программа никогда не вызовет CmAddItem(), когда команда Click Me уже существует.

    Команда Click Me удаляется из системного меню почти так же, как и помещается туда. Функция CmDeleteItem() выполняет эту задачу:

void TWndw::CmDeleteItem()
{
  // Удалить разделитель и новый пункт.
  sysMenu->DeleteMenu(sysMenu->GetMenuItemCount()-1, MF_BYPOSITION);
  sysMenu->DeleteMenu(sysMenu->GetMenuItemCount()-1, MF_BYPOSITION);
  // Указать, что новый пункт в меню не существует.
  itemPresent = FALSE;
}

    CmDeleteItem() всего лишь вызывает дважды DeleteMenu(), по одному разу для каждого пункта меню. Какие есть предположения относительно того, почему оба вызова DeleteMenu() используют результат работы функции GetMenuItemCount()? Сначала DeleteMenu() вызывается для удаления команды Click Me, а затем удаляется разделитель.

    Обработка сообщений системного меню не столь очевидна, как сообщений ваших собственных меню. Хотя команда Click Me имеет свой уникальный идентификатор (CM_NEWITEM), Windows не посылает командного сообщения в окно приложения, если пользователь выберет эту команду. Все, что делает Windows в этом случае, это посылает сообщение WM_SYSCOMMAMD.

    Если программа должна реагировать на выбор пункта системного меню, вы должны написать функцию отклика на сообщение, EvSysCommand(), для данного сообщения. В приведенном примере EvSysCommand() выглядит следующим образом:

void TWndw::EvSysCommand (UINT cmdType, TPoint&)
{
  // Если пользователь выбрал новый пункт
  // системного меню...
  if (cmdType == CM_NEWITEM)
	 // Изобразить окно сообщений.
	 MessageBox("Обработка ссобщения CM_NEWITEM", "Сообщение", MB_OK);
	 // Иначе возвратить сообщение в Windows.
  else DefaultProcessing();
}

    Для обеспечения простоты программирования OWL расщепляет (выделяет соответствующие значения) переменные WPARAM и LPARAM сообщения. В случае сообщения WM_SYSCOMMAND, OWL посылает функции EvSysCommand() переменную типа команда (cmdType) и ссылку на объект класса TPoint (который не используется этой программой), содержащий координаты курсора мыши. Тип команды для пункта Click Me - CM_NEWITEM. Проверяя значение cmdType, функция может определить, было ли это сообщение сгенерировано пунктом меню Click Me, или это какое-то другое системное сообщение.

    Если сообщение было сгенерировано пунктом Click Me, EvSysCornmand () отображает окно сообщения, показывающее, что сообщение получено. Больше никакой обработки не требуется, и функция завершается. Если, однако, cmdType не равно CM_NEWITEM, полученное сообщение является системным. Необходимо, чтобы все остальные системные сообщения возвращались в Windows для обработки. В OWL-программах вы можете вызвать DefaultProcessing() - функцию, наследуемую из класса Twindow, для возврата сообщении в среду Windows для обработки.


    Замечание. Информация о конкретном сообщении посылается стандартному Windows-приложению в параметрах длиной в слово (WPARAM) и двойного слова (LPARAM). Дело конкретной функции, получившей сообщение - расшифровать необходимую программе информацию, находящуюся в этих значениях. Например, в сообщении WM_SYSCOMMAND, параметр LPARAM содержит координаты курсора мыши: координату х в младшем слове и координату у в старшем слове, параметр WPARAM содержит тип сообщения. Автоматическая расшифровка сообщения в ObjectWindows избавляет ваши программы от необходимости извлекать информацию "вручную".

    Системное меню можно вернуть его к первоначальному, немодифицированному виду, создав новый объект TSystemMenu, на этот раз с булевым параметром, установленным в TRUE. Это выполняется функцией CmRevert():

void TWndw::CmRevert()
{
	// Удалить старый объект меню.
	delete sysMenu;
	// Создать объект меню системного меню окна,
	// заданного по умолчанию.
	sysMenu = new TSystemMenu (HWindow, TRUE);
	// Указать, что это меню - обычное системное меню.
	reverted = TRUE;
}

    Сначала функция удаляет старый объект TSystemMenu. Однако, поскольку поле ShouldDelete объекта было установлено в FALSE (заданное по умолчанию для объекта TSystemMenu), основной дескриптор меню Windows не удаляется. Затем функция создает новый объект TsystemMenu, на этот раз со значением булева параметра, равным TRUE, который определяет, что OWL будет восстанавливать немодифицированное системное меню. OWL выполняет это через вызов функции GetSystemMenu() Windows API, которая, благодаря булеву значению TRUE, удаляет возможно измененное системное меню и заменяет его немодифицированным меню. После того, как нормальное системное меню вернулось на свое место, оно не может быть модифицировано.

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




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