【问题标题】:Deleting Buttons from TPanel, Firemonkey Edition从 TPanel 中删除按钮,Firemonkey 版
【发布时间】:2020-04-26 00:30:47
【问题描述】:

使用 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

【问题讨论】:

  • 请显示与此相关的所有代码,尤其是声明。我不知道 NvPanelButtons、NavPanel、MakeButton 是什么或做什么......
  • 包括那些并简化了 MakeButton。
  • 我可以使用 RemoveObject 但内存仍然被占用。我正在尝试处理内存,但我采取的任何步骤都会导致这种行为。
  • 请提供重现问题的minimal reproducible example。您已经完全断章取义地发布了两个程序。 minimal reproducible example 将包括完整的源(包括 .fmx 文件的文本内容)和源 .pas 文件。它应该可以直接复制并粘贴到 IDE 中并运行以重现您描述的问题。
  • 好的。这就是全部内容。

标签: delphi firemonkey


【解决方案1】:

您的代码的问题是您正在删除当前正在运行的操作的按钮。当操作返回时,按钮不再存在,在 Windows 上它被DisposeOf() 释放,而在移动平台上它处于“僵尸”状态。

解决方法是延迟删除按钮,直到操作结束。在标准的 Windows 应用程序中,我会向自己发布一条消息,以确保在我收到消息之前操作已经结束,并且可以致电 CreateNavPanelButtons()。但我不确定这是否适用于所有其他平台。

以下内容应该适用于任何平台。

添加TTimerEnabled = FalseInterval = 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) 会将新的按钮列表添加到面板中。

这样做的缺点是内存使用量更大,以防万一。

【讨论】:

  • 正如您所指出的,TActions 是关键。我最终采用了一种稍微不同的方法,如下所示,但我可能会使用你的方法,因为(尽管使用了计时器)它实际上更简单。
【解决方案2】:

汤姆有正确的答案。我不喜欢计时器的用户,因为我不喜欢中断代码流,除非我必须这样做,所以我设计了一个两面板系统:

  TForm1 = class(TForm)
    NavPanel1: TPanel;
    NavPanel2: TPanel;
. . .
    FrontPanel: TPanel;
    BackPanel: TPanel;

释放所有按钮后,我将所有按钮放在 BackPanel 上,然后将其移到前面/取消隐藏它。 (这段代码真的是为多个按钮设计的,所以有点复杂。)

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 解决方案。我将其发布为替代方案。

【讨论】:

    猜你喜欢
    • 2015-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-03
    • 1970-01-01
    • 2012-07-14
    • 2020-02-03
    相关资源
    最近更新 更多