Удаление кнопок из TPanel, Firemonkey Edition - PullRequest
0 голосов
/ 26 апреля 2020

Используя 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

Ответы [ 2 ]

1 голос
/ 28 апреля 2020

Проблема с вашим кодом в том, что вы удаляете кнопку, действие которой выполняется в данный момент. Когда действие возвращается, кнопка больше не существует, на Windows она освобождается на DisposeOf(), а на мобильных платформах она находится в состоянии "zomb ie".

Лечение заключается в отложить удаление кнопок до завершения действия. В стандартном приложении Windows я отправляю сообщение самому себе, чтобы убедиться, что действие завершено, прежде чем я получу сообщение и смогу вызвать 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 нужно будет удалить (не B.DisposeOf) с панели только с помощью NavPanel.RemoveObject(B) в oop.

Наконец, новый список кнопок будет добавлен на панель: for b in ButtonList do NavPanel.AddObject(b).

Недостатком этого является большее использование памяти, если это имеет значение.

0 голосов
/ 28 апреля 2020

У Тома правильный ответ. Мне не понравился пользователь таймера, потому что я не люблю прерывать поток кода без необходимости, поэтому я разработал двухпанельную систему:

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

Я положил все кнопки на задней панели после освобождения того, что там, а затем переместить его вперед / показать его. (Этот код действительно предназначен для более чем одной кнопки, поэтому он немного сложнее.)

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;

Но это увеличивает сложность с точки зрения необходимости использования двух панелей и размещения их друг над другом в форме, и т. Д. c., Что, возможно, более запутанно, чем использование таймера. Так что я могу просто использовать решение Timer. Я публикую это как альтернативу.

...