Voir le flux RSS

Blog de Serge Girard (aka SergioMaster)

[FMX] Sélection d'éléments dans un TListView

Noter ce billet
par , 08/10/2019 à 14h59 (99 Affichages)
Je vous invite à une nouvelle plongée dans l'univers des TListViews. Après l'utilisation des groupements, l'ajout de couleurs, la modification de la boite de recherche,
pour en savoir plus utilisez la balise listview du nuage de tag,
je vous propose quelques pistes permettant à l'utilisateur de sélectionner un plusieurs éléments dans une liste.

Pour débuter, j'écrirais que la sélection d'éléments au sein d'un TListView est simple si l'on réunit quelques conditions à savoir passer le TListView en mode édition et utiliser les apparences standards contenant une boîte à cocher :
  • ListItemShowCheck ;
  • ListItemRightDetailShowCheck ;
  • ImageListItemShowCheck ;
  • ImageListItemBottomDetailShowCheck ;
  • ImageListItemBottomDetailRightButtonShowCheck ;
  • ImageListItemRightButtonShowCheck ;
  • et, sous réserve, Custom.

Nom : proprietes.PNG
Affichages : 130
Taille : 17,0 Ko
Même l'apparence dynamique (DynamicAppearance) nous permettra d'ajouter un TGlyphButtonObjectAppearance, cependant cela s'avère un peu plus complexe et il ne faut pas oublier que ce dernier ne se verra qu'en mode édition.

Maintenant, quelques challenges, tout d'abord vous ne voulez peut-être pas que la case à cocher soit visible. C'est possible en modifiant la propriété Visible de l'objet mais, dans ce cas là comment notifier à l'utilisateur que l'élément est sélectionné ? Autre désir que vous pourriez avoir : ne pas passer en mode édition (diverses raisons à ce choix).
Comment alors indiquer à l'utilisateur que l'élément est sélectionné ? Et, plus compliqué, comment faire une sélection si la liste n'est pas en mode édition ?

J'avoue avoir été fouiller profondément dans les sources, songer à créer une nouvelle interface (au moment où cette question s'est posée je planchais sur une recherche fine dans la liste) avant de trouver une manière simple.

C'est sciemment que je n'ai pas mis l'apparence dynamique dans le même panier que les autres apparences que je qualifierai de "prédéterminées" car les méthodes pour atteindre les différents objets d'un élément de liste ne seront pas identique.

I. Les apparences prédéterminées
I.1 Comment sélectionner des éléments sans que la liste soit en mode édition ?
Ce qu'il faut savoir :
  • La liste des éléments sélectionnés se trouve dans un TList<Integer> accessible en lecture via ListView.Items.CheckedIndexes(True);
  • Il est possible de modifier le statut d'un élément en utilisant la procédure ListView.Items.SetChecked.

Tout d'abord, s'assurer que la propriété AllowSelection de la liste soit bien cochée sous peine de n'avoir aucune réaction à l'évènement OnItemClick.
Il n'y a alors plus qu'à coder dans ce dernier
Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
procedure TMainForm.ListViewSimpleItemClick(const Sender: TObject;
  const AItem: TListViewItem);
 
begin
if not ListeViewSimple.EditMode then  // s'assure que la liste n'est pas en mode édition 
  ListeViewSimple.Items.SetChecked(AItem.Index,not Aitem.Checked);
...
end;

I.2 Comment mettre en valeur les éléments de liste sélectionnés ?
En mode édition, aucune modification faite, c'est la case à cocher qui joue ce rôle, seulement si celle-ci est invisible il faut trouver une autre solution. De même si la liste n'est pas en mode édition, la case à cocher n'est pas visible (c'est dû au composant). La solution consiste à jouer sur la présentation des éléments visibles mais la difficulté est de savoir comment y accéder. En fait, c'est simple, une fois trouvé , chaque élément de liste contient des objets recensé dans une liste de type TListViewItemObjects (AItem.Objects), de là, la première idée qui pourrait venir serait de rechercher dans cette liste, via la fonction AItem.Objects.FindObjectT (on ne peut pas écrire que l'aide soit explicite ).
Pour accéder à l'objet 'text', on utiliserait ce type de code
Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
 
    AListItemText : TListItemText;
