Чтение онлайн

ЖАНРЫ

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

 // RegisterWindowMessage, то поле FSendNumberMessage

 // будет иметь присвоенное ему по умолчанию значение 0,

 // а это - код сообщения WM_NULL. Таким образом, если в

 // это время окно получит сообщение WM_NULL, оно будет

 // неправильно обработано. Конечно, вероятность получения

 // WM_NULL во время выполнения унаследованного

 // конструктора крайне мала, но лучше подстраховаться и

 // сделать так, чтобы поле FSendNumberMessage на момент

 // первого вызова WndProc уже имело правильное значение.

 FSendNumberMessage := RegisterWindowMessage('WM_DelphiKingdom_APISample_SendNumber');

 inherited;

 //
Здесь мы меняем стиль окна поля ввода, добавляя в него

 // ES_NUMBER. Стиль ES_NUMBER запрещает полю ввода

 // вводить какие-либо символы, кроме цифр. Это уменьшает

 // риск ошибки ввода в тех случаях, когда требуется целое

 // неотрицательное число.

 SetWindowLong(EditNumber.Handle, GWL_STYLE, GetWindowLong(EditNumber.Handle, GWL_STYLE) or ES_NUMBER);

end;

procedure TForm1.BtnBroadcastClick(Sender: TObject);

var

 Num: Integer;

 Recipients: DWORD;

begin

 try

Num := StrToInt(EditNumber.Text);

// Для широковещательной рассылки сообщения служит

// функция BroadcastSystemMessage. В литературе обычно

// советуют использовать более простую функцию

// PostMessage, указывая в качестве адресата

// HWND_BROADCAST. Однако PostMessage рассылает

// сообщения только окнам верхнего уровня, не имеющим

// владельца (в терминах системы). Но главная форма

// приложения имеет владельца - это невидимое окно

// приложения, реализуемое объектом TApplication.

// Поэтому такое широковещательное сообщение главная

// форма приложения не получит — его получит только

// невидимое окно приложения (это сообщение можно

// будет перехватить, назначив обработчик

// Application.OnMessage - вручную или с помощью

// компонента TApplicationEvents). Чтобы главная форма

// тоже попала в список окон, получающих

// широковещательное сообщение, используется функция

// BroadcastSystemMessage.

Recipients := BSM_APPLICATIONS;

BroadcastSystemMessage(BSF_POSTMESSAGE, @Recipients, FSendNumberMessage, Num, 0);

 except

on EConvertError do

begin

Application.MessageBox(

'Введенное значение не является числом', 'Ошибка',

MB_OK or MB_ICONSTOP);

end;

 end;

end;

procedure TForm1.WndProc(var Msg: TMessage);

begin

 if Msg.Msg = FSendNumberMessage then

LabelNumber.Caption := IntToStr(Msg.WParam)

 else inherited;

end;

end.

Как уже отмечалось ранее, для обработки глобального сообщения нельзя использовать методы с директивой

message
, т.к. номер сообщения на этапе компиляции еще не известен. Здесь для обработки глобального сообщения мы перекрываем метод
WndProc
. Соответственно, все оконные сообщения, в том числе и те, которые окно получает при создании, будет обрабатывать перекрытый метод
WndProc
. Это значит, что поле
FSendNumberMessage
,
которое задействовано в этом методе, должно быть правильно инициализировано раньше, чем окно получит первое сообщение. Поэтому вызов функции
RegisterWindowMessage
выполнять, например, в обработчике события
OnCreate
формы уже поздно. Его необходимо выполнить в конструкторе формы, причем до того, как будет вызван унаследованный конструктор.

Примечание

Существует другой способ решения этой проблемы: метод

WndProc
должен проверять значение поля
FSendNumberMessage
, и, если оно равно нулю, сразу переходить к вызову унаследованного метода. В этом случае инициализировать
FSendNumberMessage
можно позже.

Нажатие на кнопку

BtnBroadcast
приводит к широковещательной отправке сообщения. Отправить широковещательное сообщение можно двумя способами: функцией
PostMessage
с адресатом
HWND_BROADCAST
вместо дескриптора окна и с помощью функции
BroadcastSystemMessage
. Первый вариант позволяет отправить сообщения только окнам верхнего уровня, не имеющим владельца в терминах системы. Таким окном в VCL-приложении является только невидимое окно приложения, создаваемое объектом
Application
. Главная форма имеет владельца в терминах системы — то самое невидимое окно приложения. Поэтому широковещательное сообщение, посланное с помощью
PostMessage
, главная форма не получит, это сообщение пришлось бы ловить с помощью события
Application.OnMessage
. Мы здесь применяем другой способ — отправляем сообщение с помощью функции
BroadcastSystemMessage
, которая позволяет указывать тип окон, которым мы хотим отправить сообщения. В частности, здесь мы выбираем тип
BSM_APPLICATION
, чтобы сообщение посылалось всем окнам верхнего уровня, в том числе и тем, которые имеют владельца. При таком способе отправки главная форма получит это широковещательное сообщение, поэтому его обработку можно реализовать в главной форме.

1.2.6. Пример ButtonDel

Программа ButtonDel демонстрирует, как можно удалить кнопку в обработчике нажатия этой кнопки. Очень распространенная ошибка — попытка написать код, один из примеров которого приведен в листинге 1.32.

Листинг 1.32. Неправильный вариант удаления кнопки в обработчике ее нажатия

procedure TForm1.Button1Click(Sender: TObject);

begin

 Button1.Free;

end;

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

WM_COMMAND
. При обработке форма выясняет, что источником сообщения является объект
Button1
и передает этому объекту сообщение
CN_COMMAND
.
Button1
, получив его, вызывает метод
Click
, который проверяет, назначен ли обработчик
OnClick
, и, если назначен, вызывает его. Таким образом, после завершения
Button1Click
управление снова вернется в метод
Click
объекта
Button1
, из него — в метод
CNCommand
, из него — в
Dispatch
, оттуда — в
WndProc
, а оттуда — в
MainWndProc
. А из
MainWndProc
управление будет передано в оконную процедуру, сформированную компонентом с помощью
MakeObjectInstance
. В деструкторе
Button1
эта оконная процедура будет уже удалена. Таким образом, управление получат последовательно пять методов уже не существующего объекта и одна несуществующая процедура. Это может привести к самым разным неприятным эффектам, но, скорее всего, — к ошибке Access violation (обращение к памяти, которую программа не имеет права использовать). Поэтому приведенный в листинге 1.32 код будет неработоспособным. В классе
TCustomForm
для безопасного удаления формы существует метод
Release
, который откладывает уничтожение объекта до того момента, когда это будет безопасно, но остальные компоненты подобного метода не имеют.

Поделиться с друзьями: