Personnaliser un TListView

Mettre des pieds de groupes

Serge Girard

Dans les applications pour mobiles, on voit souvent apparaître des listes, plus ou moins personnalisées, remplaçant généralement les grilles des programmes de bureau. On retrouve aussi souvent des démonstrations vantant la facilité d'utilisation du composant TListView associé à une source de données via les LiveBindings.

Il est vrai que pour une liste simple, c'est carrément du zéro code et il n'est guère plus complexe de faire apparaître des entêtes de groupes pour une liste triée.

Par contre, la frustration peut prendre à la gorge dès qu'il s'agit de déborder un peu des exemples proposés.

L'objectif de ce tutoriel est de vous proposer une méthode, peut-être pas très académique, pour pouvoir ajouter à une liste d'éléments des pieds de groupes.

Commentez

- Titre : Personnaliser un TListView

- Auteur : Serge Girard

- Parution : 5 mai 2018

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2018 Serge Girard. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Voir également :

- Mettre de la couleur dans un TListView

Sommaire

- I. Construction de l'interface

- II. Liaison entre la liste et les données

- II-A. Avec le concepteur visuel de liaisons

- II-B. Sans le concepteur visuel de liaisons

- III. Ajouter des pieds de groupes

- III-A. Logique

- III-B. Les événements de TLinkListControlToField

- III-C. Programmation

- III-C-1. Pistes à explorer, questions sans réponses

- IV. Application

- IV-A. Préparer les données

- IV-B. Préparer la liste

- IV-C. Remplissage de la liste

- IV-C-1. Avec le concepteur de liaisons visuel

- IV-C-2. Sans le concepteur visuel

- IV-C-3. Codification

- IV-D. Finalisation

- V. Mettre à bas le jeu des chaises musicales entre entête et pied de groupe

- VI. Conclusion

I - Construction de l'interface

Pour cette démonstration, j'ai besoin d'un TListView et d'une source de données quelconque pour remplir celle-ci. Dans une nouvelle application multipériphérique, je pose donc un TListView qui prendra tout l'espace client (align=alclient).

Pour les données, j'utilise un TPrototypeBindSource qui m'évitera d'avoir à utiliser des composants d'accès aux bases de données et surtout de créer une table et de fournir un jeu d'essai. Le TPrototypeBindSource générera des données aléatoires en fonction du type demandé ; il faut juste faire attention à ce que la liste de données soit ordonnée.

(cliquez ici pour aggrandir l'image)

J'ajoute ensuite deux colonnes à mon TPrototypeBindSource en utilisant son éditeur de champs que l'on obtient soit en double-cliquant sur le composant, soit en utilisant son menu contextuel.

(cliquez ici pour aggrandir l'image)

Notez bien que la propriété Options.optShuffle est à False. C'est ce qui permettra d'obtenir une liste de noms de couleurs en ordre alphabétique.

La deuxième colonne sera un entier. C'est cette valeur que je cumulerai par la suite pour chaque groupe.

(cliquez ici pour aggrandir l'image)

J'améliore alors l'affichage au sein de ma liste en modifiant tout d'abord l'apparence de mes éléments en adaptant la propriété ItemApperance.ItemApperance du TListView en DynamicAppearance.

(cliquez ici pour aggrandir l'image)

La modification en apparence dynamique me permet ensuite de basculer la liste en mode conception.

Pour basculer en mode conception, deux possibilités : soit utiliser le menu contextuel du composant TListView, soit utiliser le menu situé sous la liste des propriétés (voir image ci-dessous).

(cliquez ici pour aggrandir l'image)

J'obtiens ainsi un espace de conception sur mon composant que je vais pouvoir arranger selon mes besoins.

(cliquez ici pour aggrandir l'image)

Tout d'abord, je réduis la hauteur de mon texte principal qui contiendra par la suite le nom de la couleur.

(cliquez ici pour aggrandir l'image)

Puis je vais ajouter une seconde zone pour l'entier associé.

(cliquez ici pour aggrandir l'image)

J'ajoute donc un TTextObjectAppearance supplémentaire.

(cliquez ici pour aggrandir l'image)

Il me faudra également retravailler la taille et la position de cette zone.

Cette étape est assez délicate. Ce concepteur est certainement une des fonctionnalités à améliorer.

À la fin de la conception, après quelques améliorations des tailles et des couleurs des éléments, j'obtiens l'affichage ci-dessous.

(cliquez ici pour aggrandir l'image)

Il est temps à présent de rebasculer la liste en mode normal et de passer à la partie liaison de celle-ci avec les données.

II - Liaison entre la liste et les données

Une fois de plus, les possesseurs d'une version professionnelle ou plus ont l'avantage du concepteur visuel de liaisons ! Je rassure les possesseurs de versions inférieures : si je commence par le concepteur visuel, je décrirai ensuite comment faire sans.

II-A - Avec le concepteur visuel de liaisons

Je dois en premier lieu lier la source de données, représentée par l'astérisque du PrototypeBindSource1 à la liste de façon synchrone (Synch).

(cliquez ici pour aggrandir l'image)

(cliquez ici pour aggrandir l'image)

Puis je lie mes deux colonnes. La « magie » opère déjà : ma liste est remplie, sans avoir écrit une seule ligne de commande !

Je vais d'abord me concentrer sur les entêtes. À ce stade, la première idée serait de continuer avec le concepteur de liaisons et de lier NomCouleurs aux deux propriétés citées afin d'obtenir le schéma suivant. Cependant, il me faudra par la suite aller modifier ces deux liens.

(cliquez ici pour aggrandir l'image)

À mon avis, il est moins fastidieux de modifier directement les propriétés.

(cliquez ici pour aggrandir l'image)

Une fois de plus la « magie » opère : aucune ligne de code et pourtant ma liste est bien groupée !

Aucune ligne de code, c'est vite écrit ! Si effectivement il n'y a toujours aucune ligne de code dans le source du programme, j'ai quand même utilisé une méthode du moteur des LiveBindings (SubString), à la fois pour indiquer comment grouper (propriété FillBreakCustomFormat) et comment afficher le texte de l'entête de groupe (propriété FillHeaderCustomFormat).

II-B - Sans le concepteur visuel de liaisons

Les images écran qui vont suivre ont été réalisées avec une version Starter de Tokyo 10.2.0.

Première étape indispensable, il faut ajouter un composant BindingsList à la forme principale.

(cliquez ici pour aggrandir l'image)

Un double-clic sur ce nouveau composant permet d'ouvrir la liste des liens qui nous permettra d'en ajouter (plus rapidement, il est possible d'utiliser le menu contextuel du composant et utiliser l'option du menu « Nouveau composant LiveBindings »). Comme type de lien à ajouter, je sélectionne TLinkListControlToField.

(cliquez ici pour aggrandir l'image)

Troisième étape, je remplis les propriétés principales du lien, à savoir :

- la propriété Control ;

- la propriété DataSource.

(cliquez ici pour aggrandir l'image)

Puis je clique sur la propriété FillExpressions pour faire la liaison entre les données et les différents libellés de mes éléments de liste.

(cliquez ici pour aggrandir l'image)

Reste ensuite à procéder de la même manière qu'au chapitre précédent pour gérer les entêtes et j'obtiens le même résultat qu'avec le concepteur visuel de liaisons.

La liste ne se remplit pas, ou, pour reprendre ma formule, la « magie » n'opère pas ! Si c'est le cas, il suffit de rafraîchir l'ensemble des données, c'est-à-dire : basculer la propriété AutoActivate du PrototypeBindSource1 à False puis la remettre à True.

III - Ajouter des pieds de groupes

J'ai maintenant une liste groupée de mes données, ce qui amène à l'étape suivante. Le concepteur visuel de liaisons montre qu'il est possible de lier un ItemHeaderText et un ItemHeaderBreak, mais ne dit rien au sujet des Footers. Il est vrai que pour ceux qui n'ont que la version starter, n'ayant pas de visuel, la question est simplement : comment remplir mon pied de groupe ?

La logique voudrait pourtant que, comme pour les entêtes d'éléments, il y ait moyen de faire une liaison, mais ces propriétés n'apparaissent pas !

Faut-il pour autant abandonner les LiveBindings et coder dans le programme le remplissage de la liste ?

III-A - Logique

Tout d'abord, un peu de logique pour expliquer comment procéder pour remplir une liste.

(cliquez ici pour aggrandir l'image)

III-B - Les événements de TLinkListControlToField

Après la logique, il faut procéder à un état des lieux : quels sont les moyens dont je dispose pour gérer le remplissage de la liste via la liaison ? C'est ici qu'entrent en scène les événements de la liaison. Toutefois, l'aide sur ces derniers n'est pas très explicite. Pour tous ces événements, il n'existe qu'une seule phrase que je cite :

« Événement se produisant quand les expressions de liaison de ce wrapper délégué ont été activées. »

J'ai tenté, dans le tableau qui suit, de mettre en relation les événements de ce lien avec ceux d'un ensemble de données :

OnActivated

AfterOpen

OnActivating

BeforeOpen

OnAssignedValue

?

OnAssigningValue

AfterGetRecord

OnEvalError

OnError (se passe de commentaire)

OnFilledList

OnClose ou plus exactement quand la fin du fichier est atteinte

OnFilledListItem

AfterScroll ou AfterRowRequest ?

OnFillingList

?

OnFillingListItem

BeforeScroll ou AfterRowRequest ?

Ce tableau n'est qu'une tentative personnelle de compréhension du mécanisme de remplissage de liste.

III-C - Programmation

Pour ce qui est de la détection du remplissage d'un élément de la liste, l'événement évident est OnFilledListItem.

Restait à savoir :

1. Comment accéder aux valeurs ?

2. Comment savoir de quel type d'élément il s'agit ?

À ces deux questions, l'événement propose un paramètre AEditor de type interface d'un IbindListEditorItem. Il est donc possible d'obtenir un TListViewItem en transtypant l'objet de cet « éditeur ».

procedure TForm13.LinkListControlToField1FilledListItem(Sender: TObject;
const AEditor: IBindListEditorItem);
var AnItem : TListViewItem;
begin
AnItem:=AEditor.CurrentObject as TListViewItem;

Obtenir le type d'élément ajouté est alors possible en testant l'objectif de l'élément avec la propriété Purpose.

Trois valeurs sont possibles :

- None pour un élément normal ;

- Header pour un entête de groupe ;

- Footer pour un pied de groupe.

Je peux donc coder une partie de mon logigramme ainsi.

OnFilledListItem

procedure TForm13.LinkListControlToField1FilledListItem(Sender: TObject;
const AEditor: IBindListEditorItem);
// le remplissage d'un item est fini
var AnItem : TListViewItem;
begin
AnItem:=AEditor.CurrentObject as TListViewItem;
if (AnItem.Purpose=TListItemPurpose.Header) then
begin
// Ajout du Pied de Groupe si ce n'est pas le premier groupe
if (AnItem.Index>1)  then
begin
with ListView1.Items.Add do
begin
Text := Format('Total %s %d',[idBloc,Cumul]);
Purpose := TListItemPurpose.Footer;
end;
end;
idBloc:=AnItem.Text;
Cumul:=0;
end;

À ce stade, une exécution du programme fournit le résultat suivant :

(cliquez ici pour aggrandir l'image)

Constat : le pied de groupe se trouve sous l'entête du groupe prochain et non au-dessus. La raison ? J'ai utilisé l'ajout d'un nouvel élément de liste : indiqué le type d'élément ne change rien. Il aurait donc fallu, plutôt que faire un ajout, faire une insertion. Toutefois, mes tentatives en ce sens ont échoué, les pieds se retrouvant tous ensemble et la plupart des éléments de ma liste vides !

Donc, il me faut déplacer ces pieds de groupes. Le mieux est de le faire une fois la liste remplie. Or, cela tombe bien puisqu'il me reste un dernier pied à ajouter, celui de fin de fichier.

J'utilise donc l'événement OnFilledList pour satisfaire cette dernière exigence.

OnFilledList

procedure TForm13.LinkListControlToField1FilledList(Sender: TObject);
// le remplissage de la liste est fini
var Pied : TListViewItem;
texte : String;
Li : integer;
begin
// Déplacement des Pieds d'une ligne vers le haut
for Li := 1 to Pred(ListView1.ItemCount) do
begin
if ListView1.Items[Li].Purpose=TListItemPurpose.Footer then
begin
// récupére le texte du pied
Pied:=ListView1.Items[Li];
Texte:=Pied.Text;
// Supprime le pied créé (pas d'autre solution ?)
ListView1.Items.Delete(Li);
// Insére le nouveau pied
with ListView1.Items.Insert(Li-1) do
begin
Text := Texte;
Purpose := TListItemPurpose.Footer;
end;
end;
end;
// Ajout de la dernière totalisation (=fin de fichier)
with ListView1.Items.Add do
begin
Text := Format('Total %s %d',[IdBloc,Cumul]);
Purpose := TListItemPurpose.Footer;
end;
end;

Une nouvelle exécution du code montre la liste groupée enfin conforme aux attentes.

III-C-1 - Pistes à explorer, questions sans réponses

Confronté à la problématique, j'ai peut-être pris un chemin peu académique. En tout cas, je trouve que l'opération consistant à remonter le pied de groupe à la fin du remplissage de la liste, si elle est efficace, n'est pas très satisfaisante. Y aurait-il un moyen d'utiliser l'événement OnFillingListItem ?

Avec cet exemple, je n'ai pas vérifié le comportement des cumuls en cas de mise à jour d'un des éléments.

IV - Application

Il est temps de faire un bond dans une vie plus réelle. Pour montrer comment il est possible d'utiliser ce qui a été expliqué précédemment, je vais utiliser des fichiers exemples fournis avec Delphi dans le répertoire C:\Users\Public\Documents\Embarcadero\Studio\19.0\Samples\Data . En l'occurrence, je vais utiliser les fichiers de commandes (orders.cds) et clients (customers.cds) proposés et faire une liste groupée des commandes par client.

Il y a peu de changements par rapport au programme précédent si ce n'est l'utilisation de TClientDatasets au lieu des données aléatoires d'un TPrototypeBindSource.

(cliquez ici pour aggrandir l'image)

L'objectif est d'indiquer le nom du client (Company) en entête et le montant total des ordres en pied de groupe. En détail, j'indiquerai la date de l'ordre (SaleDate), son montant total (ItemsTotal) et le montant payé (AmountPaid) fera office de cerise sur le gâteau en me permettant, lors d'une étape de finalisation, de faire ressortir en couleur les impayés.

IV-A - Préparer les données

Au risque de me répéter, l'important est avant tout l'ordre de l'ensemble de données qui va remplir la liste. Pour cela, je vais indiquer la liste des colonnes qui me serviront d'index pour mon TClientDataset.

(cliquez ici pour aggrandir l'image)

Je déclare ensuite toutes les colonnes nécessaires (double-clic sur le composant pour obtenir la fenêtre de l'éditeur de champs) et sélectionne les colonnes qui vont me servir.

Ensuite, j'ajoute une nouvelle colonne de type recherche de façon à obtenir le nom de la société (Company) en fonction de l'identifiant du client (Custno).

(cliquez ici pour aggrandir l'image)

IV-B - Préparer la liste

J'applique la méthode exposée au chapitre I Construction de l'interface, c'est-à-dire l'utilisation d'une apparence dynamique pour mes éléments de liste puis je bascule en mode conception pour ajouter et retailler les différents objets textes ajoutés (cf. image en début de chapitre)

IV-C - Remplissage de la liste

Le remplissage de la liste va se faire en deux temps : tout d'abord l'utilisation des liaisons (Livebindings) puis un peu de codification des événements de la liaison.

IV-C-1 - Avec le concepteur de liaisons visuel

Via le concepteur, le remplissage est presque un jeu d'enfant. Il faut obtenir le schéma suivant :

(cliquez ici pour aggrandir l'image)

IV-C-2 - Sans le concepteur visuel

Sans le concepteur visuel, il faut suivre les étapes du chapitre II.B Sans le concepteur visuel de liaisons

(cliquez ici pour aggrandir l'image)

À la fin de l'étape de liaison, j'obtiens déjà un premier résultat.

Premier résultat

(cliquez ici pour aggrandir l'image)

IV-C-3 - Codification

Comme exposé au chapitre III.C Programmation, la suite passe par la codification des deux événements OnFilledListItem et OnFilledList de la liaison LinkListControlToFielField.

procedure TForm15.LinkListControlToField1FilledList(Sender: TObject);
var Pied : TListViewItem;
texte : String;
Li : integer;
begin
// Déplacement des Pieds d'une ligne vers le haut
for Li := 1 to Pred(ListView1.ItemCount) do
begin
if ListView1.Items[Li].Purpose=TListItemPurpose.Footer then
begin
// récupére le texte du pied
Pied:=ListView1.Items[Li];
Texte:=Pied.Text;
// Supprime le pied créé (pas d'autre solution ?)
ListView1.Items.Delete(Li);
// Insère le nouveau pied
with ListView1.Items.Insert(Li-1) do
begin
Text := Texte;
Purpose := TListItemPurpose.Footer;
end;
end;
end;
// Ajout de la dernière totalisation (=fin de fichier)
with ListView1.Items.Add do
begin
Text := Format('Total %s %3.2f',[idCust,Cumul]);
Purpose := TListItemPurpose.Footer;
end;
end;

procedure TForm15.LinkListControlToField1FilledListItem(Sender: TObject;
const AEditor: IBindListEditorItem);
// le remplissage d'un item est fini
var AnItem : TListViewItem;
begin
AnItem:=AEditor.CurrentObject as TListViewItem;
if (AnItem.Purpose=TListItemPurpose.Header) then
begin
// Ajout du Pied de Groupe si ce n'est pas le premier groupe
if (AnItem.Index>1)  then
begin
with ListView1.Items.Add do
begin
Text := Format('Total %s %3.2f',[idCust,Cumul]);
Purpose := TListItemPurpose.Footer;
end;
// cet item devra être déplacé vers le haut
end;
idCust:=OrdersSociete.asString;
Cumul:=0;
end
else begin
// Cumul par client
Cumul:=Cumul+Orders.FieldByName('ItemsTotal').AsCurrency;
end;
end;

À l'exécution, les pieds de groupes sont bien calculés et affichés.

Pour donner un peu plus de corps à la liste, j'ai ajouté un style (TStyleBook) chargé à partir du fichier MetropolisUIBlue.Style

(cliquez ici pour aggrandir l'image)

IV-D - Finalisation

Dans le fichier proposé, nous remarquons que certaines factures ne sont pas encore payées (AmountPaid=0) : tant qu'à faire, il serait bon de pouvoir mettre celles-ci en exergue.

Pour faire ceci, en guise de première étape, il faut ajouter un TImageObjectAppearance.

(cliquez ici pour aggrandir l'image)

Je le concède, en mode conception, c'est loin d'être séduisant ! En rebasculant en mode normal, l'image est invisible. Pour revoir tous mes objets textes, il me faudra modifier le code source du fichier dfm afin de mettre l'élément image en début de collection.

Modification du dfm

object Form15: TForm15
Left = 0
Top = 0
Caption = 'Form15'
ClientHeight = 526
ClientWidth = 529
StyleBook = StyleBook1
FormFactor.Width = 320
FormFactor.Height = 480
FormFactor.Devices = [Desktop]
DesignerMasterStyle = 0
object ListView1: TListView
ItemAppearanceClassName = 'TDynamicAppearance'
ItemEditAppearanceClassName = 'TDynamicAppearance'
HeaderAppearanceClassName = 'TListHeaderObjects'
FooterAppearanceClassName = 'TListHeaderObjects'
AlternatingColors = True
ItemIndex = 1
ItemSpaces.Right = 0.000000000000000000
Align = Left
Size.Width = 385.000000000000000000
Size.Height = 526.000000000000000000
Size.PlatformDefault = False
TabOrder = 4
ItemAppearance.HeaderHeight = 30
ItemAppearance.FooterHeight = 30
ItemAppearanceObjects.ItemObjects.ObjectsCollection = <
item
AppearanceObjectName = 'Image4'
AppearanceClassName = 'TImageObjectAppearance'
Appearance.ScalingMode = Stretch
Appearance.Opacity = 0.500000000000000000
end
item
AppearanceObjectName = 'Text1'
AppearanceClassName = 'TTextObjectAppearance'
Appearance.TextAlign = Leading
Appearance.Width = 111.000000000000000000
Appearance.Height = 25.000000000000000000
end
item
AppearanceObjectName = 'Text2'
AppearanceClassName = 'TTextObjectAppearance'
Appearance.TextAlign = Trailing
Appearance.Width = 158.000000000000000000
Appearance.Height = 25.000000000000000000
Appearance.PlaceOffset.X = 198.000000000000000000
end
item
AppearanceObjectName = 'Text3'
AppearanceClassName = 'TTextObjectAppearance'
Appearance.TextAlign = Trailing
Appearance.Width = 158.000000000000000000
Appearance.Height = 21.000000000000000000
Appearance.PlaceOffset.X = 198.000000000000000000
Appearance.PlaceOffset.Y = 24.000000000000000000
end

Il faut à présent coder de façon à remplir cette image. Dans le tutoriel FMX : Mettre de la couleur dans un TListView, j'ai proposé l'utilisation de l'événement OnUpdatingObject du composant TListView. Fort de mes nouvelles expériences, je propose plutôt de faire l'opération à l'intérieur de l'événement OnFilledListItem déjà codé.

Ajout couleur

procedure TForm15.LinkListControlToField1FilledListItem(Sender: TObject;
const AEditor: IBindListEditorItem);
// le remplissage d'un item est fini
var AnItem : TListViewItem;
Montant : String;
AListItemBitmap : TListItemImage;
AColor : TAlphaColor;
begin
AnItem:=AEditor.CurrentObject as TListViewItem;
if (AnItem.Purpose=TListItemPurpose.Header) then
begin
// Ajout du Pied de Groupe si ce n'est pas le premier groupe
if (AnItem.Index>1)  then
begin
with ListView1.Items.Add do
begin
Text := Format('Total %s %3.2f',[idCust,Cumul]);
Purpose := TListItemPurpose.Footer;
end;
// cet item devra être déplacé vers le haut
end;
idCust:=OrdersSociete.asString;
Cumul:=0;
end
else begin
// Gestion de la couleur ---------------------------------------------
AListItemBitmap:=AnItem.Objects.FindObjectT<TListItemImage>('Image4');
if assigned(AListItemBitmap) then
begin
AListItemBitmap.Bitmap:=TBitmap.Create(40,40);
if OrdersAmountPaid.AsCurrency<OrdersItemsTotal.asCurrency then
AColor:=TAlphacolorRec.Crimson
else
AColor:=TAlphaColorRec.Null;
AListItemBitmap.Bitmap.Clear(AColor);
end;
// ----------------------------------------------------------
// Cumul par client
Cumul:=Cumul+Orders.FieldByName('ItemsTotal').AsCurrency;
end;
end;

À l'exécution, j'obtiens alors ceci :

(cliquez ici pour aggrandir l'image)

Cette implémentation m'a même permis de répondre à une question que je me posais : comment ajouter des informations fixes à ma liste ? En effet, l'utilisation de CustomFormat pour les montants n'est pas très satisfaisante, les libellés n'étant pas alignés. La présentation est donc encore perfectible. J'ai par conséquent ajouté deux éléments textes et supprimé ces CustomFormat inélégants.

Un peu de code supplémentaire finalisera ma procédure.

Éléments fixes

procedure TForm15.LinkListControlToField1FilledListItem(Sender: TObject;
const AEditor: IBindListEditorItem);
// le remplissage d'un item est fini
// le remplissage d'un item est fini
var AnItem : TListViewItem;
AListItemBitmap : TListItemImage;
AListItemTextFixe : TListItemText;
AColor : TAlphaColor;
begin
AnItem:=AEditor.CurrentObject as TListViewItem;
if (AnItem.Purpose=TListItemPurpose.Header) then
begin
// Ajout du Pied de Groupe si ce n'est pas le premier groupe
if (AnItem.Index>1)  then
begin
with ListView1.Items.Add do
begin
Text := Format('Total %s %3.2f',[Customer,Cumul]);
Purpose := TListItemPurpose.Footer;
end;
// cet item devra être déplacé vers le haut
end;
idCust:=OrdersCustNo.Value;
Customer:=OrdersSociete.asString;
Cumul:=0;
end
else begin
// Ajout des libellés fixes
AListItemTextFixe:=AnItem.Objects.FindObjectT<TListItemText>('Text6');
if Assigned(AListItemTextFixe) then
AListItemTextFixe.Text:='Montant Total';
AListItemTextFixe:=AnItem.Objects.FindObjectT<TListItemText>('Text7');
if Assigned(AListItemTextFixe) then AListItemTextFixe.Text:='Payé';
// Gestion de la couleur ------------------------------------------
AListItemBitmap:=AnItem.Objects.FindObjectT<TListItemImage>('Image4');
if assigned(AListItemBitmap) then
begin
AListItemBitmap.Bitmap:=TBitmap.Create(40,40);
if OrdersAmountPaid.AsCurrency<OrdersItemsTotal.asCurrency
then AColor:=TAlphacolorRec.Crimson
else
AColor:=TAlphaColorRec.Null;
AListItemBitmap.Bitmap.Clear(AColor);
end;
// ----------------------------------------------------------
// Cumul par client
Cumul:=Cumul+Orders.FieldByName('ItemsTotal').AsCurrency;
end;
end;

(cliquez ici pour aggrandir l'image)

J'ai même profité de l'occasion pour associer à la liste une boîte de recherche. Toutefois, cette recherche ne fonctionne que sur les éléments de liste et pas sur les parties entêtes ou pieds de groupes, et c'est dommage !

Une astuce pour pouvoir pallier cette lacune ? Comme la recherche ne se fait que sur les éléments de l'item de liste, rien n'empêche d'ajouter un (ou plusieurs) nouveau(x) TTextObjectAppareance, mais de le(s) rendre invisible(s).

Par exemple, j'ai ajouté un élément texte, lié à la colonne Société :

(cliquez ici pour aggrandir l'image)

J'ai alors la possibilité de faire la recherche sur le nom du client.

(cliquez ici pour aggrandir l'image)

Aucune ligne de code n'a été écrite pour ajouter cette fonctionnalité de recherche !

V - Mettre à bas le jeu des chaises musicales entre entête et pied de groupe

Soit vous avez suivi mon cheminement et, comme moi, vous trouvez que la solution proposée n'est pas très orthodoxe, soit vous avez sauté les étapes intermédiaires pour obtenir mes dernières cogitations.

Pour ceux qui accèdent directement à ce chapitre, un seul prérequis : savoir comment grouper des éléments de listes via les LiveBindings et savoir qu'un lien entre une liste et sa source, un TLinkListControlToField, a aussi ses propres événements.

Pour résumer, l'astuce consiste donc à utiliser les événements de ce lien pour, lors de la création d'un élément de type Header, ajouter à la volée un autre élément que l'on définit ensuite de type Footer.

L'élément de type Header est créé automatiquement par le moteur des LiveBindings. Mon raisonnement a été de me poser la question : « ne pourrait-on pas changer ce type sans que cela influe sur le comportement du moteur ? ».

Eh bien, oui ! C'est possible : il suffit alors de remplir le texte de l'élément créé automatiquement du texte de bas de groupe, de changer sa finalité (Purpose) et de faire de l'élément créé à la volée (qui se retrouvera dessous, je le rappelle) non plus le pied de groupe mais l'entête du nouveau groupe, texte associé bien évidemment.

Le schéma de code devient alors le suivant. Ce qui change vraiment ? Les lignes 28 à 39.

1. {Fin de remplissage de la liste}

2. procedure TForm15.LinkListControlToField1FilledList(Sender: TObject);

3. var Pied : TListViewItem;

4. begin

5. // Ajout de la dernière totalisation (= fin de fichier)

6. with ListView1.Items.Add do

7. begin

8. //texte du dernier pied de groupe

9. Text :='Pied de groupe';

10. Purpose := TListItemPurpose.Footer;

11. end;

12. end;

14. {Fin de remplissage d'un élément de la liste}

15. procedure TForm15.LinkListControlToField1FilledListItem(Sender: TObject;

16. const AEditor: IBindListEditorItem);

17. var AnItem : TListViewItem;

18. AListItemBitmap : TListItemImage;

19. AListItemTextFixe : TListItemText;

20. AColor : TAlphaColor;

21. begin

22. AnItem:=AEditor.CurrentObject as TListViewItem; // Item créé

23. if (AnItem.Purpose=TListItemPurpose.Header) then

24. begin

25. // Ajout du Pied de Groupe si ce n'est pas le premier groupe

26. if (AnItem.Index>1)  then

27. begin

28. with ListView1.Items.Add do

29. begin

30. // cet item n'a plus besoin d'être déplacé vers le haut

31. // et sera l'entête du nouveau groupe

32. // je récupère l'entête de groupe de l'élément créé automatiquement

33. Text := AnItem.text;

34. Purpose := TListItemPurpose.Header;

35. end;

36. // L'item créé automatiquement devient le pied de groupe

37. AnItem.Purpose:=TListItemPurpose.Footer;

38. // on y met le texte de pied de groupe

39. AnItem.Text:='Pied de groupe'

40. end;

41. // mémorisations

42. // initialisations

43. end

44. else begin

45. // Ajout des libellés fixes

46. // Gestion de la couleur

47. // Cumuls

48. … ;

49. end;

50. end;

VI - Conclusion

Les LiveBindings ne font pas tout. Si avec les besoins simples on a bien du zéro code, il faut parfois ajouter son grain de sel. Très peu documentés, utilisés et même connus, les événements sur les liaisons offrent des possibilités intéressantes et c'est ce que ce tutoriel a tenté de montrer.

Mon seul regret est de ne pas avoir su utiliser l'événement OnFillingListItem dans ce cadre. Un bémol : au chapitre III.C.1, j'émettais des doutes sur le comportement des totaux en cas de modification d'un montant… Ces doutes sont confirmés, car, sauf rafraîchissement de l'ensemble des données, en cas de modification d'une donnée, si les modifications s'appliquent à l'élément détail, elles ne sont pas répercutées sur les cumuls du groupe. Pareillement, si une recherche filtre les éléments d'un groupe, cela n'a aucune incidence sur l'affichage du pied de groupe. En bref, il y a quand même des limites !

Mes remerciements aux relecteurs techniques gvasseur58 et orthographiques f-leb qui ont mis la touche finale à ce tutoriel.

Vous pourrez télécharger les sources des deux projets à cette adresse.

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article