begin
AListItemText:=Item.Objects.FindObjectT<TListItemText>('Text');
pour les apparences prédéterminées cette fonction renvoie null
Pour obtenir les objets d'une apparence prédéterminée, on y accédera via des propriétés
  • Item.Objects.TextObject qui fournira un TListItemText;
  • Item.Objects.DetailObject qui fournira un TListItemText;
  • Item.Objects.GlyphButton qui fournira un TListItemGlyphButton;
  • Item.Objects.ImageObject qui fournira un TListItemImage;
  • Item.Objects.TextButton qui fournira un TListItemTextButton.

Il devient alors facile de modifier l'apparence des divers éléments.
Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
procedure TMainForm.ListeViewSimpleItemClick(const Sender: TObject;
  const AItem: TListViewItem);
var  AListItemText : TListItemText;
begin
if not ListeViewSimple.EditMode then
  ListeViewSimple.Items.SetChecked(AItem.Index,not Aitem.Checked);
  AListItemText:=AItem.Objects.TextObject;
  if AItem.Checked then begin
                         AListItemText.Font.Style:=[TFontStyle.fsBold];
  //                       AListItemText.TextColor:=TAlphaColorRec.Red;
                    end
                   else begin
                     AListItemText.Font.Style:=[];
  //                   AListItemText.TextColor:=TAlphaColorRec.black;
                   end;
end;
J'ai mis en commentaire le changement de couleur pour deux raisons : les couleurs de texte dépendent du style utilisé et, de surcroît, l'élément en cours utilise la couleur surexposée (hightlighted). L'obtention des couleurs systèmes débordant largement du cadre de ce billet, je ne m'étends pas aujourd'hui sur cette possibilité.

Nom : ListViewSimple.PNG
Affichages : 76
Taille : 13,0 Ko Nom : ListViewmodeedition.PNG
Affichages : 75
Taille : 9,1 Ko Nom : listeviewsimplenochkbox.PNG
Affichages : 75
Taille : 7,9 Ko

Vous remarquerez qu'en mode édition, le fond des éléments sélectionnés est très légèrement mis en valeur. Je n'ai malheureusement pas pu, à ce jour, modifier la couleur (en fait une certaine opacité qui s'applique sur la couleur de l'élément en cours).

I.3 Utiliser une image pour mettre en valeur les éléments de liste sélectionnés ?
Quelque chose de différent , pourquoi ne pas jouer avec les images ? Quatre apparences prédéterminées propose d'afficher une image et celle-ci peut être contenue dans un TImageList. La première opération consiste à lier la liste au conteneur d'images par la propriété ImageList, maintenant que le processus a été établi plus haut, la seule astuce consiste à savoir comment indiquer quel image utiliser, ce qui se fera via la propriété ImageIndex de l'élément.

Utiliser la propriété Offset de l'objet image pour repositionner l'image selon ses besoins.

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
 
procedure TMainForm.LstViewAvecImageItemClick(const Sender: TObject;
  const AItem: TListViewItem);
begin
if not LstViewAvecImage.EditMode then
  LstViewAvecImage.Items.SetChecked(AItem.Index,not Aitem.Checked);
 if AItem.Checked then AItem.ImageIndex:=0 else AItem.ImageIndex:=-1; // -1 pas d'image
end;

Nom : ListViewImage.PNG
Affichages : 83
Taille : 19,6 Ko

Le plus gros effort : choisir l'image qui "va bien"

I.4 Dernière astuce
Basculer entre le mode édition et le mode "normal" efface les éléments sélectionnés. Si vous voulez garder d'un mode à l'autre les sélections faites un petit bout de code s'impose

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
var CheckedList : TArray<Integer>;
    i : Integer;
