IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Voir le flux RSS

Blog de Serge Girard (aka SergioMaster)

[FMX] Utilisation de FillBreakGroups de la liaison des données à un TListView

Noter ce billet
par , 15/10/2019 à 16h36 (1020 Affichages)
La curiosité est-elle un défaut ? En rédigeant mes diverses interventions sur les TListViews et, en particulier, en liant ces dernières à une source de données la propriété FillBreakGroups du lien (un TLinkFillControlToField) me titillait l'esprit. Je me suis donc penché sur ce point particulier que je n'avais, jusqu'à présent jamais utilisé.

Pour tester ceci je suis parti d'un ensemble de données simple, récupéré à partir d'un fichier fourni dans le répertoire <Embarcadero>\Samples\Data\customers.xml, que j'ai quelque peu condensé pour ne garder que les colonnes CustNo, Country, Company et mis dans un fichier mémoire TFDMemtable.

Après établissement du lien entre mon TListView (apparence ListItemRightDetail) et ma source de données en utilisant le concepteur visuel de liaisons, je suis retourné sur les propriétés du lien ainsi créé pour indiquer le nom de la colonne qui allait être utilisé pour contröler les groupes : propriété FillBreakFieldName. Ensuite, en cliquant sur bouton points de suspension (...) de la propriété FillBreakGroups j'ai ajouté quelques groupes.

Nom : DesignGroupes.PNG
Affichages : 237
Taille : 46,2 Ko

Vous pouvez constater, dès le design, la prise en compte de ces éléments.

Bien sûr, la saisie des groupes étant plutôt rédhibitoire je me suis ensuite posé la question : Est-il possible, à l'exécution, de créer mes groupes en fonction des données ?

J'ai donc ajouté un bouton qui exécutera le processus, découper l'ensemble des données en centaines.
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
procedure TmainForm.btngrp100Click(Sender: TObject);
var n : integer;
    aGroup : TFillBreakGroupItem;
// min et max qu'il faudrait récupérer via une requête
const min = 1221;
      max = 9841;
begin
LinkListGroups.Active:=false;
LinkListGroups.FillBreakGroups.Clear;
// Créations des divers groupes
for n := (min div 100) to (max div 100) do
  begin
   aGroup := LinkListGroups.FillBreakGroups.AddItem;
   aGroup.MaxValue:=(n*100+99).ToString;
   aGroup.MinValue:=(n*100).ToString;
   aGroup.DisplayText:=format('Centaine %d',[n*100]);
  end;
LinkListGroups.Active:=True;
end;
MinValue et MaxValue attendent des chaines de caractères pas des entiers

Nom : RunGroupes100.PNG
Affichages : 507
Taille : 16,0 Ko

Deux autres questions se sont alors posées :
  1. La récupération des valeurs minimum et maximum, plutôt que l'utilisation de constantes;
  2. Le changement dans l'ordre de la source de données pour la création d'une liste groupée différemment.


Point 1
Table en mémoire oblige, pour obtenir les valeurs minimale et maximale de ma source de données il me faut passer par du SQL Local. Pour cela j'ajoute un TFDConnexion (driver SQLite), un TFDLocalSQL et un TFDQuery . Enfin, je renseigne la propriété LocalSQL de ma table mémoire.

Nom : design.PNG
Affichages : 209
Taille : 17,0 Ko

Il ne reste alors qu'à modifier le code
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
24
25
26
27
28
29
procedure TmainForm.btngrp100Click(Sender: TObject);
var n : integer;
    aGroup : TFillBreakGroupItem;
    NoMin, NoMax : Integer;
begin
LinkListGroups.Active:=false;
LinkListGroups.FillBreakGroups.Clear;
{---}
fdCustomers.DisableControls;  // éviter le scintillement
fdCustomers.IndexName:='DEFAULT_ORDER';  // s'assurer de l'ordre (ascendant sur la colonne custno)
FDLocalSQL1.Active:=True; // activer le SQL Local
fdquery1.SQl.text:='SELECT MIN(CUSTNO) AS CMIN,MAX(CUSTNO) AS CMAX FROM fdCustomers';
fdQuery1.Active:=True;
noMin:=fdQuery1.FieldByName('CMin').asInteger div 100;
noMax:=fdQuery1.FieldByName('CMax').asInteger div 100;
FDLocalSQL1.Active:=False;
// Créations des divers groupes
for n := noMin to noMax do{--}
  begin
   aGroup := LinkListGroups.FillBreakGroups.AddItem;
   aGroup.MaxValue:=(n*100+99).ToString;
   aGroup.MinValue:=(n*100).ToString;
   aGroup.DisplayText:=format('Centaine %d',[n*100]);
  end;
LinkListGroups.FillBreakFieldName:='CustNo'; // s'assure que c'est bien que c'est sur la colonne Custno que s'opére la rupture
LinkListGroups.Active:=True;
fdCustomers.EnableControls;  // réactive les contrôles
end;
Point 2
Jusqu'à présent la table était ordonnée (obligatoire) selon le numéro de client (custno), lors des définitions de la table en mémoire, j'avais défini un second index basé sur un ordre alphabétique des colonnes Country et Company. Bien évidemment il faut changer les groupes mais aussi le critère de regroupement .

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
24
25
26
procedure TmainForm.btnPaysNomClick(Sender: TObject);
var aGroup : TFillBreakGroupItem;
begin
fdCustomers.DisableControls;
fdCustomers.IndexName:='PAYS';  // Changement d'ordre
FDLocalSQL1.Active:=True;  
// Récupération des différents pays 
fdquery1.SQl.text:='SELECT DISTINCT COUNTRY FROM fdCustomers ORDER BY COUNTRY';
fdQuery1.Active:=True;

LinkListGroups.Active:=false;
// Création des groupes
LinkListGroups.FillBreakGroups.Clear;
while Not FDQuery1.EOF do
  begin
   aGroup := LinkListGroups.FillBreakGroups.AddItem;
   aGroup.MaxValue:=FDQuery1.FieldByName('COUNTRY').asString;
   aGroup.MinValue:=FDQuery1.FieldByName('COUNTRY').asString;
   aGroup.DisplayText:=FDQuery1.FieldByName('COUNTRY').asString;
   FDQuery1.Next;
  end;
FDLocalSQL1.Active:=False;
LinkListGroups.FillBreakFieldName:='Country';  // Indication du critère de regroupement 
LinkListGroups.Active:=True;
fdCustomers.EnableControls;
end;
Plus une feuille de style pour donner un peu de "peps" au résultat et voilà le résultat

Nom : Groupe100.PNG
Affichages : 357
Taille : 17,2 Ko Nom : Pays.PNG
Affichages : 405
Taille : 16,9 Ko

Voilà ma curiosité satisfaite, j'espère qu'il en sera de même pour vous. Une entrée de plus à mon carnet de plongée qui s'étoffe tant que rien que dans cette petite mer des listes (TListBox, TListView) il y a matière à un livre. Qui sait, peut-être un jour m'y lancerai-je !

En pièce jointe mon petit projet.

[Correctif]
En utilisant cette astuce pour diverses sources de données, je me suis aperçu que les collections de groupes (FillBreakGroups) ne fonctionnaient pas si la liste était synchronisée . L'exemple suivant utilisant le célèbre fichier "biolife" en fait la démonstration. L'objectif était de grouper les éléments en fonction de la taille en cm.

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
39
40
41
42
43
44
45
46
47
procedure TMainForm.rdbListBoxChange(Sender: TObject);
var AFillBreakGroup : TFillBreakGroupItem;
begin
LinkListBoxGroup.Active:=False;
if TRadioButton(Sender).IsChecked then
 begin
   case TRadioButton(Sender).tag of
     0 : begin
          LinkListBoxGroup.FillBreakGroups.Clear;
          LinkListBoxGroup.FillBreakFieldName:='';
          LinkListBoxGroup.FillHeaderFieldName:='';
          ClientDataSet1.IndexFieldNames:='';
         end;
     1 : begin
          LinkListBoxGroup.FillBreakGroups.Clear;
          LinkListBoxGroup.FillBreakFieldName:='Category';
          LinkListBoxGroup.FillHeaderFieldName:='Category';
          ClientDataSet1.IndexFieldNames:='Category';
         end;
     2 : begin
          ClientDataSet1.IndexFieldNames:='Length (cm)';
          LinkListBoxGroup.FillBreakFieldName:='Length (cm)';
          LinkListBoxGroup.FillHeaderFieldName:='';
          // créer les groupes
          aFillBreakGroup:=LinkListBoxGroup.FillBreakGroups.AddItem;
          AFillBreakGroup.MinValue:='0';
          AFillBreakGroup.MaxValue:=30.ToString;
          AFillBreakGroup.DisplayText:='Petits';
 
          aFillBreakGroup:=LinkListBoxGroup.FillBreakGroups.AddItem;
          AFillBreakGroup.MinValue:=30.ToString;
          AFillBreakGroup.MaxValue:=60.toString;
          AFillBreakGroup.DisplayText:='Moyens';
 
          aFillBreakGroup:=LinkListBoxGroup.FillBreakGroups.AddItem;
          AFillBreakGroup.MinValue:=60.ToString;
          AFillBreakGroup.MaxValue:=90.ToString;
          AFillBreakGroup.DisplayText:='Gros';
 
          aFillBreakGroup:=LinkListBoxGroup.FillBreakGroups.AddItem;
          AFillBreakGroup.MinValue:=90.ToString;
          AFillBreakGroup.DisplayText:='Enormes';
         end;
   end;
 end;
LinkListBoxGroup.Active:=True;
end;

Liste non synchronisée classement par taille ("Petits" de 0 à 30, "Moyens" de 30 à 60, "Gros" de 60 à 90, "Énormes" >90)
Nom : Capture.PNG
Affichages : 201
Taille : 46,4 Ko

Liste synchronisée
Nom : Capture_2.PNG
Affichages : 216
Taille : 23,3 Ko

Gênant et embarrassant même si aucun des lecteurs de ce billet n'a, jusqu'à présent, relevé ce fait. Peut-être même que certains ont tenté en vain d'obtenir le même résultat que ce qui était proposé sans y parvenir ?

Notez bien que la synchronisation fonctionne parfaitement si le groupement est établi plus "classiquement".

Bref, bogue ou non, il fallait quand même pouvoir avoir une synchronisation si l'on veut, par exemple, obtenir des détails sur l'item cliqué.

Pour contourner cet écueil voici une suggestion de code
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
 
procedure TMainForm.ListGroupItemClick(const Sender: TObject;
  const AItem: TListViewItem);
var i,rec : integer;
begin
rec:=0;
{ petite optimisation, si la liste n'est pas groupée  
 if LinkGroupes.FillBreakFieldName.IsEmpty   
 then rec:=AItem.Index+1
 else} 
// recherche le rang de l'élément (numéro d'enregistrement) en excluant les entêtes et pieds de groupes
    for i := 0 to AItem.Index do
         if ListGroup.Items[i].Purpose=TListItemPurpose.none then inc(rec);
ClientDataSet1.Recno:=rec;  // << c'est cette instruction qui fait la synchronisation
// suite du traitement 
end;
Miniatures attachées Fichiers attachés

Envoyer le billet « [FMX] Utilisation de FillBreakGroups de la liaison des données à un TListView » dans le blog Viadeo Envoyer le billet « [FMX] Utilisation de FillBreakGroups de la liaison des données à un TListView » dans le blog Twitter Envoyer le billet « [FMX] Utilisation de FillBreakGroups de la liaison des données à un TListView » dans le blog Google Envoyer le billet « [FMX] Utilisation de FillBreakGroups de la liaison des données à un TListView » dans le blog Facebook Envoyer le billet « [FMX] Utilisation de FillBreakGroups de la liaison des données à un TListView » dans le blog Digg Envoyer le billet « [FMX] Utilisation de FillBreakGroups de la liaison des données à un TListView » dans le blog Delicious Envoyer le billet « [FMX] Utilisation de FillBreakGroups de la liaison des données à un TListView » dans le blog MySpace Envoyer le billet « [FMX] Utilisation de FillBreakGroups de la liaison des données à un TListView » dans le blog Yahoo

Mis à jour 19/11/2019 à 08h59 par SergioMaster

Tags: fmx, groupes, listview
Catégories
Programmation , Delphi , FMX

Commentaires