Выбрать главу

1.1.8. Обработка сообщений с помощью VCL

При использовании VCL в простых случаях самостоятельно работать с оконными сообщениями нет нужды, поскольку практически все можно сделать с помощью свойств, методов и событий компонентов. Тем не менее, некоторые сообщения приходится обрабатывать вручную. Чаще всего это приходится делать при разработке собственных компонентов, но и в обычных приложениях это также может быть полезным.

Кроме сообщений, предусмотренных в системе, компоненты VCL обмениваются сообщениями, созданными авторами этой библиотеки. Эти сообщения имеют префиксы CM_ и CN_. Они нигде не документированы, разобраться с ними можно только по исходным кодам VCL. При разработке собственных компонентов приходится обрабатывать эти сообщения, которые мы здесь не будем полностью описывать, но некоторые из них будут упоминаться в описании работы VCL с событиями.

В Windows API нет понятия главного окна — все окна, не имеющие родителя (или владельца в терминах системы), равноценны, и приложение может продолжать работу после закрытия любых окон. Но в VCL введено понятие главной формы: форма, которая создается первой, становится главной, и ее закрытие означает закрытие всего приложения.

Если окно не имеет ни родителя, ни владельца в терминах системы (такие окна называются окнами верхнего уровня), то на панели задач появляется кнопка, связанная с этим окном (окно, имеющее владельца, также может обзавестись такой кнопкой, если оно создано со стилем WS_EX_APPWINDOW). Обычно в приложении одно окно главного уровня, и оно играет роль главного окна этого приложения, хотя система не запрещает приложению создавать несколько окон верхнего уровня (примеры — Internet Explorer, Microsoft Word). Разработчики VCL пошли по другому пути: окно верхнего уровня, ответственное за появление кнопки на панели задач, создается объектом Application. Дескриптор этого окна хранится в свойстве Application.Handle, а само оно невидимо, т.к. имеет нулевые размеры. Как и любое другое, это окно имеет оконную процедуру и может обрабатывать сообщения. Главная форма — это отдельное окно, не имеющее, с формальной точки зрения, никакого отношения к кнопке на панели задач. Видимость связи между этой кнопкой и главной формой обеспечивается взаимодействием объекта Application и объекта главной формы внутри VCL. Таким образом, даже простейшее VCL-приложение создает два окна: невидимое окно объекта Application и окно главной формы. Окно, создаваемое объектом Application, мы будем называть невидимым окном приложения. Невидимое окно приложения по умолчанию становится владельцем (в терминах системы) всех форм, у которых явно не установлено свойство Parent, в том числе и главной формы.

При обработке сообщений VCL решает две задачи: выборка сообщений из очереди и передача сообщения конкретному компоненту. Рассмотрим сначала первую задачу.

Выборкой сообщений из очереди занимается объект Application, непосредственно за извлечение и диспетчеризацию сообщения отвечает его метод ProcessMessage (листинг 1.13).

Листинг 1.13. Метод TApplication.ProcessMessage

function TApplication.ProcessMessage(var Msg: TMsg): Boolean;

var

 Unicode: Boolean;

 Handled: Boolean;

 MsgExists: Boolean;

begin

 Result := False;

 if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then

 begin

  Unicode := (Msg.hwnd <> 0) and IsWindowUnicode(Msg.hwnd);

  if Unicode then MsgExists := PeekMessageW(Msg, 0, 0, 0, PM_REMOVE)

  else MsgExists := PeekMessage(Msg, 0, 0, 0, PM_REMOVE);

  if not MsgExists then Exit;

  Result := True;

  if Msg.Message <> WM_QUIT then

  begin

   Handled := False;

   if Assigned(FOnMessage) then FOnMessage(Msg, Handled);

   if not IsPreProcessMessage(Msg) and not IsHintMsg(Msg) and not Handled and

    not IsMDIMsg(Msg) and not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then

   begin

    TranslateMessage(Msg);

    if Unicode then DispatchMessageW(Msg);

    else DispatchMessage(Msg);

   end;

  end else FTerminate := True;

 end;

end;

В этом коде отдельного комментария требует то, как используется функция PeekMessage. Сначала эта функция вызывается с параметром PM_NOREMOVE, — так выполняется проверка условия, что в очереди присутствует сообщение, а также выясняется, для какого окна предназначено первое сообщение в очереди. Само сообщение при этом остается в очереди. С помощью функции IsWindowUnicode производится проверка, использует ли окно-адресат кодировку ANSI или Unicode, и затем, в зависимости от этого, сообщение извлекается либо функцией PeekMessage, либо ее Unicode-аналогом PeekMessageW (о Unicode-аналогах функций см. разд. 1.1.12). При диспетчеризации сообщения также вызывается либо функция DispatchMessage, либо ее Unicode-аналог DispatchMessageW.