begin
CheckedList:=ListeViewSimple.Items.CheckedIndexes(True); // mémorisation des éléments sélectionnés 
ListeViewSimple.EditMode:=not ListeViewSimple.EditMode;   // bascule du mode  
for i in CheckedList do ListeViewSimple.Items.SetChecked(i,true);  // copie des  éléments sélectionnés

I.5 Ce qui n'a pas l'air de vouloir fonctionner
Il aurait été intéressant que la sélection d'un élément ne soit faite que si l'utilisateur coche la case alors qu'actuellement un simple clic sur l'élément déclenche le changement d'état. Une piste me paraissait bonne : la propriété ClickOnSelect de la case à cocher. Cependant cela n'a pas l'air d'être pris en compte dans le composant. Même si j'ai pu rédiger un code presque opérationnel :
Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
 
procedure TForm1.ListView1ItemClickEx(const Sender: TObject;
  ItemIndex: Integer; const LocalClickPos: TPointF;
  const ItemObject: TListItemDrawable);
var Etat : Boolean;
begin
Etat:=ListView1.Items[ItemIndex].Checked;  // le changement a déjà été effectué  
if not ListView1.Items[ItemIndex].HasClickOnSelectItems  
    then ListView1.Items[ItemIndex].Checked:=not Etat;  // changer à nouveau l'état
if ItemObject.ClassNameIs('TListItemGlyphButton') then
    ListView1.Items[ItemIndex].Checked:=not TListItemGlyphButton(ItemObject).Checked;
end;
Cet essai est loin d'être une panacée : pour pouvoir modifier la case à cocher il faut tout d'abord bien prendre garde à sélectionner l'élément puis cliquer dans la case. Dans le cas contraire, un clic sur une des quelconques cases à cocher visibles modifie l'état de l'élément en cours Peut-être qu'une utilisation de LocalClickPos sera la sente à poursuivre.

II. L'apparence dynamique
II.1 Première différence : accéder aux objets de l'élément
L'apparence dynamique, arrivée plus tardivement, est en fait particulière, déjà le code concernant cette dernière se trouve dans une unité séparée de FMX.ListView.
le principe de sélection reste le même que pour les autres apparences
Code Pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
procedure TMainForm.LstViewDynItemClick(const Sender: TObject;
  const AItem: TListViewItem);
begin
if not LstViewDyn.EditMode then
  LstViewDyn.Items.SetChecked(AItem.Index,not Aitem.Checked);
UpdateItem(AItem); // mise en exergue
end;

Mes divers essais sur cette apparence m'ont laissé perplexe. Quelques-fois la case à cocher, visible en mode édition, fonctionnait et d'autres fois non ! J'ai longtemps soupconné une erreur de manipulation de ma part jusqu'à ce que je codifie la bascule du mode édition. Il semblerait que cela dépende du mode au moment de la création des liens avec le concepteur visuel. Je n'ai pas investigué plus. Comme je voulais continuer à mettre en exergue le texte j'ai pris également en compte ce problème.

Si nous voulons changer l'aspect d'un objet, cette fois, c'est bien via FindObjectT que l'on accédera à celui-ci.
Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
procedure TMainForm.UpdateItem(Item: TListViewItem);
var AListItemText : TListItemText;
     ACheckBox : TListItemGlyphButton;
begin
AListItemText:=Item.Objects.FindObjectT<TListItemText>('Text1');
if Assigned(AListItemText) then
     if Item.Checked then AListItemText.Font.Style:=[TFontStyle.fsBold]
                      else AListItemText.Font.Style:=[];
if Item.HasClickOnSelectItems then
 begin
   ACheckBox:=Item.Objects.FindObjectT<TListItemGlyphButton>('GlyphButton2');
   if Assigned(ACheckBox) AND (ACheckBox.ButtonType=TGlyphButtonType.Checkbox)
     then ACheckBox.Checked:=Item.Checked;
 end;
