Firemonkey TListView 删除列表项滑动崩溃中的最后一项

Firemonkey TListView Delete Last Item on List Item Swipe Crash

我在 android 9

上的 Rad Studio 10.3.2 测试中使用 dephi firemonkey

我想从 TListView 中删除最后一个项目。但是在我删除它之前我想先确认一下,然后删除。

为此我构建了下面的示例代码,它有 1 个 TListview、2 个 Speedbuttons、1 个矩形和 1 个标签。

矩形是可见的 false,所以当用户滑动 listviewitem 时它会显示删除按钮。在删除按钮中,我将取消删除并将问题所在的矩形放在可见位置,如果单击是,则删除该项目。问题是 listviewitem 删除按钮永远不会消失,当用户再次点击屏幕时应用程序崩溃。

下图说明操作

Before swipe

After swipe

after click delete

after click yes

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.ListView.Types, FMX.ListView.Appearances, FMX.ListView.Adapters.Base,
  FMX.ListView, FMX.StdCtrls, FMX.Controls.Presentation, FMX.Objects;

type
  TForm1 = class(TForm)
    ListView1: TListView;
    Rectangle1: TRectangle;
    SpeedButton1: TSpeedButton;
    SpeedButton2: TSpeedButton;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure ListView1DeletingItem(Sender: TObject; AIndex: Integer;
      var ACanDelete: Boolean);
    procedure SpeedButton1Click(Sender: TObject);
    procedure SpeedButton2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  ItemDelete  : Integer;

implementation

{$R *.fmx}

procedure TForm1.FormCreate(Sender: TObject);
var
  Item: TListViewItem;
begin
  Item := ListView1.Items.Add();
  Item.Text := 'Item 1';
  Item := ListView1.Items.Add();
  Item.Text := 'Item 2';
  Item := ListView1.Items.Add();
  Item.Text := 'Item 3';
  Item := ListView1.Items.Add();
  Item.Text := 'Item 4';
end;

procedure TForm1.ListView1DeletingItem(Sender: TObject; AIndex: Integer; var ACanDelete: Boolean);
begin
  ACanDelete := false;
  ItemDelete := AIndex;
  Rectangle1.Visible := true;
end;

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  ItemDelete := -1;
  Rectangle1.Visible := false;
end;

procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
  ListView1.Items.Delete(ItemDelete);
  ItemDelete := -1;
  Rectangle1.Visible := false;
end;

end.

你做错了。

ListView OnDeletingItem event 的目的是控制是否可以删除 ListView 项。这应该在该事件的执行时间内完成,最好通过在该事件内显示模式对话框进行确认。

但是在您的代码中,您 return ACanDelete 为假,因此通知 ListView 它无法删除该项目。稍后您尝试使用您自己的矩形中的快速按钮从您自己的代码中删除特定的 ListView 项目。问题是,当用户单击任何这些速度按钮时,ListView 的内容可能已经更改,因为一旦您退出 OnDeletinItem 事件,您的应用程序就会恢复正常执行。

所以我强烈建议不要使用带按钮的矩形,而是使用模态对话框来获得用户的确认,因为通过使用模态对话框,你可以确保在模态对话框未关闭之前,其余代码进程将停止,因此ListView 的内容在此之前无法更改。

ListView OnDeletingItem event 上的文档还提供了一个小的 hot 代码示例,用于在这种情况下显示此类模式对话框,因此您一定要检查一下。


编辑: 在花了很多时间研究这个问题后,我设法找到了它的原因,但不幸的是我不知道如何轻松解决它。

问题的第一部分是 Android 不支持真正的模态对话框。因此,您需要通过使用同步对话框或使用您尝试过的一些自定义方式,以同步方式向您的用户实施额外确认。

但现在我们到了问题的第二部分,即 Embarcadero 部分的 TListView 组件设计不佳。

当您使用滑动手势显示删除按钮时,您会看到删除按钮不是作为特定项目的一部分创建的,而是作为最终派生 TListView 的 TListViewBase class class 的一部分创建的从。此外,一个名为 FDeleteButtonIndex 的字段设置有执行滑动手势的项目的索引号。因为这个 Delete 按钮是在 TListViewBase class 中声明和创建的,所以无法直接访问它,因为它被标记为私有。

现在,当您单击该删除按钮时,将执行一个特殊的事件方法 DeleteButtonClicked,在该方法中,删除按钮将被销毁,并且 FDeleteButtonIndex 将设置为 -1。

但是当您从代码中删除 ListView 项时,“删除”按钮不会被破坏,FDeleteButtonIndex 字段也不会设置为 -1。这意味着如果您第二次单击该删除按钮,TListView 将去删除与之前具有相同索引的项目。如果您之前删除了最后一项,您现在将因尝试访问 ListView 之外的项目而遇到访问冲突。

所以恐怕我没有任何简单的解决方案。你可以试试:

  • TListView 创建您自己的自定义 class 作为制作所需方法的一种方式 public 但您必须制作 5 个自定义,因为 TListVievBase 有五个层次与 TListView 相比。
    • 您可以尝试使用一些 TRL hack 来访问 TListViewBase class
    • 的私有方法
    • 您可以禁用默认的 SwipeToDelete 功能并实现您自己的功能,您将使用它在 运行 时创建您自己的删除按钮,其方式是它是 ListItem 的一部分,因此会被销毁与 ListItem
    • 或者您可以要求 Embarcadero 修复 TListView,以便它始终检查您正在以编程方式删除的特定项目是否存在删除按钮,或者向 TListView 添加另一种方法允许我们随时删除删除按钮。

抱歉,我不能提供更好的帮助