Jump to content
corneliusdavid

TListView OnItemClick problems

Recommended Posts

I've got a very simple cross-platform app with a TListView on the main form and when you click on an item, it should fire an event which takes the user to the next tab and displays details about the item that was clicked. I want to use OnItemClick so that it updates the current record of a memory table it's hooked to via LiveBindings.

 

For the Windows platforms, this works perfectly as expected. On iOS, there is no OnItemClick event fired. On Mac, I can use the arrow key, then the space bar to activate it but not the mouse.

 

I tried this a couple of days ago with Delphi 10.4.2, found a reported issue that was supposedly fixed in 10.4.1 (I can't find the issue now), and just tried it again today in Delphi 11 but I still have the same problem.

 

Does TListView's click events behave differently on mobile devices? Is there a separate event I can use if I want to use the Accessory Button instead?  I've found lots of information to configure viewing data in a TListView but not much in responding to events. Any clues would be most appreciated.

Share this post


Link to post
7 hours ago, corneliusdavid said:

Any clues would be most appreciated.

I don't have an answer for this per se, but i have a workaround for completely different problem that might give you an insight or be a food for thought to solve this.

 

I love one particular feature in Listbox (VCL) which is the sliding feature when you hold the left mouse button down and move it over items, but it doesn't support or trigger any item selection event or changes until the mouse button up, so i use this to trigger/simulate item select in this case.

procedure TForm1.ListBox1Click(Sender: TObject);
begin
  //Memo1.Lines.Add('Clicked '+IntToStr(ListBox1.ItemIndex));
end;

var
  LastItem: Integer = -1;

procedure TForm1.ListBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
  CurrentItem: Integer;
  ListBox: TListBox absolute Sender;
begin
  CurrentItem := ListBox.ItemIndex;
  if (CurrentItem > -1) and (CurrentItem <> LastItem) then
  begin
    // Trigger/Simulate ItemSelect here for CurrentItem
    Memo1.Lines.Add('Selected ' + ListBox.Items[CurrentItem]);

    LastItem := CurrentItem;
  end;
end;

I don't know about ListView behaviour on iOS if it will handle mouse movement in way allowing such usage, but i think you got the idea, also not a fan of LiveBindings but might be easier to use a similar approach then trigger an event accordingly.

Share this post


Link to post

Thanks for this suggestion. There is an ItemClickEx event I can hook into that does work the same on all platforms and gives me the ItemIndex to the list view items.  I guess I need to figure out how to get at the properties of the item. It's not a simple Text field but a variable list of objects and object types. I was hoping to be able to use something simpler.

Share this post


Link to post
On 9/10/2021 at 11:22 PM, corneliusdavid said:

 

For the Windows platforms, this works perfectly as expected. On iOS, there is no OnItemClick event fired. On Mac, I can use the arrow key, then the space bar to activate it but not the mouse. 

I don't have any Apple devices but on Androïd OnItemClick works fine.

 

for IOS : what about onTap or Gesture management

  • Like 1

Share this post


Link to post

I tried OnTap but it doesn't indicate which item was clicked.

 

I was playing around and enabled a property that got OnItemClick to work on Mac/iOS!  Tomorrow, I'll have to go back carefully and hunt down which property did the trick.

Share this post


Link to post
29 minutes ago, corneliusdavid said:

I tried OnTap but it doesn't indicate which item was clicked.

I rely on ItemClickEx only, for all purposes, this works best IMHO.

Share this post


Link to post

Here what I use.:

 

by the way.. I don't know, if it works on iOS. Android was fine for me.

 

unit ListViewTap;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FMX.Controls.Presentation, FMX.StdCtrls, FMX.ListView.Types,
  FMX.ListView.Appearances, FMX.ListView.Adapters.Base, FMX.ListView,
  FMX.Objects, FMX.TabControl, FireDAC.Stan.Intf, FireDAC.Stan.Option,
  FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def,
  FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.IB,
  FireDAC.Phys.IBDef, FireDAC.FMXUI.Wait, Data.DB, FireDAC.Comp.Client;

type
  TForm1 = class(TForm)
    ListView01: TListView;
    lbError: TLabel;
    sbtExit01 : TSpeedButton;   
    sbtShowAll: TSpeedButton;  

    procedure FormCreate(Sender: TObject);

    procedure FormDestroy(Sender: TObject);


  private
    { Private declarations }
  public

    procedure MainSetup;

    {$IFDEF ANDROID}

    procedure SetupGestures_ListView01;
    procedure Gestures_ListView01(Sender: TObject; const EventInfo: TGestureEventInfo; var Handled: Boolean);

    {$ELSE}
      {$IFDEF MSWINDOWS}

      procedure ListView01Click(Sender: TObject);  

      {$ENDIF}
    {$ENDIF}

  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}
{$R *.LgXhdpiPh.fmx ANDROID}
{$R *.Surface.fmx MSWINDOWS}

//uses SharedGlobals, CRUDL_Work20;


{$region 'FormCreate & FormDestroy'}

procedure TForm1.FormCreate(Sender: TObject);
begin

  MainSetup;

{$IFDEF ANDROID}
  lbError.Text := 'Android';

  ListView01.OnGesture := Self.Gestures_ListView01;
{$ELSE}
  {$IFDEF MSWINDOWS}
    lbError.Text := 'MSWindows';

    ListView01.OnClick :=  Self.ListView01Click;
  {$ENDIF}
{$ENDIF}

end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  // do something
end;

procedure TForm1.sbtExit01Click(Sender: TObject);
begin
{$IFDEF ANDROID}
  Application.Terminate;
{$ENDIF}

