Освобождение кнопок в списке в OnClick
В упрощенной форме я пытаюсь создать список динамически создаваемых кнопок. При нажатии на одну из кнопок она должна быть удалена из списка, а ее объект должен быть освобожден. Мой подход:
- Создать
TList<TButton>
- Создать пару
TButton
объекты и добавить их вTList<TButton>
- Назначьте
Form
какParent
для каждого из созданныхTButton
объекты - Назначить
Position
для каждого из созданныхTButton
объекты - Назначить
OnClick
метод-обработчик для каждого из созданныхTButton
объекты OnClick
Обработчик устанавливаетSender
TButton
"sParent
вnil
и удаляет его изTList<TButton>
так что ARC может освободитьTButton
объект, на который нажали.
Когда я нажимаю на одну из динамически созданных кнопок, я получаю "Ошибка сегментации". Я подозреваю, что это потому, что я освобождаю TButton
объект в своем собственном OnClick
обработчик и класс пытается сделать что-то еще с этим после моего обработчика.
Я проверил это на Android. Я предполагаю, что то же самое произойдет на iOS или любой другой платформе ARC в этом отношении.
Есть ли лучший / правильный способ сделать это или другой подход, которому я должен следовать, чтобы заставить его работать так, как я хочу?
Вот пример кода. Это для формы с одной кнопкой времени разработки (Button1
) в теме. Повторное нажатие этой кнопки динамически создает новые кнопки и добавляет их в список.
unit Unit2;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.Controls.Presentation, FMX.StdCtrls, System.Generics.Collections;
type
TForm2 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
ButtonList : TList<TButton>;
procedure ButtonClick(Sender: TObject);
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.fmx}
procedure TForm2.ButtonClick(Sender: TObject);
var
pos : Integer;
begin
pos := ButtonList.IndexOf(TButton(Sender));
TButton(Sender).Parent := nil;
ButtonList.Delete(pos);
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
ButtonList := TList<TButton>.Create;
end;
procedure TForm2.Button1Click(Sender: TObject);
var
pos : Integer;
begin
pos := ButtonList.Add(TButton.Create(nil));
ButtonList.Items[pos].Parent := Form2;
ButtonList.Items[pos].Position.Y := 50 * ButtonList.Count;
ButtonList.Items[pos].OnClick := ButtonClick;
end;
end.
1 ответ
Когда я нажимаю на одну из динамически созданных кнопок, я получаю "Ошибка сегментации". Я подозреваю, что это потому, что я освобождаю объект TButton в своем собственном обработчике OnClick, и класс пытается сделать с ним некоторые другие вещи после моего обработчика.
Это именно то, что происходит. После выхода из обработчика событий RTL по-прежнему требуется доступ к объекту кнопки, чтобы завершить обработку щелчка и обработку сообщения. Никогда не безопасно уничтожать объект пользовательского интерфейса из его собственных событий. Таким образом, вы должны убедиться, что объект остается живым во время обработки события.
Я проверил это на Android. Я предполагаю, что то же самое произойдет на iOS или любой другой платформе ARC в этом отношении.
Да. И это также произойдет на не-ARC платформах, если вы попытаетесь Free
кнопка явно, например:
procedure TForm2.ButtonClick(Sender: TObject);
var
btn: TButton;
begin
btn := TButton(Sender);
ButtonList.Remove(btn);
{$IFDEF AUTOREFCOUNT}
btn.Parent := nil;
{$ELSE}
btn.Free;
{$ENDIF}
end;
Есть ли лучший / правильный способ сделать это или другой подход, которому я должен следовать, чтобы заставить его работать так, как я хочу?
Вы могли бы иметь OnClick
обработчик отправляет асинхронное сообщение в основной поток (например, путем вызова TThread.Queue()
Внутри TThread.CreateAnonymousThread()
или же TTask.Run()
) и затем немедленно завершите работу, позволяя обработчику сообщений освободить кнопку в более позднее время, когда кнопка больше не используется, например:
procedure TForm2.ButtonClick(Sender: TObject);
var
btn: TButton
begin
btn := TButton(Sender);
ButtonList.Remove(btn);
TThread.CreateAnonymousThread(
procedure
begin
TThread.Queue(nil,
procedure
begin
btn.DisposeOf;
end
);
end
).Start;
end;
Или вы можете переместить объект кнопки в другой список, а затем запустить короткий таймер, чтобы пройти по этому списку, освобождая его объекты, например:
unit Unit2;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.Controls.Presentation, FMX.StdCtrls, System.Generics.Collections;
type
TForm2 = class(TForm)
Button1: TButton;
Timer1: TTimer;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
ButtonList : TList<TButton>;
DisposeList : TList<TButton>;
procedure ButtonClick(Sender: TObject);
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.fmx}
procedure TForm2.ButtonClick(Sender: TObject);
var
btn: TButton;
begin
btn := TButton(Sender);
ButtonList.Remove(btn);
DisposeList.Add(btn);
Timer1.Enabled := true;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
ButtonList := TList<TButton>.Create;
DisposeList := TList<TButton>.Create;
end;
procedure TForm2.Button1Click(Sender: TObject);
var
btn: TButton;
begin
btn := TButton.Create(nil);
ButtonList.Add(btn);
btn.Parent := Self;
btn.Position.Y := 50 * ButtonList.Count;
btn.OnClick := ButtonClick;
end;
procedure TForm2.Timer1Timer(Sender: TObject);
var
btn: TButton;
begin
Timer1.Enabled := False;
for btn in DisposeList do
btn.DisposeOf;
DisposeList.Clear;
end;
end.