从 TPanel 中删除按钮,Firemonkey 版
Deleting Buttons from TPanel, Firemonkey Edition
使用 Delphi 10.2(在 Windows 10 "19H2" 下),我可以创建一个新的应用程序,在上面放置一个面板,以及一个包含两个项目的操作列表。这两个项目调用相同的例程,其目的是删除面板上的任何按钮,然后添加新按钮:
procedure TForm1.CreateNavPanelButtons(Action: TAction);
begin
NavPanel.RemoveObject(Btn);
Btn.DisposeOf; //problem line
Btn := MakeButton(Action);
NavPanel.AddObject(Btn);
end;
(我在这里简化为只使用一个按钮。)删除现有按钮,添加新按钮。如果我调用 DisposeOf(以释放按钮的内存),Window 对象变为无响应(无法调整大小、移动、关闭),直到我将注意力从应用程序移开。
我在下面包含了完整的代码:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
FMX.Controls.Presentation, System.Actions, FMX.ActnList;
type
TForm1 = class(TForm)
NavPanel: TPanel;
ActionList: TActionList;
acNextMenu: TAction;
acBackToMainMenu: TAction;
procedure FormCreate(Sender: TObject);
procedure acNextMenuExecute(Sender: TObject);
private
{ Private declarations }
public
Btn: TButton;
procedure CreateNavPanelButtons(Action: TAction);
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
function MakeButton(A: TAction): TButton;
begin
Result := TButton.Create(nil);
Result.Action := A;
Result.Text := (A as TAction).Text;
end;
procedure TForm1.acNextMenuExecute(Sender: TObject);
begin
CreateNavPanelButtons(acBackToMainMenu);
end;
procedure TForm1.CreateNavPanelButtons(Action: TAction);
begin
NavPanel.RemoveObject(Btn);
Btn.DisposeOf;
Btn := MakeButton(Action);
NavPanel.AddObject(Btn);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
CreateNavPanelButtons(acNextMenu);
end;
end.
表格如下:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 480
ClientWidth = 640
FormFactor.Width = 320
FormFactor.Height = 480
FormFactor.Devices = [Desktop]
OnCreate = FormCreate
DesignerMasterStyle = 0
object NavPanel: TPanel
Align = Top
Size.Width = 640.000000000000000000
Size.Height = 73.000000000000000000
Size.PlatformDefault = False
TabOrder = 0
end
object ActionList: TActionList
Left = 392
Top = 192
object acNextMenu: TAction
Category = 'Navigation'
Text = 'NextMenu'
OnExecute = acNextMenuExecute
end
object acBackToMainMenu: TAction
Category = 'Navigation'
Text = 'Back To &Main Menu'
OnExecute = FormCreate
end
end
end
您的代码存在问题,您正在删除当前正在执行操作的按钮 运行ning。当操作 returns 时,按钮不再存在,在 Windows 上它被 DisposeOf()
释放,在移动平台上它处于 "zombie" 状态。
解决方法是延迟删除按钮,直到操作结束。在标准 Windows 应用程序中,我会 post 给自己发送一条消息,以确保在我收到消息并可以调用 CreateNavPanelButtons()
之前操作已经结束。但我不确定这是否适用于所有其他平台。
以下应该适用于任何平台。
添加一个TTimer
、Enabled = False
、Interval = 1
。然后声明一个私有字段,格式为 Action: TAction
.
像这样更改任何更改 NavPanelButtons
的操作处理程序:
procedure TForm2.acNextMenuExecute(Sender: TObject);
begin
// CreateNavPanelButtons(acBackToMainMenu);
Action := acBackToMainMenu;
Timer1.Enabled := True;
end;
并添加 OnTimer
事件
procedure TForm2.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
if Action <> nil then
CreateNavPanelButtons(Action);
end;
避免TTimer
的更新
另一种不需要消息或计时器的解决方案是预先创建所有按钮,并且在程序 运行 期间根本不处理它们 运行。
它们可以分组到 TButtonList
列表中,这些列表将包含相关的按钮并同时显示。
当 TButtonList
需要显示时,NavPanel
中的旧按钮只需要通过 NavPanel.RemoveObject(B)
从面板中删除(不需要 B.DisposeOf
)在循环中。
最后,for b in ButtonList do NavPanel.AddObject(b)
将新的按钮列表添加到面板中。
这样做的缺点是内存使用量更大,以防万一。
汤姆答对了。我不喜欢定时器的用户,因为我不喜欢中断代码流,除非我不得不这样做,所以我设计了一个双面板系统:
TForm1 = class(TForm)
NavPanel1: TPanel;
NavPanel2: TPanel;
. . .
FrontPanel: TPanel;
BackPanel: TPanel;
我在释放那里的东西后把所有的按钮都放在了 BackPanel 上,然后把它移到 front/unhide 上。 (这段代码确实是为多个按钮设计的,所以有点复杂。)
procedure TForm1.CreateNavPanelButtons(Action: TAction);
procedure Swap;
var P: TPanel;
begin
P := BackPanel;
BackPanel := FrontPanel;
FrontPanel := P;
BackPanel.Visible := false;
FrontPanel.Visible := true;
end;
var P: TPanel;
B: TButton;
I: Integer;
begin
for I := BackPanel.ChildrenCount-1 downto 0 do
if BackPanel.Children[I] is TButton then
begin
B := BackPanel.Children[I] as TButton;
BackPanel.RemoveObject(B);
B.DisposeOf;
end;
BackPanel.AddObject(MakeButton(Action));
Swap;
end;
但这增加了复杂性,无论是在需要两个面板还是在表单上将它们彼此重叠等方面,这可以说比使用计时器更复杂。所以我可能只使用 Timer 解决方案。我post这只是一个替代方案。
使用 Delphi 10.2(在 Windows 10 "19H2" 下),我可以创建一个新的应用程序,在上面放置一个面板,以及一个包含两个项目的操作列表。这两个项目调用相同的例程,其目的是删除面板上的任何按钮,然后添加新按钮:
procedure TForm1.CreateNavPanelButtons(Action: TAction);
begin
NavPanel.RemoveObject(Btn);
Btn.DisposeOf; //problem line
Btn := MakeButton(Action);
NavPanel.AddObject(Btn);
end;
(我在这里简化为只使用一个按钮。)删除现有按钮,添加新按钮。如果我调用 DisposeOf(以释放按钮的内存),Window 对象变为无响应(无法调整大小、移动、关闭),直到我将注意力从应用程序移开。
我在下面包含了完整的代码:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
FMX.Controls.Presentation, System.Actions, FMX.ActnList;
type
TForm1 = class(TForm)
NavPanel: TPanel;
ActionList: TActionList;
acNextMenu: TAction;
acBackToMainMenu: TAction;
procedure FormCreate(Sender: TObject);
procedure acNextMenuExecute(Sender: TObject);
private
{ Private declarations }
public
Btn: TButton;
procedure CreateNavPanelButtons(Action: TAction);
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
function MakeButton(A: TAction): TButton;
begin
Result := TButton.Create(nil);
Result.Action := A;
Result.Text := (A as TAction).Text;
end;
procedure TForm1.acNextMenuExecute(Sender: TObject);
begin
CreateNavPanelButtons(acBackToMainMenu);
end;
procedure TForm1.CreateNavPanelButtons(Action: TAction);
begin
NavPanel.RemoveObject(Btn);
Btn.DisposeOf;
Btn := MakeButton(Action);
NavPanel.AddObject(Btn);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
CreateNavPanelButtons(acNextMenu);
end;
end.
表格如下:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 480
ClientWidth = 640
FormFactor.Width = 320
FormFactor.Height = 480
FormFactor.Devices = [Desktop]
OnCreate = FormCreate
DesignerMasterStyle = 0
object NavPanel: TPanel
Align = Top
Size.Width = 640.000000000000000000
Size.Height = 73.000000000000000000
Size.PlatformDefault = False
TabOrder = 0
end
object ActionList: TActionList
Left = 392
Top = 192
object acNextMenu: TAction
Category = 'Navigation'
Text = 'NextMenu'
OnExecute = acNextMenuExecute
end
object acBackToMainMenu: TAction
Category = 'Navigation'
Text = 'Back To &Main Menu'
OnExecute = FormCreate
end
end
end
您的代码存在问题,您正在删除当前正在执行操作的按钮 运行ning。当操作 returns 时,按钮不再存在,在 Windows 上它被 DisposeOf()
释放,在移动平台上它处于 "zombie" 状态。
解决方法是延迟删除按钮,直到操作结束。在标准 Windows 应用程序中,我会 post 给自己发送一条消息,以确保在我收到消息并可以调用 CreateNavPanelButtons()
之前操作已经结束。但我不确定这是否适用于所有其他平台。
以下应该适用于任何平台。
添加一个TTimer
、Enabled = False
、Interval = 1
。然后声明一个私有字段,格式为 Action: TAction
.
像这样更改任何更改 NavPanelButtons
的操作处理程序:
procedure TForm2.acNextMenuExecute(Sender: TObject);
begin
// CreateNavPanelButtons(acBackToMainMenu);
Action := acBackToMainMenu;
Timer1.Enabled := True;
end;
并添加 OnTimer
事件
procedure TForm2.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
if Action <> nil then
CreateNavPanelButtons(Action);
end;
避免TTimer
另一种不需要消息或计时器的解决方案是预先创建所有按钮,并且在程序 运行 期间根本不处理它们 运行。
它们可以分组到 TButtonList
列表中,这些列表将包含相关的按钮并同时显示。
当 TButtonList
需要显示时,NavPanel
中的旧按钮只需要通过 NavPanel.RemoveObject(B)
从面板中删除(不需要 B.DisposeOf
)在循环中。
最后,for b in ButtonList do NavPanel.AddObject(b)
将新的按钮列表添加到面板中。
这样做的缺点是内存使用量更大,以防万一。
汤姆答对了。我不喜欢定时器的用户,因为我不喜欢中断代码流,除非我不得不这样做,所以我设计了一个双面板系统:
TForm1 = class(TForm)
NavPanel1: TPanel;
NavPanel2: TPanel;
. . .
FrontPanel: TPanel;
BackPanel: TPanel;
我在释放那里的东西后把所有的按钮都放在了 BackPanel 上,然后把它移到 front/unhide 上。 (这段代码确实是为多个按钮设计的,所以有点复杂。)
procedure TForm1.CreateNavPanelButtons(Action: TAction);
procedure Swap;
var P: TPanel;
begin
P := BackPanel;
BackPanel := FrontPanel;
FrontPanel := P;
BackPanel.Visible := false;
FrontPanel.Visible := true;
end;
var P: TPanel;
B: TButton;
I: Integer;
begin
for I := BackPanel.ChildrenCount-1 downto 0 do
if BackPanel.Children[I] is TButton then
begin
B := BackPanel.Children[I] as TButton;
BackPanel.RemoveObject(B);
B.DisposeOf;
end;
BackPanel.AddObject(MakeButton(Action));
Swap;
end;
但这增加了复杂性,无论是在需要两个面板还是在表单上将它们彼此重叠等方面,这可以说比使用计时器更复杂。所以我可能只使用 Timer 解决方案。我post这只是一个替代方案。