{$IFDEF MSWINDOWS}
//  mtWarning       -> Warns the user about a potential issue.
//  mtError         -> Informs the user of an error that occurred.
//  mtInformation   -> Provides information to the user.
//  mtConfirmation  -> Ask the user for confirmation.
//	mtCustom        -> None of the above.

  if  MessageDlg('Realy exit?',
        TMsgDlgType.mtConfirmation,
        [TMsgDlgBtn.mbYes,TMsgDlgBtn.mbNo],
        0) = mrYes
  then
  begin
    Application.Terminate;
  end;
{$ENDIF}
end;

procedure TForm1.MainSetup;
begin
  {$IFDEF ANDROID}
  sbtShowAll.TintColor    := TAlphaColors.Aqua;
  {$ENDIF}
  {$IFDEF MSWINDOWS}
  sbtShowAll.IsPressed    := Boolean(1);
  {$ENDIF}
end;


{$region 'Android_Setup'}
{$IFDEF ANDROID}

procedure TForm1.SetupGestures_ListView01;
begin
  ListView01.Touch.InteractiveGestures :=
    [TInteractiveGesture.DoubleTap,  //Similar to a double-click. One of the basic survival rules in ZombieLand.
    //TInteractiveGesture.TwoFingerTap, // The "Two Finger Tap" gesture; requires two fingers.
    //TInteractiveGesture.PressAndTap, // The "Press And Tap" gesture; requires two fingers, one to hold pressed and one to tap.
    TInteractiveGesture.LongTap]; // The "Long Tap" gesture (also known as "Tap and hold", "Long Press" or "Press"); requires just one finger. Elicits a command such as Copy (for a picture), text editing commands or the TMagnifierGlass (for TMemo).
end;

procedure TForm1.Gesture_ListView01(Sender: TObject;
  const EventInfo: TGestureEventInfo; var Handled: Boolean);
begin
  if ListView01.Selected <> nil then
  begin
    case EventInfo.GestureID of
      igiLongTap :
      begin
        lbError.Text := ('Long Tap: ' + ListView01.Selected.Index.ToString);
        //do something
      end;
      igiDoubleTap:
      begin
        lbError.Text := ('Double Tap: ' + ListView01.Selected.Index.ToString);
        //do something
      end;
    end;
  end;
end;

{$ENDIF}
{$endregion}

{$region 'MSWindows_setup'}
{$IFDEF MSWINDOWS}

procedure TForm1.ListView01Click(Sender: TObject);
begin
  lbError.Text := 'Click';
end;


{$ENDIF}
{$endregion}

end.

 

Edited by skyzoframe[hun]

Share this post


Link to post
51 minutes ago, skyzoframe[hun] said:

 I don't know, if it works on iOS. Android was fine for me.

In ancient past versions I had some issues from time to time, on various platforms, not sure what the root cause of them was.

Anyway, I only can say that since I moved completely to ItemClickEx I had no more issues.

Maybe everything is OK now, in the last Delphi versions.

Edited by Rollo62

Share this post


Link to post

I checked the difference in my form and no properties changed from what works now and what didn't the other day. I'm wondering if simply changing a property, compiling, then changing it back activated something in the compilation that should've been enabled in the first place.  I've seen this once before in a different component (I don't remember which).

 

I still want to learn more about the sub-properties of the TListItemDrawable item returned in the OnItemClickEx event but for now, ItemClick is working--and simpler to use.

Share this post


Link to post

Simpler, yes, but the "Ex" receives also more information

image.thumb.png.5b444df14d176ad2a6e92d5893f8aab6.png

 

image.thumb.png.b6abe6e348ebc89df4afe317efc051c1.png

 

Especially the ItemObject is quite handy, to select the right sub-component in the TListItem, which you are interested in.

Share this post


Link to post

I think everything goes wrong, when you want to use "ListView1.SearchVisible :=True" properities.

ListView indexes are then unusable.

 

If the ListView Index must be the same as the database index, then I use the tag properties.

item  :=  ListView1.Items.Add;

item.Index := YourDatabaseIndex; //  (After the search, it will be changed!)
item.Tag := YourDatabaseIndex; //integer
// or  item.Objects.FindObjectT<TListItemText>('INDEX').Text := YourDatabaseIndex.ToString;
  
  
  
//and if you search, and wanted the selected item database indexes
  
procedure TForm2.ListView1ItemClickEx(const Sender: TObject; ItemIndex: Integer;
  const LocalClickPos: TPointF; const ItemObject: TListItemDrawable);
begin
  Label1.text := Itemindex.ToString; (After the search, it will be changed!)
  Label2.Text := Listview1.Selected.Tag.ToString;
end;

image.thumb.png.02e88cc025399aa33a930c50b2824864.png

 

 

Share this post


Link to post

Why, it looks OK to me.

The index shows the entries from 0 ... nn, the Tag determines what item you have.

If you filter, then the index might change, I think that is correct.

Share this post


Link to post
4 hours ago, Rollo62 said:

Simpler, yes, but the "Ex" receives also more information

True, but digging through the sub-properties to get what I want is so much more work than simply grabbing the value from the memory table to which it's attached. There might be times when this is necessary but I'd much rather use LiveBindings get the original value from the table.

 

By the way, the working project is on GitHub under the name, AppPaths.

  • Thanks 1

Share this post


Link to post

I think you could bind the Tag property too, with LiveBindings.

For me the whole LB thing is also still very much unclear, whats it's BestPractices are, to get best results out of it.

Until now: "Handle with care" :classic_biggrin:

Edited by Rollo62

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×