Jump to content
sjordi

TListView multi selection?

Recommended Posts

Hi,

It seems that the MultiSelection property has disappeared from the TListView component. It was available in XE4 if I'm correct.

Any quick and smart way to allow the user to touch/click any TListView item and mark it as selected?

And then be able to access the list of the selected item? I'd like the list to draw each selected item's background in, say,  light blue!

Thanks for any light on the technics.

Steve

Share this post


Link to post

Hi Dave,

Yes I have seen this and it works, but I'm not sure I can escape the "Edit" mode.
I'll study this. I just want to achieve a click or touch on any item and set it as selected/not selected...
But I guess that it works the same way under the hood.

Steve

 

Share this post


Link to post

Ok after tinkering with it, it seems that there is no way to multi select except when in Edit mode.

Problem is I absolutely don't want it to be in Edit mode, and I don't want the check boxes to be shown...

Hard to believe that such a basic feature is not implemented in TListView.

I tried to work around this by implementing a CustomDraw  mechanism, but even that seems not to be available, rendering ListViews useless in my case...

 

Share this post


Link to post

Come to think of it, I did something like this back around the XE3 era and like you had to fight with it to make it work. Sadly, I may not have the code any more, but I'll take a look later just in case.

Share this post


Link to post

I found an example from Rahiche Raouf, an excellent MVP in Algeria. He has an example on how to colorize a list view by adding a TListItemTextButton

But unfortunately it seems that it doesn't work in Rio anymore. Modifying text, etc... works fine, but not the TintColor.

procedure TForm1.ListView1UpdateObjects(const Sender: TObject;
  const AItem: TListViewItem);
var
   textRect : TListItemTextButton ;
begin
   textRect := AItem.View.FindDrawable('Rectangle') as TListItemTextButton ;
   textRect.TintColor := TAlphaColorRec.Green ;
   textRect.Text := 'Hello' ;
end;

This doesn't seem to work anymore 😞 
I'll investigate further and post back once (because it will!) it works!!!

Share this post


Link to post

I wrote various tutorials (and preparing some others) and blog post about TListView but in french you can read here https://serge-girard.developpez.com/

There are more than one solution to your goal

1- using a list object (i.e the color), yes it's one  

2- using an interface to keep all items selected is another one you can use.

I don't work on this part, currently working on customizing searching in TListView,  but in my mind an interface should do the trick easily. Hum, seem a good subject if y have some times  

Share this post


Link to post

Thanks,
I'm going to check this. French is my mother tongue, so no problem.

Interfaces are a must go anyway in good practices.


Steve

Edited by sjordi

Share this post


Link to post

Honte à moi, en plus j'avais le document quelque part et ne l'ai pas vu!!! Argh!

Merci !

 

(Translated: shame on me, I had that document somewhere and didn't notice it! Argh!)

Share this post


Link to post
5 hours ago, Serge_G said:

1- using a list object (i.e the color), yes it's one

I think that may be how I did it. I was unable to locate my old code.

Share this post


Link to post

Using a list object (even an invisible one) is the easiest way but if you want to have a quick "SelectedItems.count" that's not the fastest way.

 

Sleeping on my second option, using interface, I think it's the best one. Reviewing one Marco Cantu's lesson "Advanced Interface : Using Interfaces to Implement an Adapter Pattern" on Embarcadero Academy my opinion seems to be confirmed. 

// scheme interface
ISelectedLstViewItems = Interface

['{A3A6DE5C-496B-4766-89FF-23F08EA5B329}']

function GetSelectedItems : TList<Integer>;

procedure SetSelectedItem(Value : TList<Integer>);

property SelectedItems : TList<Integer> read GetSelectedItems write SetSelectedItems;

end;

In my mind building an interface containing a SelectedItems as a TList<integer> and then overriding TListView as 

TListView = class(FMX.ListView.TListview, ISelectedLstViewItems)

in another unit should do the trick. 

The only thing I don't know is "is it possible to use TagObject for the "link" ?" (even if it is a contraint) . Think I will test this with my SearchListViewInterface (for info you can follow my steps here)

 

to sjordi :classic_biggrin: "A quoi cela sert que Ducros il se décarcasse !"

 

Edited by Serge_G
adding scheme code
  • Thanks 1

Share this post


Link to post

Mhh,

@Serge_G I implemented your code and it works fine except... on iOS. Nothing happens.
Then I downloaded your code from the website you mention, compiled. Same thing. Ok on macOS, Windows, but not iOS. 

Don't have my Android device here, so I can't say.

 

Any idea why damn iOS won't colorize?
Steve

Share this post


Link to post

Hum,  here you touch a point "I am not a pear 😣" = "I don't have any Apple device (jealous)"  so I could not check my code  on these systems, I had to trust developpez.net technical correctors and some beta testers.

None of them rise this behavior though !

Now, as far as I guess could be a problem with style, I ran in this problem when stylecollection have various entries.

On another hand, I use a Bitmap (and I read somewhere Apple is not really fan of windows things) it's perhaps there a response   

       

Share this post


Link to post

Ok, I won't throw a stone if you don't have Apple devices.
I tried on my Android system, same thing: Gray interface.
Now I made sure not to have any styles, and also tried the ControlType of the ListView to be set  to Styled or Platform. No change.
Smartphones are gray. macOS, Windows are colorful with the color I picked...
Very strange.

 

Edited by sjordi

Share this post


Link to post

Damned ! As far as I remember I test these codes with Berlin version but not for mobile devices.

I investigate, (check my old hard disk to remember my various tests) and found the problem but no solution yet.

I was right it as to do with bitmap !

But I cheat a little :classic_smile:. I don't know which source you use, hope it's the color demo one

Cheating :

I add a bitmap to my Tprotypebindsource .

I link this one to the image object of the list, doing that we don't need to create (and destroy) bitmaps.

Finally I change my code

procedure TForm1.ListView1UpdatingObjects(const Sender: TObject;
  const AItem: TListViewItem; var AHandled: Boolean);
var AListItemBitmap : TListItemImage;
    AListItemText : TListItemText;
    AColor : TAlphaColor;
begin
AListItemBitmap:=AItem.Objects.FindObjectT<TListItemImage>('Image2');
AListItemText:=AItem.Objects.FindObjectT<TListItemText>('Text1');
if Assigned(AListItemBitmap) then
 begin
 //    AListItemBitmap.Bitmap:=TBitmap.Create(40,40);
     try
       AColor:=StringToAlphaColor(AListItemText.Text)
     except // certaines couleurs sont inconnues! i.e. monneygreen, ltgrey
      AColor:=TAlphaColorRec.Null;
     end;
     AListItemBitmap.Bitmap.Clear(AColor);
 end;
end;

Don't forget to remove the onCloseQuery event 

 

And, as you can see, image attached, it works !

 

After this "ascertainment", I dug in the developpez.net forum and found this post i wrote. The goal is now to mix the two but how ? 

 

Screenshot_20190925-090001.png

Edited by Serge_G

Share this post


Link to post

Great,

I confirm that it now works on iOS... Probably on Android as well but my device is at home.
Good workaround. I use it to actually change the background for multi-selected items...
 

for i := 1 to 1000 do
    ShowMessage('Thanks a lot Serge !');

Share this post


Link to post

Even if this workaround works 👍, for me it's not satisfactory. I don't understand why this bitmap creation does not work on mobile devices !  

Share this post


Link to post

I knew I can find a better solution not involving bitmaps in the datasource .

Here is the new code

procedure TForm1.ListView1UpdatingObjects(const Sender: TObject;
  const AItem: TListViewItem; var AHandled: Boolean);
var AListItemBitmap : TListItemImage;
    AListItemText : TListItemText;
    AColor : TAlphaColor;
    i : Word;
begin
AListItemBitmap:=AItem.Objects.FindObjectT<TListItemImage>('Image2');
AListItemText:=AItem.Objects.FindObjectT<TListItemText>('Text1');
if Assigned(AListItemBitmap) then
 begin
     AListItemBitmap.OwnsBitmap:=True; // this is the trick
     AListItemBitmap.Bitmap:=TBitmap.Create(40,40);
     try
       AColor:=StringToAlphaColor(AListItemText.Text)
     except // certaines couleurs sont inconnues! i.e. monneygreen, ltgrey
      AColor:=TAlphaColorRec.Null;
     end;
     AListItemBitmap.Bitmap.Clear(Acolor); //:=ABitmap;
 end;
{$ENDIF}
end;

See line 12.

Icing on the cake freeing bitmaps created is not needed anymore !

 

Tested on Windows10 and Android, hope it works on OSX and iOS 

 

 

  • Like 2

Share this post


Link to post

I confirm that it also works on macOS and iOS
YEAH
You will be in my thanks in the about box of the app.

 

Share this post


Link to post
Quote

You will be in my thanks in the about box of the app

😤

Well, I follow the track with those selections in edit mode, and I am disappointed  !

2 black points with the checkbox

1- with the common appearance (ListItemChecked, and all same type appearance). It works but if you don't want the checkbox checked when the item is clicked  in theory  you have to use option ClickOnSelect of TGlyphObjectButtonObject but this option as no effect (note was working with XE4)

2- I try to do the same thing but with appearance I prefer, the TDynamicAppearance one. In this case the checkbox don't react

 

One question to @sjordi  which event did you use for your selection,  OnItemClick or OnItemClickEx ?      

  • Like 1

Share this post


Link to post

Ok I checked now. My problem is I want to skip the checkboxes. I just want to touch/click an item to select/unselect it.
Both OnItemClick or OnItemClickEx seem to be triggered, but I don't find a way to update/refresh the listview. My guess is I actually have to hook the click to the underlying database. This way, when data is changed it should update.
I'm not sure whether there is a way to force a refresh: for testing purpose only I was using the Item.Tag to set it on/off, but obviously it's not enough.

Share this post


Link to post
Quote

My problem is I want to skip the checkboxes.

Well you can skip or hide :classic_wink:, putting the listview in edit mode and hiding the checkbox works "as expected". I put commas in reference to my previous post.

Using a Dynamic appearance this is the best way because checkbox doesn't react (point 2, bug ?)

Quote

2- I try to do the same thing but with appearance I prefer, the TDynamicAppearance one. In this case the checkbox don't react

But color change :classic_huh: the only thing is to find how the color is changing and set-it

Quote

My guess is I actually have to hook the click to the underlying database... Tag to set it on/off, but obviously it's not enough.

Well, if you have a Boolean field to bind, yes (I am writing another tutorial about Listview and ListImage but in review/correction till now).

I checked for event like "OnChecked"  or similar but for now I don't found one reachable, I searched in Interface IListViewCheckProvider and  IListViewPresentation but without good result.

I am looking for the checkbox drawing in the source but it's a little hard to track !

 

The more I check for selection the more I think there is a bug there but, "scalded cat fears cold water", I don't want to report to Bug Tracker (2 last one I report was said to be yet reported and one not reproducible)  without any proof/solution 
 

I also work on an interface

 IListViewSelectedItems = interface
   ['{3D8B9910-A017-4A10-8260-D44E9F7129B7}']
   procedure StartSelection;
   procedure SetSelected(Value : TList<Integer>);
   function  GetSelected :  TList<Integer>;
   property  Selected :  TList<Integer> read GetSelected write SetSelected;
 end;
   TListView = class(FMX.ListView.TListView,IListViewSelectedItems)
   private
     FSelected : TList<Integer>;
   public
     constructor Create(AOwner : TObject); // <<<<<<<<<<<<
     destructor Destroy; override;
     procedure StartSelection;
     procedure SetSelected(Value : TList<Integer>);
     function  GetSelected :  TList<Integer>;
     property  Selected :  TList<Integer> read GetSelected write SetSelected;
   end;

{ TListView override}        
procedure TListView.StartSelection;
begin
if not Assigned(FSelected) then FSelected:=TList<Integer>.Create;        
end;

function TListView.GetSelected : TList<Integer>;
begin
result := FSelected;       
end;       

procedure TlistView.SetSelected(value : TList<Integer>);
begin
FSelected:=Value;       
end;       

Here I can surely add an OnSelect event,(optionally a ClearSelection, SelectAll procedure).

But I am still blocked

   1 - a way to initialize FSelected at creation time (without using StartSelection)

   2 -on painting selection (overriding  or duplicating IListViewCheckProvider ?)    

Share this post


Link to post

Fool I am with this unnecessary interface ! In fact, it does exists yet.

So here is my steps with a TListView / DynamicAppearance without passing it in Edit mode

There for write  the OnItemClickEvent (here my DynamicAppearance contains an TlistItemImage named 'Image'

here is dfm part

    object YourListView: TListView
      ItemAppearanceClassName = 'TDynamicAppearance'
      ItemEditAppearanceClassName = 'TDynamicAppearance'
      HeaderAppearanceClassName = 'TListHeaderObjects'
      FooterAppearanceClassName = 'TListHeaderObjects'
      Position.X = 32.000000000000000000
      Position.Y = 360.000000000000000000
      Size.Width = 225.000000000000000000
      Size.Height = 265.000000000000000000
      Size.PlatformDefault = False
      TabOrder = 5
      ItemAppearanceObjects.ItemObjects.ObjectsCollection = <
        item
          AppearanceObjectName = 'Image'
          AppearanceClassName = 'TImageObjectAppearance'
          Appearance.ScalingMode = Stretch
        end
        item
          AppearanceObjectName = 'Text1'
          AppearanceClassName = 'TTextObjectAppearance'
        end>
      ItemAppearanceObjects.ItemEditObjects.ObjectsCollection = <
        item
          AppearanceObjectName = 'Text1'
          AppearanceClassName = 'TTextObjectAppearance'
        end>
      OnItemClick = YourListViewItemClick
    end

 

and code part

var AListItemBitmap : TListItemImage;
    AListItemText : TListItemText;
    AColor : TAlphaColor;
begin
YourListView.Items.SetChecked(AItem.Index,not Aitem.Checked);
AListItemBitmap:=AItem.Objects.FindObjectT<TListItemImage>('Image');
if Assigned(AListItemBitmap) then
 begin
     AListItemBitmap.OwnsBitmap:=True; 
     AListItemBitmap.Bitmap:=TBitmap.Create(40,40);
     if AItem.Checked then AColor:=TAlphaColorRec.AliceBlue
                      else AColor:=TAlphaColorRec.Null;
     AListItemBitmap.Bitmap.Clear(Acolor);
 end; 

but you can change text font (afraid AListItemText.TextColor don't work)

AListItemText:=AItem.Objects.FindObjectT<TListItemText>('Text1');
if Assigned(AListItemText) then
     if AItem.Checked then AListItemText.Font.Style:=[TFontStyle.fsBold]
                      else AListItemText.Font.Style:=[];

 

 

Capture.PNG

Edited by Serge_G
attach image

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

×