О чём не пишут в книгах по 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
, который откладывает уничтожение объекта до того момента, когда это будет безопасно, но остальные компоненты подобного метода не имеют.
Поделиться с друзьями: