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

ОГЛАВЛЕНИЕ

Данная статья продолажет предыдущю по теме изучения способа написания приложений Win32 с помощью одних классов C++.

• Скачать проект и демо - 97.7 Кб

Добавленные возможности

Макросы NOTIFY_xxx_HANDLER

Есть макросы карты сообщений типа ATL, применяемые для отправки сообщений WM_NOTIFY. Благодаря их улучшению они принимают дополнительный параметр "type".

Новая форма - GE_NOTIFY_xxx_TYPEDHANDLER( ... , func, type). (Примечание: в зависимости от конкретного макроса, "..." равен code, id, range идентификаторов, или их сочетание). Это позволяет указать в макросе тип, к которому он будет приведен, LPNMHDR, переносимый параметром сообщения LPARAM.

Это также позволяет объявлять обработчики сообщений, имеющие в качестве параметра непосредственную ссылку на нужную структуру (например, NMLISTVIEW&), а не LPNMHDR, приводимый в теле функции.

Типизированные уведомления

Чтобы позволить Windows взаимно оповещать о событиях, Windows предоставляет архитектуру отправки сообщений на базе сообщений (MSG) и некоторые API для отправки (SendMessage), публикации (PostMessage), извлечения (GetMessage, PeekMessage) и передачи (DispatchMessage, WINDOWPROC). В этом каркасе WINDOWPOC всегда является внутренним подклассом оконной процедуры, и отправка осуществляется с помощью карт сообщений. Зато отправка сообщений требует больше внимания.

При отправке уже определенных сообщений вызывается SendMessage API с передачей требуемых параметров. При отправке других видов сообщений нужно хотя бы определить способ их идентификации. Это можно сделать путем определения именованных констант типа #define WM_MYMESSAGE (WM_USER+xxx), но представьте исходник, состоящий их разных модулей библиотек и разных компонентов от разных разработчиков. Нужно очень строгое соглашение о нумерации (во избежание повторного использования одних и тех же идентификаторов в разных исходниках) или какая-то автоматизация этого.

NUtil::XId_base предоставляет статическую функцию (UINT NewVal()), возвращающую значение увеличенного статического счетчика при каждом вызове.

NUtil::SId<T> предоставляет функцию UINT _getval(), возвращающую значение статической переменной, которой присвоено начальное значение XId_base::NewVal() при первом вызове _getval. Также он имеет operator UINT(), возвращающий это значение. Это позволяет привязать столько UINT, сколько нужно,к стольким типам T, сколько нужно использовать с SId.

SNmHdr<N> является структурой struct с первым членом  NMHDR, инициализирующим ее член "code" в (UINT)SId<N>(). Windows определяет коды WM_NOTIFY в библиотеке "общего управления" в виде (OU - xxxU) (значит: от 0xFFFFFFFF до ... около 3000 кодов). Так как XId_base сделан начинающимся с 0x2000 и возрастающим ... есть много идентификаторов для использования.

SNmHdr<N> также имеет функцию LRESULT Send(HWND hTo), делающую SendMessage(hTo, WM_NOTIFY, nmhdr.idFrom, (LPARAM)this);. Можно породить структуру struct (скажем, SMyNotification) от SNnHdr<SMyNotification>, заполнив ее члены как требуется, и вызвать Send.

Такое сообщение извлекают макросы карты сообщений (GE_NOTIFY_REGISTREDHANDLER,GE_NOTIFY_CODE_REGISTREDHANDLER, GE_NOTIFY_RANGE_CODE_REGISTREDHANDLER), принимающие параметр "type", проверяющие uMsg == WM_NOTIFY и GE_::NUtil::SId<type><TYPE>() == ((LPNMHDR)lParam)->code), вызывающие функцию в виде lResult = func((int)wParam, *(type*)lParam, bHandled).

Следовательно, можно поместить запись GE_NOTIFY_CODE_REGISTREDHANDLER(OnMyHandler, SMyNotification) в карту сообщений окна, чтобы вызвать функцию-член LRESULT OnMyHandler(int nID, SMyNotification& myntf,bool& bHandled).

Маршрутизация команд

Представьте, что есть окно-рамка с дочерним видом внутри. Меню и панели инструментов обычно принадлежат рамке и отправляют WM_COMMAND и WM_NOTIFY рамке.

Но может понадобиться обрабатывать эти команды из дочернего вида. Этого можно добиться путем привязывания карты сообщений рамки к другой карте сообщений вида (но при этом передаются все сообщения). Или можно пересылать только WM_COMMAND или WM_NOTIFY.

Для этого служат макросы GE_ROUTE_MSG_MAP_xxxx. Можно передать классу, члену, или через указатель (производится проверка указателя NULL).

Есть две разных серии макросов для команд и уведомления. Но при использовании команд автообновления (GE_COMMAND_ID_HANDLER_U) не забывайте, что само автообновление является сообщением WM_NOTIFY. Следовательно, при маршрутизации команд ... маршрутизируйте WM_NOTIFY точно так же. Или используйте макросы, вызывающие обе серии одновременно.

Заметьте, что макросы "ROUTE" вызывают some::ProcessWindowMessage. Это отличается от "отправки" сообщения: если окно имеет несколько оберток, вызов SendMessage позволяет всем оберткам получить сообщение в своей стандартной карте сообщений, тогда как "ROUTE" заставляет только переданную обертку обработать маршрутизированные сообщения или команды.

Чтобы снова отправить команду заданному окну, вместо "маршрутизации" его заданной обертке используйте GE_FORWARD_COMMANDS. Он повторно отправляет WM_COMMAND и WM_NOTIFY.

Пересылка сообщений

Другой способ приказать окну обработать сообщение, изначально отправленное другому окну (не путая его с сообщениями, предназначенными для того окна) - "пересылка путем инкапсуляции". Оригинальное сообщение снова отправляется внутри другого сообщения другому окну. Данная хитрость входит в ATL (ATL_FORWRARD_MESSAGE), но здесь ее обобщили.

GE_FORWARD_MESSAGE(hWndTo, code) отправляет WM_GE_FORWARDMSG, чьими параметрами являются идентификатор кода (как WPARAM) и NWin::XWndProcParams* (как LPARAM: переносит параметры оригинального сообщения).

Можно обработать его в целевой карте сообщений обертки HWND с помощью GE_WM_FORWARDMSG(func), где "func" является aLRESULT func (NWin::XWndProcParams& msg, DWORD nCode, bool& bHandled);.

Или можно извлечь оригинальное сообщение, рекурсивно вызывая ProcessWindowMessage после извлечения параметра из XWndProcParams.

Это можно сделать с помощью:

GE_WM_FORWARDMSG_ALT(msgMapID): извлечь оригинальное сообщение и предоставить его той же карте сообщений в другой секции ALT_MSG_MAP. Это позволяет обработать сообщение распаковщиками GE_WM_xxx.

GE_WM_FORWARDMSG_ALT_CODE(code, msgMapID): как раньше, но только те сообщения, оболочка которых была помечена "кодом" при повторной отправке.

Привязка карт сообщений

Серия GE_CHAIN_MSG_MAP устроена так, чтобы иметь одинаковое число макросов: можно привязать стандартную карту или конкретную "запасную карту" (xxx_ALT(..., msgMapID): тот же принцип, что и в ATL).

Можно привязать карту к:

•  Классу – помогает производным классам ссылаться на их базовые классы.
•  Члену –  полезно для классов, в членах которых хранятся другие классы.
•  Указателю –  полезно в более сложных структурах, где имеются разные ссылки. Пустые указатели проверяются перед вызовом указанного ProcessWindowMessage.

Соображения по обработке сообщений

Объединив все техники маршрутизации сообщений, можно сделать почти все. И учитывая, что любой класс может быть производным NWin::IMessageMap (нет нужды самому быть оберткой окна), карты сообщений позволяют классам взаимодействовать без необходимости запутывать себя (конструктивно знать свой взаимный интерфейс) статически.

Если нужно больше динамики, правильным решением может быть использование "событий" (NWrp::Event<A> andNWrp::EventRcv<D>: смотрите описание в части 1).

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

Технически возможно определить взаимодействие между классами посредством карт сообщений и можно преобразовать сообщения Windows в события. Однако приведенный способ поддержки событий нельзя использовать с сообщениями как есть: связывание карт сообщений позволяет передать несколько сообщений от одной карты к другой. На настоящий момент события взаимно-однозначны: получатель должен отдельно зарегистрировать все события, которые он хочет получать.

Нахождение обертки заданного типа (RTTI)

Рассмотрите окно с несколькими прикрепленными обертками. Может понадобиться найти одну заданного типа (или унаследованную от заданного типа). Так как обертки HWND связаны, это легко делается с помощью шаблонной функции и dynamic_cast, путем прохода по цепи, пока приведение не будет непустым. Более общий boolWrpChainBase::DynamicFind<A>(A*&, TT::IH) делает именно это. Но он реализован в WrpChainBase, поэтому работает для каждой связанной обертки.

Так как используется dynamic_cast, RTTI (информация о типе времени исполнения) должна быть активирована.

Модальное диалоговое окно

Модальные диалоговые окна асимметричны в Windows API: функция DialoBox Windows требует DLGPROC, но этот "proc" – не настоящий WNDPROC. Настоящий WNDPROC закрыт в системе. Он вызывает процедуру и – при возврате лжи - вызывает DefDlgProc. Все это происходит в модальном цикле, внутреннем для DialogBox API.

Чтобы принудительно провести это в уже существующей обертке, был реализован EWnd::CreateModalDialog, который, действуя как CreateWnd, устанавливает ловушку и передает внутреннюю скрытую функцию в качестве DLGPROC. Ловушка прикрепляет создание нового окна (в данном случае диалога) к запрашивающей обертке и автоматически отцепляет себя.

Процедура ловушки была пересмотрена, чтобы ловушка была активна в течение наиболее короткого срока (во избежание рекурсии в функции ловушки, где создается несколько вложенных окон – представьте родителя, создающего своих потомков в ходе процесса создания самого себя). Представленный скрытый DLGPROC всегда возвращает ложь, кроме WM_COMMAND с кодом от 1 до 7 включительно (IDOK, IDCANCEL, ..., IDCLOSE: чтобы была стандартная обработка, возвращающая значение, иначе программа застрянет в информационном окне).

Пересмотренная процедура ловушки также исправляет ошибку: в предыдущей версии, если еще создающееся окно обернуто и перехватывает WM_CREATE для создания дополнительных окон (представьте главное окно с потомками), создается несколько экземпляров вложенных ловушек, но при возврате отцепляется только последняя. Хотя это не влияет на функционал (процедуры ловушки только прикрепляют первую обертку), в ряде случаев это влияет на производительность. Теперь такого не случится: отцепление ловушки производится в самой процедуре ловушки, до отправки любого сообщения любой обертки. Рекурсия невозможна.