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

ОГЛАВЛЕНИЕ

Добавление изображений к меню

Команды привязываются к изображениям с помощью редактора панели инструментов Visual Studio путем создания битового массива и ресурса "панели инструментов".

При инициализации меню можно преобразовать все пункты в "прорисовку владельцем" (задача, выполняемая с помощью производного EWnd, вызывающего EMenuImager), но ... где хранить данные?

Для команд можно придумать глобальную карту, индексируемую идентификаторами команд: идентификаторы команд глобальные. Значением карты будет SCmdDraw::XCmdChara, хранящий идентификатор ресурса, из которого взято изображение, и относительный индекс изображения.

Затем можно преобразовать в прорисовку владельцем все пункты меню при WM_INITMENUPOPUP и преобразовать обратно в нормальные при WM_UNINITMENUPOPUP. Возможно, это не так эффективно, но безопасно в плане управления памятью (не хранятся все строки без идентификаторов из меню, например, в подменю).

Важно, чтобы те сообщения обрабатывались после возможного изменения состояния меню, вызванного другими обертками. Чтобы гарантировать это, обработчик сообщения помечает сообщения как обработанные (без последующей обработки), но Default() вызывается в начале обработчика.

Поэтому :
•    Если вы оборачиваете сначала (например) посредством EMenuUpdator и затем с помощью EMenuImager, подкласс оконной процедуры вызовет EMenuImager, вызывающий Default, заставляющий обработать EMenuUpdator первым.
•    Если вы оборачиваете сначала посредством EMenuImager и затем с помощью EMenuUpdator, подкласс оконной процедуры вызовет EMenuUpdator первым и (потому что она оставила сообщения необработанными) затем EMenuImager.

В обоих случаях последовательность сначала обновляет пункты, затем приспосабливает их к прорисовке.

Прорисовка пунктов

Поскольку есть разные способы прорисовки пунктов меню (в плане прорисовки эффектов и состояния: например, Win2K или WinXP или VS-IDE), можно придумать разные стратегии реализации обработки WM_DRAWITEM.

A.    Сделать шаблон EMenuImager и предоставить параметрический класс типажей, предоставляющий функцию OnDrawItem: его недостаток в статичности кода: пользователь не может переключаться между разными типажами: приходится обеспечить создание разных экземпляров шаблонов, что означает создание множественных экземпляров статической или глобальной структуры данных.
B.    Объявить OnDrawItem как абстрактный и реализовать его в разных производных классах.
C.    Не реагировать на WM_DRAWITEM и обработать его в отдельном производном EWnd, оставив осуществление полиморфизма на ядро Windows.
D.    Породить EMenuManager, дать ему новую карту сообщений, связанную с базовой (стиль WTL).

Тогда как A не подходит для таких реализаций, B налагает ряд ограничений на использование функций.
C означает - объявить EMenuImager_xxx, чтобы прикрепить к тому же самому HWND, обернутому EMenuImager, где xxx – составляющая, которую надо предоставить. Вероятно, это самое гибкое решение, но склонное к генерации сильно запутанных классов: определяется Exxx, кажущийся оберткой HWND, но не работающий сам по себе и зависящий от данных, генерируемых (и определяемых) другим независимым классом.

D наиболее традиционна, но кажется оптимальной в данном случае. Представленная реализация - NGDI::EMenuImagerIDE.

Отображение быстрых клавиш

В программах с применением быстрых клавиш обычно отображают быстрые клавиши для команд справа от текста меню.

Но писать названия быстрых клавиш в ресурсе MENU не рекомендуется: перевод сообщений основан на HACCEL, загружаемом из "таблицы быстрых клавиш", самой не являющейся меню. Быстрые клавиши меняются путем редактирования таблицы быстрых клавиш, но надо изменить и текст меню. И если одна и та же команда присутствует во множестве разных меню ... это непросто отследить.

Другой выход – не помещать текст в ресурс меню, а обеспечить возможность изменения текста меню перед его отображением. Это легко сделать в EMenuUpdator. Известив этот класс о таблице быстрых клавиш, и дав ему текстовые описания для всех клавиш, можно непосредственно перед отображением команды просмотреть заданную таблицу быстрых клавиш, найти быструю клавишу для заданной команды, извлечь ее текст и затем добавить его справа от меню.

Связывание таблицы быстрых клавиш с окном позволяет прицепиться к синглтону SMsgFiltrerEvent при активации (и отцепиться при дезактивации) и вызвать TranslateAccelerator API. Так как это событие генерируется циклом обработки сообщений сразу перед отправкой сообщения, это заставит работать быстрые клавиши, связанные с окном.

Все вышесказанное реализовано в EMenuUpdator. В частности, текст, используемый для описания быстрых клавиш, создается с помощью ресурса RC_DATA, являющегося последовательностью пар, состоящих из WORD с последующей C-строкой. WORD является константой VK_xxx, обозначающей функциональную клавишу клавиатуры, а C-строка является ее описанием. Последние три записи ресурса должны иметь идентификаторы, равные 0, и описывать "Shift+", "Ctrl+" и "Alt+" (или любое описание, имеющееся у них в данной локализации) соответственно.

В EMenuUpdator структура XKeyNames помогает читать этот ресурс: она используется обернутой в SResourceData<>, прикрепленный к HRSRC, заданный FindResource. В структуре XKeyTexts хранятся описания, индексируемые идентификатором VK_xxx. (Смотрите реализацию его функции Load.)
Чтобы уложиться в функционал IDE(интегрированная среда разработки), для проекта NLIB был предоставлен файл ресурсов NLib.rc и файл Accels.rc2, а также файл NLib_res.h, содержащий определения именованных констант.

NLib.rc сконфигурирован так, чтобы включить NLib_res.h в качестве своего собственного заголовка и Accels.rc2 в качестве включенного файла ресурсов. Поскольку Nlib - библиотека, NLib.rc сам не должен компилироваться. Наоборот, он должен включиться в файл ресурсов проекта "exe": в данном случае проект "W3" имеет файл ресурсов (W3.rc), включающий традиционный resouce.h и Nlib_res.h, а также включающий в свое тело файл NLib.rc.