Механизм обработки сообщений в Delphi
Последовательность обработки сообщений в Delphi
Все классы Delphi имеют встроенный механизм обработки сообщений, называемый обработчики сообщений. Класс получает какое-либо сообщение и вызывает один из набора определенных методов в зависимости от полученного сообщения. Если соответствующий метод не определен, то вызывается заданный по умолчанию обработчик. Более подробно этот механизм работает следующим образом.
После того, как сообщение получено, система сообщений VCL выполняет большую предварительную работу по его обработке.
Как уже отмечалось выше, первоначально сообщение обрабатывается методом TApplication.ProcessMessage, который выбирает его из очереди в основном цикле сообщений. При этом проверяет содержимое поля FOnMessage (фактически проверяет наличие’обработчика у события OnMessage) и, если оно не пусто, то вызывает обработчик этого события, а если поле пусто (Nil), то вызывает функцию API DispatchMessage(Msg). При отправке сообщения этого не происходит.
Если обработчик события OnMessage не определен, то для обработки полученного сообщения вызывается функция API DispatchMessage, которая передает сообщение главной процедуре окна.
Рассмотрим цикл обработки сообщения после поступления его в главное окно компонента. Последовательность обработки сообщения представлена на следующем рисунке:
Видно, что сообщение передается в MainWndProc, далее в WndProc, далее в Dispatch, далее в DefaultHandler.
В Delphi предусмотрен главный не виртуальный метод MainWndProc(Var Message: TMessage) для окна каждого компонента. Он содержит блок обработки особых ситуаций, передавая структуру сообщения от Windows до виртуального метода, определенного в свойстве WindowProc. При этом этот метод обрабатывает любые исключения, которые происходят в течение обработки сообщения, вызывая метод HandleException приложения. Начиная с этого места можно обеспечить специальную обработку сообщения, если это требуется логикой работы вашей программы. Обычно на этом этапе обработку изменяют, чтобы не дать состояться стандартной обработке VCL.
По умолчанию значение свойства WindowProc объекта инициализируется адресом виртуального метода WndProc. Далее, если нет зарегистрированных перехватчиков сообщения типа TWindowHook, WndProc вызывает виртуальный метод TObject.Dispatch, который, используя поле Msg структуры поступившего сообщения, определяет, находится ли это сообщение в списке обработчиков сообщения для данного объекта. Если объект не обрабатывает сообщение, исследуется список обработчиков сообщений предков. Если такой метод, в конце концов, будет найден, то он вызывается, в противном случае вызывается виртуальный метод DefaultHandler.
Наконец сообщение достигает соответствующей процедуры его обработки, где производится предусмотренная для него обработка. С помощью ключевого слова Inherited оно далее отправляется для обработки в предках. После этого сообщение также попадает в метод DefaultHandler, который производит завершающие действия по обработке сообщений и передает его процедуре DefWindowProc (DefMDIProc) для стандартной обработки Windows.
Обработка сообщений компонентами Delphi
Таким образом, краткое описание последовательности обработки сообщения выглядит следующим образом. Все сообщения первоначально проходят через метод, адрес которого указан в свойстве WindowProc. По умолчанию это метод WndProc. После чего они разделяются и рассылаются по своим методам сообщений. В конце они вновь сходятся в методе DefaultHandler, если не были обработаны ранее или в обработчиках вызывается унаследованный обработчик (Inherited). Следовательно, у компонентов Delphi есть следующие возможности для обработки сообщений:
а) До того, как какой-нибудь обработчик сообщения увидит сообщение. В этом случае требуется либо замена адреса метода в свойстве WindowProc, либо замещение метода TControl.WndProc.
• Свойство WindowProc объявлено следующим образом:
Туре TWndMethod=Procedure(Var Message: TMessage) Of Object;
Property WindowProc: TWndMethod;
Фактически, используя свойство WindowProc, можно создать метод типа TWndMethod и временно заменить исходный метод на созданный, однако, поскольку адрес метода в свойстве WindowProc не сохраняется, то необходимо предварительно сохранять адрес исходного метода WndProc, чтобы его можно было восстановить позднее.
OldWndProc: TWndMethod;
procedure NewWndProc(var Message: TMessage);
procedure TForm1.NewWndProc(var Message: TMessage);
var Ch: char;
begin
if message.Msg= WM_MOUSEMOVE then begin
Edit1.Text:=’x=’+inttostr(message.LParamLo)+’, y=’+inttostr(message.LParamHi);
end
else WndProc(Message);
end;procedure TForm1.FormCreate(Sender: TObject);
begin
OldWndProc:=WindowProc;
end;procedure TForm1.CheckBox1Click(Sender: TObject);
begin
If CheckBox1.Checked then WindowProc:= NewWndProc
else WindowProc:= OldWndProc;
end;
б) Внутри соответствующего метода сообщения.
Приведем еще один аналогичный пример.
Воспользуется сообщением, посылаемым компонентам для перерисовки WMPAINT.
В классе TForml объявим этот метод с целью его переопределения и представим реализацию переопределенного метода сообщения:
Туре TForml=Class(TForm)
… // Все иные необходимые объявления
Protected
Procedure WMPaint(Var Msg: TWMPaint); Message WM_PAINT; End;
Procedure TForml.WMPaint(Var Msg: TWMPaint); Begin
If CheckBox1.Checked Then ShowMessage(‘О6pa6oтчик сообщения!’);
Inherited;
End;
При переопределении конкретных обработчиков сообщений всегда целесообразно вызывать Inherited для выполнения базовой обработки сообщения, которое необходимо Windows.
в) После того, как каждый из соответствующих сообщению методов увидит его.
В этом случае необходимо замещать метод DefaultHandler.
procedure DefaultHandler(var Message); override;
procedure TForm1.DefaultHandler(var Message);
var i:integer;
begin
if Cardinal(Message)=WM_defh then
for i:= 0 to 10 do begin
beep;
sleep(100);
end
else
inherited;
end;procedure TForm1.Button5Click(Sender: TObject);
begin
SendMessage(Handle,WM_defh,0,0);
end;
Связь между сообщениями и событиями
Многие события VCL Delphi непосредственно связаны с сообщениями Windows. В справочной системе Delphi эти соответствия перечислены. Представлены они в табл.1.
Таблица 1
Событие VCL | Сообщение Windows | Событие VCL | Сообщение Windows |
OnActivate | WM_ACTIVATE | OnKeyPress | WM_CHAR |
OnClick | WM_LBUTTONDOWN | OnKeyUp | WM_KEYUP |
OnCreate | WM_CREATE | OnPaint | WM_PAINT |
OnDblClick | WM_LBUTTONDBLCLK | OnResize | WM_SIZE |
OnKeyDown | WM_KEYDOWN | OnTimer | WM_TIMER |
He стоит создавать обработчики сообщений, если для него имеется предопределенное событие. В подобных случаях целесообразно использовать обработку события, поскольку на нее накладывается меньше ограничений.