end;
Nom : Dynamique.PNG
Affichages : 79
Taille : 7,7 Ko Nom : DynamiqueEdit.PNG
Affichages : 89
Taille : 8,1 Ko
Chapitre I.4, je vous faisait part de la posssibilité de garder les sélections en cours malgré la bascule du mode édition. Sans rien ajouter de code supplémentaire pour le texte la mise en exergue est gardée. Malheureusement il n'en va pas de même pour la case à cocher.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
procedure TMainForm.Switch3Switch(Sender: TObject);
var CheckedList : TArray<Integer>;
    i : Integer;
    Item : TAppearanceListViewItem;
    ACheckBox : TListItemGlyphButton;
begin
CheckedList:=LstViewDyn.Items.CheckedIndexes(True);
LstViewDyn.EditMode:=Switch3.IsChecked;
for i in CheckedList do LstViewDyn.Items.SetChecked(i,true);
// mise en conformité  de la case à cocher (coché ou non)
if LstViewDyn.EditMode then
 begin
   LstViewDyn.BeginUpdate;
   for I:=0 to LstViewDyn.Items.Count-1 do
     begin
      Item:=LstViewDyn.Items[i];
      ACheckbox:=Item.Objects.FindObjectT<TListItemGlyphButton>('GlyphButton2');
      if Assigned(ACheckBox) AND (ACheckBox.ButtonType=TGlyphButtonType.Checkbox)
        then ACheckBox.Checked:=Item.Checked;
     end;
    LstViewDyn.EndUpdate;
 end;
end;

II.2 De la couleur
Les principes de mise en couleur d'une TListView vous les retrouverez dans ce tutoriel. Rappellez vous bien qu'il faut que l'image, qui couvre tout l'élément (propriété ScalingMode =Stretch), doit être le premier objet à être créé.

Nom : ConceptionDynamique.PNG
Affichages : 76
Taille : 13,4 Ko Nom : DynamiqueImage.PNG
Affichages : 76
Taille : 17,7 Ko


III. Liste groupée
J'aurais pu m'en tenir aux différents cas déjà présentés mais l'aventure était trop tentante surtout que cela m'a permis de tester l'apparence custom.
Je ne reviens pas sur comment établir, grâce aux LiveBindings, une liste avec des groupes, plusieurs de mes billets en font état ainsi qu'un tutoriel.

Note : pour garder une suite constante au niveau de mes images la source de données n'est pas triée ce qui aurait été certainement préférable

III.1 De l'apparence custom
Apparence que je n'ai découverte que récemment, elle est une sorte de passerelle entre les diverses apparences prédéfinies et l'apparence dynamique. Il n'est pas possible de lui ajouter d'objets mais tous les types d'objets, voir la liste du chapitre I.2, sont disponibles. Pour cette présentation je ne vais utiliser qu'un seul élément en plus du texte : le bouton.

Quelque soit l'apparence prédéterminée, il est possible de basculer en mode conception pour déplacer les objets, mais ce n'est pas aussi facile que pour l'apparence dynamique. En indiquant des tailles (hauteur,largeur) spécifiques on peut obtenir ce genre de design

Nom : GroupeCustom.PNG
Affichages : 80
Taille : 3,5 Ko

Je n'ai pas réussi remplir l'image autrement que par lien (Livebinding) sur la propriété ItemHeader.Bitmap (ce qui fera, peut-être, l'objet d'un billet spécifique sur cette apparence).
Je ne pense pas qu'il soit possible de changer l'ordre de création des dits objets et donc d'appliquer l'astuce de de l'apparence Dynamique pour la colorisation.
Une liste groupée ressemble à beaucoup à un ensemble de TExpanders, biffurcation intéressante s'il est possible de modifier les hauteurs des éléments non ?

III.2 Sélectionner/Désélectionner un groupe

Nom : Groupes.PNG
Affichages : 79
Taille : 7,7 Ko

L'objectif est de sélectionner, ou déselectionner, les éléments appartenant au groupe en cliquant sur le bouton. Pour la sélection des éléments c'est toujours le même principe qui est utilisé.
Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
procedure TMainForm.ListViewGroupeItemClick(const Sender: TObject;
  const AItem: TListViewItem);
var  AListItemText : TListItemText;
     AButton       : TListItemTextButton;
