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.
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.
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
Deux autres questions se sont alors posées :
La récupération des valeurs minimum et maximum, plutôt que l'utilisation de constantes;
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.
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 .
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
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.
procedure TMainForm.rdbListBoxChange(Sender: TObject);
var AFillBreakGroup : TFillBreakGroupItem;
begin
LinkListBoxGroup.Active:=False;
if TRadioButton(Sender).IsChecked thenbegincase TRadioButton(Sender).tag of0 : 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)
Liste synchronisée
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
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 groupesfor i := 0to AItem.Index doif ListGroup.Items[i].Purpose=TListItemPurpose.none then inc(rec);
ClientDataSet1.Recno:=rec; // << c'est cette instruction qui fait la synchronisation// suite du traitement end;
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.