Reputation: 87
I'm trying to implement a 'toggle' function when clicking on a tlistview item. But when testing I notice that the click event get's fired 2 times with a single click / tap with IDENTICAL parameters..
I'm testing this for now on windows.
Is this 'works as designed' ?
I only added a listview to an empty form and implemented the 'ItemClickEx' event.
I could not find a workaround way to my toggle..except implementing a timer that would keep track of the clicks and ignore a second click to soon...
( it seems the FMX framework also works with delayed events when looking at the stack trace )
I tested also the onitemclick event and this one DOES fire only once. .. so I could probably use it to implement simple workaround. But not nice, I need the 'ex' version as well as eventually, I need to add/delete items from my list which is recommended only from the 'ex' version, according to the documentation .
Regards Dirk
Upvotes: 1
Views: 1329
Reputation: 30715
Update Please see the Update section below which is based on the OP's observations in his own answer.
The minimal project below does not exhibit the behaviour your describe.
If I click any item in ListView1, the ItemClickedCount
variable only increments by one,
as confirmed by the display on the form's caption (if I double-click the form, ItemClickedCount
increments by 2, as expected).
To implement your toggle, simply toggling a boolean should suffice or you could simply
derive the toggle state from whether the ItemClickedCount
is odd or even.
So, I think the behaviour you describe must be coming from soe part of your code not mentioned in your q. Obviously, the way to identify the cause is to iteratively simplify your form and its code. Good luck!
procedure TForm1.BuildList;
var
LItem : TListViewItem;
ListItemText : TListItemText;
Index : Integer;
begin
ListView1.BeginUpdate;
try
ListView1.Items.Clear;
ListView1.ItemAppearanceObjects.ItemEditObjects.Text.TextVertAlign := TTextAlign.Leading;
for Index := 0 to 19 do begin
LItem := ListView1.Items.Add;
LItem.ButtonText := 'Hello';
LItem.Text := 'Row: ' + IntToStr(Index);
LItem.Height := 25;
ListItemText := TListItemText.Create(LItem);
ListItemText.PlaceOffset.X := 100;
ListItemText.PlaceOffset.Y := 25 * (Index - 1);
ListItemText.Name := 'Name' + IntToStr(Index);
end;
finally
ListView1.EndUpdate;
end;
end;
procedure TForm1.ListView1ItemClickEx(const Sender: TObject; ItemIndex: Integer;
const LocalClickPos: TPointF; const ItemObject: TListItemDrawable);
begin
Inc(ItemClickedCount);
Caption := IntToStr(ItemClickedCount);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
BuildList;
end;
Update Replying to your comment + answer, yes, I do see ListView1ItemClickEx
being called twice for each mouse click. I've
looked into why this happens, and it is seemingly deliberate though why it should be that way isn't obvious to me.
Looking at the source of FMX.ListView (I'm doing this in Seattle so your line numbers may vary),
ListView1ItemClickEx
is calledby line 2003 (Case Entry.Incident of ... TDelayedIncident.ClickEvent:)
in procedure TListViewBase.ProcessIncident(const Entry: TDelayedIncidentEntry);
Obviously, to be called twice, there must be two such Incidents
per click, so I then looked at how these Incidents get added to whatever list/queue is being processed. So I then looked at procedure TListViewBase.StartIncident(const Incident: TDelayedIncident; const Triggered: Boolean;
const TimeToWait: Single; const CustomData: NativeInt);
at line 1949.
Following each mouse click, this is called twice:
The first time, looking at the call stack, the call originates in procedure TListViewBase.SetNewItemIndex(const NewIndex: Integer)
at line 4083.
The second time, it is from inside procedure TListViewBase.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single)
.
It's not obvious to me how to avoid this by property settings of the TListView, but it might be. However, there is still a simple work-around that can be included in the ListView1ItemClickEx
handler:
procedure TForm1.ListView1ItemClickEx(const Sender: TObject; ItemIndex: Integer;
const LocalClickPos: TPointF; const ItemObject: TListItemDrawable);
var
LItem : TListViewITem ;
begin
Inc(ItemClickedCount);
if not Odd(ItemClickedCount) then
Exit;
Caption := IntToStr(ItemClickedCount);
LITem := ListView1.Items[ItemIndex];
LItem.Tag := LItem.Tag + 1 ;
LItem.Text := LItem.Text + ' ' + LItem.Tag.ToString + 'clicks';
end;
I'm wondering about wiring this de-duplication handling into an interposer class that you could include in your source unit. If I can think of a clean way, I'll maybe add it here later on.
Upvotes: 3
Reputation: 87
Thx for your input.
You are correct, if you only add a TListview and fill it with TListviewitems in code and leave item appearance default as ListItem, the event fires only once also in my setup.
However, as soon as I change the ItemAppearance to 'custom' and, additionally, only make the Glyphbutton visible, it fires 2 times.
It seems this is independant whether the actual itemobjects ( Text , Glyph , Accessory ) overlap or not ( so it's not that e.g. the glyphbutton click event is also passed once more into an itemclick event )
It's also not important where I click exactly, it always gets fired 2 times
Do you see the same behaviour ?
type
TForm1 = class(TForm)
Panel1: TPanel;
Button1: TButton;
ListView1: TListView;
procedure Button1Click(Sender: TObject);
procedure ListView1ItemClickEx(const Sender: TObject; ItemIndex: Integer;
const LocalClickPos: TPointF; const ItemObject: TListItemDrawable);
private
{ Private declarations }
public
{ Public declarations }
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i : integer ;
LItem : TListViewItem ;
lListView : TListView ;
begin
// Here we should create the overal listview structure
// The visual appearance of items need to be done in updateobjects
// Only this one is always (re)triggered !!
lListView := ListView1;
lListView.BeginUpdate;
try
for i := 0 to 10 - 1 do begin
LItem := lListView.Items.Insert(i);
LItem.Text := 'Test' + I.ToString;
end;
finally
lListView.EndUpdate;
end;
end;
procedure TForm1.ListView1ItemClickEx(const Sender: TObject; ItemIndex: Integer;
const LocalClickPos: TPointF; const ItemObject: TListItemDrawable);
var
LItem : TListViewITem ;
begin
LITem := ListView1.Items[ItemIndex];
LItem.Tag := LItem.Tag + 1 ;
LItem.Text := LItem.Text + ' ' + LItem.Tag.ToString + 'clicks';
end;
...
object ListView1: TListView
ItemAppearanceClassName = 'TCustomizeItemObjects'
ItemEditAppearanceClassName = 'TCustomizeItemObjects'
HeaderAppearanceClassName = 'TListHeaderObjects'
FooterAppearanceClassName = 'TListHeaderObjects'
Align = Client
Size.Width = 640.000000000000000000
Size.Height = 439.000000000000000000
Size.PlatformDefault = False
TabOrder = 1
ItemAppearanceObjects.ItemObjects.Text.Width = 100.000000000000000000
ItemAppearanceObjects.ItemObjects.Text.Height = 44.000000000000000000
ItemAppearanceObjects.ItemObjects.Text.PlaceOffset.X = 168.000000000000000000
ItemAppearanceObjects.ItemObjects.Detail.Width = 230.000000000000000000
ItemAppearanceObjects.ItemObjects.Detail.Height = 44.000000000000000000
ItemAppearanceObjects.ItemObjects.Detail.PlaceOffset.X = 230.000000000000000000
ItemAppearanceObjects.ItemObjects.Accessory.Visible = True
ItemAppearanceObjects.ItemObjects.GlyphButton.Width = 31.000000000000000000
ItemAppearanceObjects.ItemObjects.GlyphButton.Height = 30.000000000000000000
ItemAppearanceObjects.ItemObjects.GlyphButton.Visible = True
ItemAppearanceObjects.ItemObjects.GlyphButton.PlaceOffset.X = 24.000000000000000000
OnItemClickEx = ListView1ItemClickEx
end
regards Dirk
Upvotes: 0