1.2.6. Пример ButtonDel

We use cookies. Read the Privacy and Cookie Policy

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

Примечание

Метод TCustomForm.Release на поверку тоже оказывается не совсем безопасным — подробнее об этом написано в разд. 3.4.3.

Очевидно, что для безопасного удаления кнопки эту операцию следует отложить до того момента, когда все методы удаляемой кнопки уже закончат свою работу. Вставить требуемый код в обработчик WM_COMMAND формы достаточно сложно, поэтому мы будем использовать другой способ: пусть обработчик кнопки посылает форме сообщение, в обработчике которого она будет удалять кнопку. Здесь важно, что сообщение посылается, а не отправляется, т. е. ставится в очередь, из которой извлекается уже после того, как будет полностью обработано сообщение WM_COMMAND. В этом случае методы удаляемой кнопки не будут вызваны, и удаление пройдет без неприятных последствий.

Как раз для подобных случаев и предусмотрена возможность определять свои сообщения, т. к. ни одно из стандартных для наших целей не подходит. Свое сообщение мы будем посылать только одному окну, без широковещания, поэтому для него вполне подходит диапазон сообщений класса. Номер сообщения становится известным на этапе компиляции, поэтому для обработки этого сообщения мы можем применить самый удобный способ написать метод-обработчик с помощью директивы message. С учётом всего этого код выглядит следующим образом (листинг 1.33).

Листинг 1.33. Модуль главной формы программы ButtonDel

unit BDMain;

interface

uses

 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;

// Определяем свое сообщение. Константа, добавляемая к

// WM_USER, может иметь произвольное значение в диапазоне

// от 0 до 31743.

const

 WM_DELETEBUTTON = WM_USER + 1;

type TForm1 = class(TForm)

 BtnDeleteSelf: TButton;

 procedure BtnDeleteSelfClick(Sender: TObject);

private

 // Определяем метод — обработчик событий WM_DELETEBUTTON.

 // Ему будет передано управление через Dispatch.

 procedure WMDeleteButton(var Msg: TMessage); message WM_DELETEBUTTON;

public

 { Public declarations }

end;

var

 Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.BtnDeleteSelfClick(Sender: TObject);

begin

 // Помещаем сообщение WM_DELETEBUTTON в очередь формы.

 // Указатель на объект, который нужно удалить, помещаем

 // в LParam. В 32-разрядных версиях Windows указатель

 // можно помещать как в wParam, так и в lParam, но по

 // традиции, берущей начало в 16-разрядных версиях,

 // указатель обычно передают через lParam.

 PostMessage(Handle, WM_DELETEBUTTON, 0, LParam(BtnDeleteSelf));

 // Здесь принципиально использование PostMessage, а не

 // SendMessage. SendMessage в данном случае привел бы к

 // немедленному вызову оконной процедуры, и метод

 // WMDeleteButton был бы вызван до завершения работы

 // BtnDeleteSelfClick. Это привело бы к тому же

 // результату, что и прямой вызов BtnDeleteSelf.Free.

end;

procedure TForm1.WMDeleteButton(var Msg: TMessage);

begin

 // Просто удаляем объект, указатель на который передан

 // через lParam.

 TObject(Msg.LParam).Free;

end;

end.

Приведенный здесь способ хорошо работает в такой простой ситуации, но в более сложных случаях может не дать результата. Рассмотрим, например, ситуацию, когда на форме лежат две кнопки: Button1 и Button2. Обработчик нажатия Button1 содержит длительную операцию, и поэтому в нем вызывается Application.ProcessMessages. Обработчик нажатия Button2 содержит строку Button1.Free. Если после запуска программы сразу нажать Button2, проблем не возникнет и объект Button1 будет благополучно удален. Но если сначала нажать Button1, а затем — Button2, возникнет ошибка. Это произойдёт потому, что нажатие Button2 будет в данном случае обработано локальной петлей сообщения, и после обработки управление вернется Button1Click, а оттуда — в методы уже не существующего объекта Button1. Посылка в Button2Click сообщения форме здесь не поможет, потому что это сообщение также будет извлечено и обработано локальной петлей. Общего решения таких проблем, видимо, не существует. В сложных случаях можно посоветовать не удалять объект, а просто прятать его (Visible:= False) — видимый результат для пользователя будет тот же самый.

Данный текст является ознакомительным фрагментом.