在 OnClick 中释放列表中的按钮
Freeing buttons in a list in OnClick
我试图以简化的形式实现的是创建一个动态创建的按钮列表。当单击其中一个按钮时,它应该从列表中删除并且它的对象应该被释放。我的方法是:
- 创建
TList<TButton>
- 创建几个
TButton
对象并将它们添加到 TList<TButton>
- 为每个创建的
TButton
对象分配 Form
作为 Parent
- 为每个创建的
TButton
个对象分配一个 Position
- 为每个创建的
TButton
对象分配一个 OnClick
处理程序方法
OnClick
处理程序将 Sender
TButton
的 Parent
设置为 nil
并将其从 TList<TButton>
中删除,这样ARC 可以释放被点击的 TButton
对象。
当我点击其中一个动态创建的按钮时,我得到一个 "Segmentation Fault"。我怀疑这是因为我在它自己的 OnClick
处理程序中释放 TButton
对象,而 class 试图在我的处理程序之后用它做一些其他事情。
我已经在 Android 上测试过了。我假设在 iOS 或任何其他 ARC 平台上也会发生同样的情况。
有没有 better/right 方法可以做到这一点,或者我应该遵循另一种方法来让它按我想要的方式工作?
这是一些示例代码。它适用于带有一个设计时按钮 (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.
When I click on one of the dynamically created buttons, I get a "Segmentation Fault". I suspect it is because I am freeing the TButton object in its own OnClick handler and the class is trying to do some other stuff with it after my handler.
这正是正在发生的事情。事件处理程序退出后,RTL 仍需要访问按钮对象以完成处理点击和消息处理。从一个 UI 对象自身的事件中销毁它是不安全的。所以你必须确保对象在事件处理期间保持活动状态。
I have tested this on Android. I assume the same will happen on iOS, or any other ARC platform for that matter.
是的。如果您尝试明确 Free
按钮,它也会在非 ARC 平台上发生,例如:
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;
Is there a better/right way to do this, or another approach I should be following to get it working the way I want?
您可以让 OnClick
处理程序 post 向主线程发送异步消息(例如通过在 10.2 Tokyo 及更高版本中调用 TThread.Queue()
inside of TThread.CreateAnonymousThread()
/TTask.Run()
, or using TThread.ForceQueue()
),然后立即退出,让消息处理程序在以后不再使用按钮时释放按钮,例如:
procedure TForm2.ButtonClick(Sender: TObject);
var
btn: TButton
begin
btn := TButton(Sender);
ButtonList.Remove(btn);
TThread.CreateAnonymousThread(
procedure
begin
TThread.Queue(nil, btn.DisposeOf);
end
).Start;
{ or:
TThread.ForceQueue(nil, btn.DisposeOf);
}
end;
或者,您可以将按钮对象移动到另一个列表,然后启动一个短计时器(或使用 TThread.(Force)Queue()
消息)以 运行 通过该列表释放其对象,例如:
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
{ Private declarations }
ButtonList : TList<TButton>;
DisposeList : TList<TButton>;
procedure ButtonClick(Sender: TObject);
procedure DisposeOfButtons;
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;
{ or:
TThread.CreateAnonymousThread(
procedure
begin
TThread.Queue(nil, DisposeOfButtons);
end
).Start;
}
{ or:
TThread.ForceQueue(nil, DisposeOfButtons);
}
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.DisposeOfButtons;
var
btn: TButton;
begin
for btn in DisposeList do
btn.DisposeOf;
DisposeList.Clear;
end;
procedure TForm2.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
DisposeOfButtons;
end;
end.
我试图以简化的形式实现的是创建一个动态创建的按钮列表。当单击其中一个按钮时,它应该从列表中删除并且它的对象应该被释放。我的方法是:
- 创建
TList<TButton>
- 创建几个
TButton
对象并将它们添加到TList<TButton>
- 为每个创建的
TButton
对象分配Form
作为Parent
- 为每个创建的
TButton
个对象分配一个Position
- 为每个创建的
TButton
对象分配一个OnClick
处理程序方法 OnClick
处理程序将Sender
TButton
的Parent
设置为nil
并将其从TList<TButton>
中删除,这样ARC 可以释放被点击的TButton
对象。
当我点击其中一个动态创建的按钮时,我得到一个 "Segmentation Fault"。我怀疑这是因为我在它自己的 OnClick
处理程序中释放 TButton
对象,而 class 试图在我的处理程序之后用它做一些其他事情。
我已经在 Android 上测试过了。我假设在 iOS 或任何其他 ARC 平台上也会发生同样的情况。
有没有 better/right 方法可以做到这一点,或者我应该遵循另一种方法来让它按我想要的方式工作?
这是一些示例代码。它适用于带有一个设计时按钮 (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.
When I click on one of the dynamically created buttons, I get a "Segmentation Fault". I suspect it is because I am freeing the TButton object in its own OnClick handler and the class is trying to do some other stuff with it after my handler.
这正是正在发生的事情。事件处理程序退出后,RTL 仍需要访问按钮对象以完成处理点击和消息处理。从一个 UI 对象自身的事件中销毁它是不安全的。所以你必须确保对象在事件处理期间保持活动状态。
I have tested this on Android. I assume the same will happen on iOS, or any other ARC platform for that matter.
是的。如果您尝试明确 Free
按钮,它也会在非 ARC 平台上发生,例如:
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;
Is there a better/right way to do this, or another approach I should be following to get it working the way I want?
您可以让 OnClick
处理程序 post 向主线程发送异步消息(例如通过在 10.2 Tokyo 及更高版本中调用 TThread.Queue()
inside of TThread.CreateAnonymousThread()
/TTask.Run()
, or using TThread.ForceQueue()
),然后立即退出,让消息处理程序在以后不再使用按钮时释放按钮,例如:
procedure TForm2.ButtonClick(Sender: TObject);
var
btn: TButton
begin
btn := TButton(Sender);
ButtonList.Remove(btn);
TThread.CreateAnonymousThread(
procedure
begin
TThread.Queue(nil, btn.DisposeOf);
end
).Start;
{ or:
TThread.ForceQueue(nil, btn.DisposeOf);
}
end;
或者,您可以将按钮对象移动到另一个列表,然后启动一个短计时器(或使用 TThread.(Force)Queue()
消息)以 运行 通过该列表释放其对象,例如:
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
{ Private declarations }
ButtonList : TList<TButton>;
DisposeList : TList<TButton>;
procedure ButtonClick(Sender: TObject);
procedure DisposeOfButtons;
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;
{ or:
TThread.CreateAnonymousThread(
procedure
begin
TThread.Queue(nil, DisposeOfButtons);
end
).Start;
}
{ or:
TThread.ForceQueue(nil, DisposeOfButtons);
}
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.DisposeOfButtons;
var
btn: TButton;
begin
for btn in DisposeList do
btn.DisposeOf;
DisposeList.Clear;
end;
procedure TForm2.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
DisposeOfButtons;
end;
end.