Содержание материала

Особые сообщения

 

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

 

Сообщение WM_CopyData используется для передачи блока данных от одного процесса к другому. В 32-разрядных версиях Windows память, выделенная процессу, недоступна для всех остальных процессов. Поэтому просто передать указатель другому процессу нельзя - он не сможет получить доступ к этой области памяти. Для сообщения WM_CopyData приходится делать исключение: блок данных временно становится доступным другому процессу. Это требует определённой синхронности действий от двух процессов, поэтому для отправки этого сообщения можно использовать только SendMessage, прямо вызывающую оконную процедуру. PostMessage использовать нельзя.

 

Сообщение WM_Paint предназначено для перерисовки клиентской области окна. Если изображение сложное, перерисовка занимает много времени. Чтобы улучшить быстродействие системы, авторы Windows сделали так, что сообщение WM_Paint пропускает все остальные сообщения в очереди, и передаётся окну только тогда, когда в очереди не остаётся никаких других сообщений. Если в очереди оказываются несколько сообщений WM_Paint, они объединяются в одно. Просто так послать сообщение WM_Paint невозможно. Для этого надо сначала объявить, что окно или его часть нуждаются в перерисовке (InvalidateRect, InvalidateRgn).

 

При обработке сообщений от клавиатуры можно использовать функцию GetKeyState, которая возвращает состояние любой клавиши (нажата-отпущена) в момент возникновения данного события. Именно в момент возникновения, а не в момент вызова функции.

Компоненты, влияющие на обработку событий

 

Так как стандартные средства Delphi не позволяют использовать все инструменты Win API для работы с окнами, иногда приходится писать компоненты, модифицирующие форму. Мне, например, приходилось писать компоненты, при помещении которых на форму она становится непрямоугольной или полупрозрачной. Очень часто таким компонентам приходится обрабатывать те сообщения, которые предназначены форме-хозяину. Delphi даёт возможность компоненту перехватить сообщения, хотя, на мой взгляд, механизм перехвата оставляет желать лучшего, потому что он не допускает возможности взаимодействия нескольких перехватчиков.

 

Вместо общего описания алгоритма перехвата я далее просто приведу один из способов сделать это. Способ этот не единственный верный, многие детали можно модифицировать для нужд конкретной задачи, однако основная идея (и основные недостатки) никуда не денутся. Далее я буду предполагать, что компонент перехватывает сообщения владельца (Owner). Что нужно изменить, чтобы он начал перехватывать сообщения родителя (Parent), я скажу чуть позже.

 

Прежде всего, владелец должен быть окном, поэтому в конструкторе компонента надо проверить класс владельца. Это очень полезно ещё и потому, что тогда в остальных методах объекта можно обойтись без такой проверки, что делает код более эффективным. Чтобы предотвратить создание компонента, достаточно в его конструкторе возбудить какое-либо исключение

 

Метод компонента не может быть оконной процедурой, потому что методу всегда неявно передаётся <лишний> параметр Self. Поэтому нужна генерация специального кода входа и выхода для того, чтобы вызывать метод вместо оконной процедуры. Этот код генерируется с помощью специальной функции Delphi, которая создаёт в памяти нужный код и возвращает на него указатель. Поэтому компонент должен иметь указатель на этот код (я условно назову этот указатель NewWndProc). Сам метод, обрабатывающий события (условно - HookWndProc) должен иметь один параметр-переменную типа TMessage, и может быть как статическим, тик и виртуальным или динамическим. Кроме того, нужен указатель на старую процедуру, которая была до установки компонента (OldWndProc). Далее, компонент должен содержать два метода для перехвата и освобождения, которые выглядят так:

Code:

procedure TMyComponent.HookOwner;

begin

if Assigned(Owner) then

  begin

   OldWndProc := Pointer(GetWindowLong(TForm(Owner).Handle, GWL_WndProc));

   NewWndProc := MakeObjectInstance(HookWndProc);

   SetWindowLong(TForm(Owner).Handle, GWL_WndProc, LongInt(NewWndProc))

  end

end;

 

procedure TMyComponent.UnhookOwner;

begin

if Assigned(Owner) and Assigned(OldWndProc) then

  SetWindowLong(TForm(Owner).Handle, GWL_WndProc, LongInt(OldWndProc));

if Assigned(NewWndProc) then

  FreeObjectInstance(NewWndProc);

NewWndProc := nil;

OldWndProc := nil

end;

  

Функции Win API GetWindowLong и SetWindowLong предназначены для получения и изменения 32-разрядного значения, связанного с данным окном. В данном случае мы с их помощью работаем с 32-разрядным параметром - адресом оконной процедуры. Изменение адреса оконной процедуры с помощью SetWindowLong и есть то самое порождение оконного подкласса, о котором я писал ранее. Функция MakeObjectInstance - это та самая функция, которая превращает метод в оконную процедуру. FreeObjectInstance освобождает память, выделенную для создания кода входа и выхода функцией MakeObjectInstance.

 

Было бы глупо перехватывать сообщения и при этом не иметь возможности вызвать ту оконную процедуру, которая была до перехвата. Если необходимо вызвать её для обработки сообщения Msg с параметрами WParam и LParam, нужно воспользоваться следующим кодом:

 

CallWindowProc(OldWndProc, TForm(Owner).Handle, Msg, WParam, LParam);

 

Результатом работы этой функции будет число, возвращаемое оконной процедурой.

 

Вызов процедуры HookOwner я обычно помещаю в самый конец конструктора компонента, UnhookOwner - в самое начало деструктора. Но в некоторых ситуациях VCL Delphi уничтожает окно и вновь создаёт его с новыми свойствами. Это происходит очень быстро, пользователь ничего не замечает. (Такое <пересоздание> формы может потребоваться при изменении во время выполнения свойств FormStyle, BorderStyle и BorderIcons.) Однако VCL ничего не знает о перехвате и поэтому не может корректно удалить его, а уж о восстановлении его потом и речи быть не может. Чтобы избежать такой ситуации, необходимо обрабатывать сообщение CM_RecreateWnd: перед вызовом унаследованного метода для обработки этого события компонент должен снять перехват, после - восстановить его.

 

Если форма содержит несколько компонентов, перехватывающих сообщения, могут возникнуть конфликты. Снятие и восстановление перехвата через обработку сообщения CM_RecreateWnd безопасно в этом смысле, потому что компоненты обрабатывают это сообщение в порядке, обратном порядку создания. Но если приходится удалять компонент-перехватчик, он не исключает себя из цепочки перехватчиков, а просто обрывает её, и все перехватчики, созданные после него, оказываются не у дел. Именно это я и считаю главным недостатком механизма перехвата.

 

Если есть необходимость перехватывать сообщения не владельца, а родителя, нужно сделать всё то же самое с точностью до замены Owner на Parent. Но владельца компонент в принципе не может поменять, а вот родителя - вполне. Поэтому нужно ещё перекрыть виртуальный метод SetParent, в котором снимается перехватчик со старого родителя, затем вызывается унаследованный SetParent, затем уже ставится обработчик на нового родителя

 

Добавить комментарий

Не использовать не нормативную лексику.

Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.

ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!


Защитный код
Обновить