begin
case AItem.Purpose of
   TListItemPurpose.None : begin
   if not ListViewGroupe.EditMode then
    ListViewGroupe.Items.SetChecked(AItem.Index,not Aitem.Checked);
    AListItemText:=AItem.Objects.TextObject;
    if AItem.Checked
        then AListItemText.Font.Style:=[TFontStyle.fsBold]
       else AListItemText.Font.Style:=[];
   end;
 // else header / footer
 end;
end;
Le code sur le bouton de l'entête de groupe est un peu plus délicat. Comme il s'agit de manipuler plusieurs éléments il faudra forcer le dessin de la liste, de mon côté je préfère utiliser les procédures BeginUpdate et EndUpdate mais Invalidate (non testé) me semble aussi possible.
Petit piège, contrairement à l'évènement OnItemClick, le paramètre AItem n'est pas un TListItemView mais un TListItem, obtenir un TListViewItem pourrait, théoriquement se faire via AItem.View, mais cela ne m'a pas permis d'accéder plus facilement à la liste des éléments sélectionnés qu'en utilisant l'élément de liste Items[AItem.ItemIndex].

Code Delphi : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 
procedure TMainForm.ListViewGroupeButtonClick(const Sender: TObject;
  const AItem: TListItem; const AObject: TListItemSimpleControl);
var
  I: Integer;
  Status : Boolean;
  AListItemText : TListItemText;
  AListTextButton : TListItemTextButton;
begin
ListViewGroupe.BeginUpdate;
// change le statut de l'item
ListViewGroupe.Items[AItem.Index].Checked:=not ListViewGroupe.Items[AItem.Index].Checked;
Status:=ListViewGroupe.Items[AItem.Index].Checked;
// modifie le visuel du groupe
AListItemText:=ListViewGroupe.Items[AItem.Index].Objects.TextObject;
AListTextButton:=ListViewGroupe.Items[AItem.Index].Objects.TextButton;
if Status then begin
            AListTextButton.Text:='Désélection';
            AListItemText.Font.Style:=[TFontStyle.fsBold];
          end
          else begin
            AListTextButton.Text:='Sélection';
            AListItemText.Font.Style:=[];
          end;
// Changer le statut des éléments du groupe
for I := AItem.Index+1 to ListViewGroupe.Items.Count-1 do
  begin
    if  (ListViewGroupe.Items[i].Purpose=TListItemPurpose.Header)
     OR (ListViewGroupe.Items[i].Purpose=TListItemPurpose.Footer)
       then Break;
    ListViewGroupe.Items.SetChecked(i,Status);
    AListItemText:=ListViewGroupe.Items[i].Objects.TextObject;
    if Status then AListItemText.Font.Style:=[TFontStyle.fsBold]
              else AListItemText.Font.Style:=[];
  end;
// forcer le redraw
ListViewGroupe.EndUpdate;
end;

vous trouverez tous ces essais dans le zip en pièce jointe, pour des facilités de portage entre cible j'ai utilisé un TPrototypeBindSource mais les principes restent identiques pour toute source de données (table en mémoire, table de base de données, clientdataset etc..)
Miniatures attachées Fichiers attachés

Envoyer le billet « [FMX] Sélection d'éléments dans un TListView » dans le blog Viadeo Envoyer le billet « [FMX] Sélection d'éléments dans un TListView » dans le blog Twitter Envoyer le billet « [FMX] Sélection d'éléments dans un TListView » dans le blog Google Envoyer le billet « [FMX] Sélection d'éléments dans un TListView » dans le blog Facebook Envoyer le billet « [FMX] Sélection d'éléments dans un TListView » dans le blog Digg Envoyer le billet « [FMX] Sélection d'éléments dans un TListView » dans le blog Delicious Envoyer le billet « [FMX] Sélection d'éléments dans un TListView » dans le blog MySpace Envoyer le billet « [FMX] Sélection d'éléments dans un TListView » dans le blog Yahoo

Mis à jour 14/10/2019 à 10h55 par Malick

Tags: delphi, fmx, tlistview
Catégories
Delphi , FMX

Commentaires