DDeberla
DDeberla

Reputation: 87

Delphi RIO 10.3.1 FMX TListView 'ItemClickEx' fires 2 for single click

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

Answers (2)

MartynA
MartynA

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

DDeberla
DDeberla

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

Related Questions