I am using dephi firemonkey on Rad Studio 10.3.2 testing on android 9
I Want to Delete the last Item From a TListView. But Before I delete it I Want to ask a confirmation, then delete.
for that I built the sample code below, it has 1 TListview, 2 Speedbuttons, 1 rectangle and 1 label.
the rectangle is visible false, so when the user swipe the listviewitem it will show the delete button. in the delete button i will cancel delete and put the rectangle visible whith the question, if click yes then delete the item. the problem is that the listviewitem delete button never go away and when the user clicks the screen again the app crash.
the following images illustrate the operation
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.
You are doing this wrong.
The purpose of ListView OnDeletingItem event is to controll whether the deletion of ListView item can occur or not. This should be done within the execution time of that event preferably by showing a modal dialog for confirmation within that event.
But in your code you return ACanDelete
as false and therefore notifying ListView that it can't delete that item. Later on you try to do deletion of specific ListView item from your own code with a speed button in that rectangle of yours. The problem is that by the time users clicks on any of those speed buttons the contents of ListView might have already changed since as soon as you exited OnDeletinItem event your application resumed with normal execution.
So I strongly recommend that instead of using your rectangle with buttons you use modal dialog for getting confirmation from your user since by using modal dialog you ensure that until modal dialog isn't closed rest of code progression is halted and thus contents of ListView can't change till then.
Documentation on ListView OnDeletingItem event also provides a small code example of hot to show such modal dialog in such scenario so you should definitely check it out.
EDIT: After spending many hours studying this problem I managed to track the cause of it but unfortunately I don't know how to solve it easily.
First part of the problem is that Android does not support true modal dialogs. So you need to implement additional confirmation to your user synchronously either by using synchronous dialog or by using some custom way as you tried.
But now here we arrive to the second part of the problem which is poor design of TListView component on Embarcadero part.
You see when you use swipe gesture to show the delete button that delete button is not created as part of specific item but as part of the TListViewBase class class from which TListView is eventually derived from. Also a filed called FDeleteButtonIndex
is set with the index number of a item on which the swipe gesture was performed. Because this Delete button is declared and created so deeply within the TListViewBase class it is not possible to access it directly as it is marked as private.
Now when you click on that Delete button a special event method DeleteButtonClicked
is executed and in this method the Delete button is destroyed and FDeleteButtonIndex
is set to -1.
But when you delete ListView item from code Delete button does not get destroyed nor does the FDeleteButtonIndex
field gets set to -1. Meaning that if you click on that Delete button the second time TListView will go and delete item with the same index as the one before. And if you previously deleted last item you will now get access violation for trying to access the item that is out of bods of your ListView.
So I'm afraid that I don't have any easy solution for you. You could try:
TListView
as a way to make needed methods public but you will have to make 5 custom since TListVievBase
is five levels deep in comparison to TListView
.
TListViewBase
classSwipeToDelete
functionality and implement your own with which you will create your own Delete button at run-time in a way that it is part of the ListItem and therefore gets destroyed with ListItemTListView
so that it either always check to see if Delete button is present for that specific item you are deleting programatically or add another method to TListView
that would allow us to remove that Delete button whenever we want.Sorry I can't be of better help