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.
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
procedure TMainForm.ListViewSimpleItemClick(const Sender: TObject;
const AItem: TListViewItem);
beginifnot 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
procedure TMainForm.ListeViewSimpleItemClick(const Sender: TObject;
const AItem: TListViewItem);
var AListItemText : TListItemText;
beginifnot ListeViewSimple.EditMode then
ListeViewSimple.Items.SetChecked(AItem.Index,not Aitem.Checked);
AListItemText:=AItem.Objects.TextObject;
if AItem.Checked thenbegin
AListItemText.Font.Style:=[TFontStyle.fsBold];
// AListItemText.TextColor:=TAlphaColorRec.Red;endelsebegin
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é.
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.
procedure TMainForm.LstViewAvecImageItemClick(const Sender: TObject;
const AItem: TListViewItem);
beginifnot LstViewAvecImage.EditMode then
LstViewAvecImage.Items.SetChecked(AItem.Index,not Aitem.Checked);
if AItem.Checked then AItem.ImageIndex:=0else AItem.ImageIndex:=-1; // -1 pas d'imageend;
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
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 :
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é ifnot ListView1.Items[ItemIndex].HasClickOnSelectItems
then ListView1.Items[ItemIndex].Checked:=not Etat; // changer à nouveau l'étatif 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
procedure TMainForm.LstViewDynItemClick(const Sender: TObject;
const AItem: TListViewItem);
beginifnot LstViewDyn.EditMode then
LstViewDyn.Items.SetChecked(AItem.Index,not Aitem.Checked);
UpdateItem(AItem); // mise en exergueend;
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.
procedure TMainForm.UpdateItem(Item: TListViewItem);
var AListItemText : TListItemText;
ACheckBox : TListItemGlyphButton;
begin
AListItemText:=Item.Objects.FindObjectT<TListItemText>('Text1');
if Assigned(AListItemText)thenif Item.Checked then AListItemText.Font.Style:=[TFontStyle.fsBold]else AListItemText.Font.Style:=[];
if Item.HasClickOnSelectItems thenbegin
ACheckBox:=Item.Objects.FindObjectT<TListItemGlyphButton>('GlyphButton2');
if Assigned(ACheckBox)AND(ACheckBox.ButtonType=TGlyphButtonType.Checkbox)then ACheckBox.Checked:=Item.Checked;
end;
end;
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.
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éé.
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
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
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é.
procedure TMainForm.ListViewGroupeItemClick(const Sender: TObject;
const AItem: TListViewItem);
var AListItemText : TListItemText;
AButton : TListItemTextButton;
begincase AItem.Purpose of
TListItemPurpose.None : beginifnot 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 / footerend;
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 viaAItem.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].
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 thenbegin
AListTextButton.Text:='Désélection';
AListItemText.Font.Style:=[TFontStyle.fsBold];
endelsebegin
AListTextButton.Text:='Sélection';
AListItemText.Font.Style:=[];
end;
// Changer le statut des éléments du groupefor I := AItem.Index+1to ListViewGroupe.Items.Count-1dobeginif(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..)
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité,
merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.