Написание приложений Win32 с помощью одних классов C++ (часть 2) - Работа с меню, командами и идентификаторами

ОГЛАВЛЕНИЕ

Работа с меню, командами и идентификаторами

Для добавления иконки или меню к окну не нужна обертка: идентификатор иконки можно задать во время регистрации класса окна, а меню можно загрузить во время создания окна. Пока HMENU остается связанным с окном, Windows обеспечивает его уничтожение.

Ниже приведена измененная winmain.

int APIENTRY _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPTSTR lpCmdLine, int nCmdShow)
{
    NWin::SMsgLoop loop(true);  //создать экземпляр цикла
    NUtil::SResID resWapp(IDR_WApp);

    LPCTSTR wcname = _T("W1MainWnd");
    //создать экземпляр класса окна
    NWin::SWndClassExReg wc(true, hInstance, wcname, resWapp);    
    CMainWnd wnd;
    wnd.CreateWnd(hInstance, wcname, NULL, WS_VISIBLE|WS_OVERLAPPEDWINDOW,
        NWin::SRect(NWin::SPoint(CW_USEDEFAULT,CW_USEDEFAULT),
        NWin::SSize(600,400)), NULL,
        LoadMenu(hInstance, resWapp), NULL);

    loop.Loop();

    return 0;
}

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

Два распространенных подхода следующие:
•    подход WTL: с командой связано состояние, управляемое приложением. Элементы GUI умеют представлять это состояние при отображении. Меню обычно делают это при WM_INITMENUPOPUP, панели инструментов – при обработке во время простоя.
•    подход MFC: элемент GUI (меню при WM_INITENUPOPUP, панели инструментов при обработке во время простоя) запрашивает у приложения свое состояние. Приложение отвечает путем установки своего состояния интерфейса.

Два подхода "двойственны": нет лучшего, но один может быть лучше другого в зависимости от ситуаций: если состояние команды зависит от множества факторов, распределенных по множеству мест, то подход MFC проще для программирования, тогда как если состояние является строго определенным условием, то проще подход WTL.

Подход MFC более общий, поэтому был реализован аналогичный подход.

Состояние команды

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

Это делается посредством интерфейса: ICmdState, предоставляющего набор абстрактных функций.
Указанные функции могут быть реализованы в разных классах, управляющих конкретным компонентом пользовательского интерфейса. Для стандартного меню Windows этим занимается SCmdMnu.
Для управления обновлением команды использовалось два закрытых сообщения GE_QUERYCOMMANDHANLER и GE_UPDATECOMMANDUI.

В частности, для обновления меню:
•    При WM_INITMENUPOPUP отправляется сообщение GE_QUERYCOMMANDHANLER. LPARAM является SCmdMnu*, а WPARAM является идентификатором команды.
•    GE_QUERYCOMMANDHANLER обрабатывается макросом COMMAND_xxxx_U (должен использоваться вместо COMMAND_xxxx, того же, который обрабатывает WM_COMMAND) путем вызова SetHandled.

Когда GUI требуется, (WM_INITMEMUPOPUP, обработчик простоя, или что угодно), он должен отправить сообщение GE_UPDATECOMMANDUI (тот же параметр, что и предыдущий). Приложение обрабатывает это сообщение путем вызова функций-членов ICmdState. Их реализация управляет установкой состояния GUI.
Для меню этим управляет производный класс EWnd, прикрепленный, например, к главному окну. Этот класс - EMenuUpdator: он обрабатывает WM_INITMENUPOPUP, как показано.

С целью применения он создается и прикрепляется к главному окну.

int APIENTRY _tWinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPTSTR lpCmdLine, int nCmdShow)
{
    NWin::SMsgLoop loop(true);  //создать экземпляр цикла
    NUtil::SResID resWapp(IDR_WApp);

    LPCTSTR wcname = _T("W1MainWnd");
    //создать экземпляр класса окна
    NWin::SWndClassExReg wc(true, hInstance, wcname, resWapp);    
    CMainWnd wnd;
    wnd.CreateWnd(hInstance, wcname, NULL, WS_VISIBLE|WS_OVERLAPPEDWINDOW,
        NWin::SRect(NWin::SPoint(CW_USEDEFAULT,CW_USEDEFAULT),
        NWin::SSize(600,400)), NULL,
        LoadMenu(hInstance, resWapp), NULL);
    NWin::EMenuUpdator mnuupdt;
    mnuupdt.Attach(wnd);

    loop.Loop();

    return 0;
}

Вот и все.

Для обработки GE_UPDATECOMMANDUI были предоставлены макросы ONMSG_GE_UPDATECOMMANDUI,ONMSG_GE_UPDATECOMMANDUI_RANGE и ONMSG_GE_UPDATECOMMANDUI_RANGE_CODE (они находятся в CmdUpdt_macros.h), применяемые в картах сообщений.

Все они вызывают следующий прототип функции:

LRESULT function(
  UINT nID,             //идентификатор команды
  WORD nCode,           //код уведомления (если от управляющего элемента)
  ICmdState* pCmdState, //ICmdState, чьи функции меняют состояние GUI
  bool& bHandled      //установлен в «Истину» перед вызовом функции.
  );      //возвращаемое значение: всегда должно быть 0.

Ложные ошибки исправлены

Было исправлено несколько ложных ошибок из предыдущей статьи. Они названы ложными, поскольку являются не "ошибками" (ошибками программирования), а "ошибками проектирования", показывая отсутствие нужного функционала.