нужно относиться осторожно, чтобы не нарушить механизм трансляции сообщений. Примером неправильного переопределения может служить класс
TCustomGrid
. В форумах нередко встречаются вопросы, почему элементы управления, родителем которых является
TDrawGrid
или
TStringGrid
, некорректно ведут себя: кнопки при нажатии не генерируют событие
OnClick
, выпадающие списки остаются пустыми и т.д. Это связано с тем, что обработчик
WM_COMMAND
в
TCustomGrid
учитывает возможность существования только одного дочернего компонента — внутреннего редактора, возникающего при включенной опции
goEditing
. Остальным дочерним компонентам
WM_COMMAND
не транслируются, и они лишены возможности
корректно реагировать на происходящие с ними события. Выходом из ситуации может стать либо создание наследника от
TDrawGrid
или
TStringGrid
, который правильно транслирует
WM_COMMAND
, либо назначение родительским окном компонента, вставляемого в сетку, формы, панели или иного оконного компонента, который правильно транслирует это сообщение.
Рассмотрим все методы, с помощью которых можно встроить свой код в цепочку обработки сообщений оконным компонентом и перехватить сообщения. Всего существует шесть способов сделать это.
1. Как и у всякого окна, у оконного компонента VCL можно изменить оконную процедуру с помощью функции
SetWindowLong
. Этот способ лучше не применять, поскольку код VCL не будет ничего "знать" об этом переопределении, и сообщения, получаемые компонентом не через оконную процедуру, а с помощью
Perform
, не будут перехвачены. Другой недостаток данного способа — то, что изменение некоторых свойств компонента (например,
FormStyle
и
BorderStyle
у формы) невозможно без уничтожения окна и создания нового. Для программиста это пересоздание окна выглядит прозрачно, но новое окно получит новую оконную процедуру, и нужно будет выполнять перехват заново. Отследить момент пересоздания окна можно с помощью сообщения
CM_RECREATEWND
, обработчик которого уничтожает старое окно, а создание нового окна откладывается до момента первого обращения к свойству
Handle
. Если перехватить по сообщение, то, в принципе, после выполнения стандартного обработчика можно зaново установить перехват с помощью SetWindowLong, но т.к. этот способ не дает никаких преимуществ перед другими, более простыми, им все равно лучше не пользоваться.
2. Можно создать собственный метод обработки сообщения и поместить указатель на него в свойство
WindowProc
. При этом старый указатель обычно запоминается, т.к. новый обработчик обрабатывает лишь некоторые сообщения, а остальные передает старому. Достоинство этого способа — то, что метод, указатель на который помещается в
WindowProc
, не обязан принадлежать тому компоненту, сообщения которого перехватываются. Это позволяет, во-первых, создавать компоненты, которые влияют на обработку сообщений родительскими формами, а во-вторых, реализовывать нестандартную обработку сообщений стандартными компонентами, не порождая от них наследника.
3. При написании нового компонента можно перекрыть виртуальный метод
WndProc
и реализовать обработку нужных сообщений в нем. Это позволяет компоненту перехватывать сообщения в самом начале цепочки (за исключением внешних обработчиков, установленных с помощью свойства
WindowProc
— здесь разработчик компонента не властен).
4. Наиболее удобный способ самостоятельной обработки событий — написание их методов-обработчиков. Этот способ встречается чаще всего. Его недостатком является то, что номера обрабатываемых сообщений должны быть известны на этапе компиляции. Для системных сообщений и внутренних сообщений VCL это условие выполняется, но далее мы будем говорить об определяемых пользователем сообщениях, номера которых в некоторых случаях на этапе компиляции неизвестны. Обрабатывать такие сообщения с помощью методов с директивой невозможно.
5. Для перехвата сообщений, которые не были обработаны с помощью методов-обработчиков, можно перекрыть виртуальный метод.
6. И наконец, можно написать оконную процедуру и поместить указатель на нее в свойство
DefWndProc
. Этот способ по своим возможностям практически эквивалентен предыдущему, но менее удобен. Однако предыдущий способ пригоден только для создания собственного компонента, в то время как
DefWndProc
можно изменять у экземпляров существующих классов. Напомним, что этот способ не подходит для форм, у которых
FormStyle = fsMDIForm
, т.к. такие формы игнорируют значение свойства
DefWndProc
.
Для перехвата сообщений неоконных визуальных компонентов допустимы все перечисленные способы, за исключением первого и последнего.
Метод
WndProc
оконного компонента транслирует сообщения от мыши неоконным визуальным компонентам, родителем которых он является. Например. если положить на форму компонент
TImage
и переопределить у этой формы метод для обработки сообщения
WM_LBUTTONDOWN
, то нажатие кнопки мыши над
TImage
не приведет к вызову этого метода, т.к.
WndProc
передаст это сообщение в
TImage
,
и
Dispatch
не будет вызван. Но если переопределить
WndProc
или изменить значение свойства
WindowProc
(т.е. использовать второй или третий метод перехвата), то можно получать и обрабатывать и те "мышиные" сообщения, которые должны транслироваться неоконным дочерним компонентам. Это общее правило: чем раньше встраивается собственный код в цепочку обработки сообщений, тем больше у него возможностей. Как мы уже говорили, начиная с BDS 2006 появился еще один способ перехвата сообщений — перекрытие метода
PreProcessMessage
. Этот способ нельзя ставить в один ряд с перечисленными ранее шестью способами, т.к. он имеет два существенных отличия от них. Во-первых, с помощью этого способа перехватываются все сообщения, попавшие в петлю сообщений, а не только те, которые посланы конкретному компоненту, из-за чего может понадобиться дополнительная фильтрация сообщений. Во-вторых, метод
PreProcessMessage
перехватывает сообщения, попавшие в петлю сообщений, а не в оконную процедуру компонента. С одной стороны, это даёт возможность перехватывать те сообщения, которые метод
Аррlication.ProcessMessage
не считает нужным передавать в оконную процедуру, но с другой стороны, не позволяет перехватывать те сообщения, которые окно получает, минуя петлю сообщений (например, те, которые отправлены с помощью
SendMessage
или
Perform
). По этим причинам область применения данного способа совсем другая, чем у способов, связанных с внедрением кода в оконную процедур. Перекрытие
PreProcessMessage
сопоставимо, скорее, с использованием события
Application.OnMessage
.
Различные способы перехвата сообщений иллюстрируются рядом примеров на прилагающемся к книге компакт-диске: использование свойства
WindowProc
показано в примерах
Line
,
CoordLabel
и
PanelMsg
, перекрытие метода
WndProc
— в примере
NumBroadcast
, создание метода для обработки сообщения — в примере
ButtonDel
.
1.1.9. Сообщения, определяемые пользователем
Сообщения очень удобны в тех случаях, когда нужно заставить окно выполнить какое-то действие. Поэтому Windows предоставляет возможность программисту создавать свои сообщения. Существуют три типа пользовательских сообщений:
□ сообщения оконного класса;
□ сообщения приложения;
□ глобальные (строковые) сообщения.
Для каждого из них выделен отдельный диапазон номеров. Номера стандартных сообщений лежат в диапазоне от 0 до
WM_USER-1
(
WM_USER
— константа, для 32-разрядных версий Windows равная 1024).
Сообщения оконного класса имеют номера в диапазоне от
WM_USER
до
WM_APP-1
(
WM_APP
имеет значение 32 768). Программист может выбирать произвольные номера для своих сообщений в этом диапазоне. Каждое сообщение должно иметь смысл только для конкретного оконного класса. Для различных оконных классов можно определять сообщения, имеющие одинаковые номера. Система никак не следит за тем, чтобы сообщения, определенные для какого-либо оконного класса, посылались только окнам этого класса — программист должен сам об этом заботиться. В этом же диапазоне лежат сообщения, специфические для стандартных оконных классов
'BUTTON'
,
'EDIT'
,
'LISTBOX'
,
'COMBOBOX'
и т.п.
Использование сообщений из этого диапазона иллюстрируется примером ButtonDel.
Диапазон от
WM_APP
до 49 151 (для этого значения константа не предусмотрена) предназначен для сообщений приложения. Номера этих сообщений также выбираются программистом произвольно. Система гарантирует, что ни один из стандартных оконных классов не задействует сообщения из этого диапазона. Это позволяет выполнять их широковещательную в пределах приложения рассылку. Ни один из стандартных классов не откликнется на такое сообщение и не выполнит нежелательных действий.
Упоминавшиеся ранее внутренние сообщения VCL с префиксами
CM_
и
CN_
имеют номера в диапазоне от 45 056 до 49 151, т.е. используют часть диапазона сообщений приложения. Таким образом, при использовании VCL диапазон сообщений приложения сокращается до
WM_APP..45055
. Сообщения оконного класса и приложения пригодны и для взаимодействия с другими приложениями, но при этом отправитель должен быть уверен, что адресат правильно его поймет. Широковещательная рассылка при этом исключена — реакция других приложений, которые также получат это сообщение, может быть непредсказуемой. Если все же необходимо рассылать широковещательные сообщения между приложениями, то следует воспользоваться глобальными сообщениями, для которых зарезервирован диапазон номеров от 49 152 до 65 535.