Personnaliser un TListView

Mettre des pieds de groupes

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.

1 commentaire Donner une note  l'article (5)

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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).

Image non disponible

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.

Image non disponible

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.

Image non disponible

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.

Image non disponible

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.

Image non disponible

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).

Image non disponible

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

Image non disponible

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

Image non disponible

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

Image non disponible

J'ajoute donc un TTextObjectAppearance supplémentaire.

Image non disponible

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.

Image non disponible

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).

Image non disponible
Image non disponible

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.

Image non disponible

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

Image non disponible

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.

Image non disponible

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.

Image non disponible

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

  • la propriété Control ;
  • la propriété DataSource.
Image non disponible

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.

Image non disponible

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.

Image non disponible

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 ».

 
Sélectionnez
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
Sélectionnez
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 :

Image non disponible

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
Sélectionnez
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.

Image non disponible

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.

Image non disponible

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).

Image non disponible

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 :

Image non disponible

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

Image non disponible

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

Image non disponible
Premier résultat

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.

 
Sélectionnez
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

Image non disponible

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.

Image non disponible

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
Sélectionnez
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
Sélectionnez
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 :

Image non disponible

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
Sélectionnez
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;
Image non disponible

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é :

Image non disponible

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

Image non disponible

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.

 
Sélectionnez
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.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
{Fin de remplissage de la liste}
procedure TForm15.LinkListControlToField1FilledList(Sender: TObject);
var Pied : TListViewItem;
begin
  // Ajout de la dernière totalisation (= fin de fichier)
  with ListView1.Items.Add do
    begin
      //texte du dernier pied de groupe  
      Text :='Pied de groupe'; 
      Purpose := TListItemPurpose.Footer;
    end;
end;

{Fin de remplissage d'un élément de la liste}
procedure TForm15.LinkListControlToField1FilledListItem(Sender: TObject; 
const AEditor: IBindListEditorItem);
var AnItem : TListViewItem;
    AListItemBitmap : TListItemImage;
    AListItemTextFixe : TListItemText;
    AColor : TAlphaColor;
begin
AnItem:=AEditor.CurrentObject as TListViewItem; // Item créé
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
        // cet item n'a plus besoin d'être déplacé vers le haut
        // et sera l'entête du nouveau groupe 
        // je récupère l'entête de groupe de l'élément créé automatiquement 
          Text := AnItem.text;
          Purpose := TListItemPurpose.Header;
     end;
     // L'item créé automatiquement devient le pied de groupe 
     AnItem.Purpose:=TListItemPurpose.Footer;
     // on y met le texte de pied de groupe
     AnItem.Text:='Pied de groupe'  
   end;
  // mémorisations
  // initialisations 
 end
 else begin
   // Ajout des libellés fixes
   // Gestion de la couleur  
   // Cumuls
   … ;
 end;
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.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

En complément sur Developpez.com

  

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.