FMX : Mettre de la couleur dans un TListView

Utilisation de l'apparence dynamique

Serge Girard

Mettre en exergue, via une couleur de fond, l'élément d'un TListView est une question fréquemment posée. Les solutions proposées ne sont pas toujours simples à mettre en œuvre, car, contrairement à une liste simple (TListBox), nous ne pouvons pas passer par des styles personnalisés pour le faire. L'apparition de l'apparence dynamique d'un élément de liste, lors de la sortie de la version Berlin 10.1, va nous permettre de le faire très simplement.

Commentez

- Titre : FMX : Mettre de la couleur dans un TListView

- Auteur : Serge Girard

- Parution : 14 octobre 2017

- 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 ® 2017 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 :

- Introduction au LiveBindings avec Delphi

Sommaire

- I. Création de l'interface

- I-A. Ajouter les champs au PrototypeBindSource

- I-B. Marche à suivre pour lier le champ à la liste

- I-B-1. Version Starter

- I-C. Modifier la présentation de l'item de liste

- I-D. Modifier les liaisons

- I-D-1. Version Starter

- I-D-2. Résultat

- I-E. L'astuce

- I-F. Utiliser l'événement

- I-F-1. Quelques explications s'imposent

- II. Un peu plus loin

- II-A. Version Starter

- II-B. Code

- III. Conclusion

I - Création de l'interface

Pour les besoins de la démonstration, nous avons besoin d'une application multi périphérique dans laquelle nous poserons notre liste (TListView). Comme source de données, nous utiliserons un prototype (TPrototypeBindSource) qui contiendra deux champs : un pour le nom des couleurs, l'autre pour contenir un bitmap.

(cliquez ici pour aggrandir l'image)

Une fois ces deux éléments posés, il nous faudra créer les champs et les lier à la liste pour obtenir une liste aléatoire de couleurs.

I-A - Ajouter les champs au PrototypeBindSource

Trois possibilités s'offrent à nous : l'utilisation du menu en bas de l'inspecteur d'objets, un clic droit sur le composant pour obtenir son menu contextuel, ou plus simplement un double clic sur ce dernier.

(cliquez ici pour aggrandir l'image)

Il nous faut choisir ensuite les types de champs souhaités ; dans notre cas, pour les noms de couleurs, nous utiliserons le générateur ColorsNames et pour l'image, le générateur Bitmaps.

(cliquez ici pour aggrandir l'image)

La propriété RecordCount est égale à -1 c'est-à-dire non limitée, ou plus exactement dans la limite du raisonnable. Nous pouvons limiter le nombre d'éléments à notre besoin (ici 30), histoire de réduire la durée de chargement du programme.

I-B - Marche à suivre pour lier le champ à la liste

Pour ceux qui ont une version professionnelle ou plus de Delphi Berlin, l'utilisation du concepteur visuel de lien rend la chose aisée. Il suffit d'une liaison entre le champ de notre source (ColorsName1) et l'objet de l'item de liste Text pour remplir une liste de base.

(cliquez ici pour aggrandir l'image)

Toutefois, pour l'instant, nous n'avons aucun endroit pour lier l'image à l'item !

I-B-1 - Version Starter

Pour ceux qui ont une version sans ce concepteur, voici les étapes à suivre :

1. Posez un composant TBindingsList. Comme pour le TPrototypeBindSource, un clic droit, un double clic, ou les options du menu en bas de l'inspecteur d'objets, vous permettent ensuite d'ajouter les liens ;

2. Ajoutez un lien de type LinkListControlToField ;

(cliquez ici pour aggrandir l'image)

3. Précisez les propriétés Control (ListView1) et DataSource (PrototypeBindSource1) ;

(cliquez ici pour aggrandir l'image)

4. Indiquez ensuite les expressions de remplissage (FillExpressions).

(cliquez ici pour aggrandir l'image)

Ajoutez un membre à ces expressions avec les propriétés :

Propriété

Valeur

Explications

ControlMembreName

Text

Correspond à l'objet de l'élément de la liste que nous voulons utiliser.

CustomFormat

SourceMemberName

ColorsName1

Le nom du champ de notre TPrototypeBindSource.

Vous obtiendrez alors le même résultat que précédemment, avec une version plus performante de l'EDI.

I-C - Modifier la présentation de l'item de liste

Par défaut, à la création de la liste, l'apparence de l'élément est de type ListItem, et ListItemShowCheck si cette liste est passée en mode modification.

Pour avoir une image que nous pourrons manipuler, nous allons modifier ce type en DynamicAppearance. Le nom de la couleur a disparu ! J'y reviendrai plus un peu plus loin.

La fausse bonne idée serait d'utiliser le type ImageListItem qui, s'il semble à première vue adéquat, ne nous permettra pas la suite des opérations.

(cliquez ici pour aggrandir l'image)

J'ai surligné sur l'image les points importants à prendre en compte.

Commencez par basculer en mode conception de l'apparence. Vous avez le choix entre utiliser l'option de menu en bas de l'inspecteur d'objets ou faire un simple clic droit sur le ListView.

(cliquez ici pour aggrandir l'image)

Se positionner sur l'item nous permet alors d'ajouter certains autres éléments :

TTextObjectAppearance

Un texte

TImageObjectAppearance

Une image

TAccessoryObjectAppearance

Un bouton accessoire (en général, la flèche vers la droite)

TButtonObjectAppearance

Un bouton permettant de mettre un texte à l'intérieur

TGlyphButtonObjectAppearance

Une case à cocher, ou un bouton + ou -
Attention, uniquement pour le mode édition, donc pour la collection d'objets d'ItemEditObjects.

Ajoutez un TImageObjectAppearance.

(cliquez ici pour aggrandir l'image)

Comme nous voulons que l'image ait la largeur de la liste, nous passerons la propriété ScalingMode à Stretch.

I-D - Modifier les liaisons

La liaison avec les données, sans être supprimée, ne fonctionne plus ! Pourquoi ? Tout simplement parce que nous n'affichons plus les mêmes objets. Lors de la liaison précédente, le texte était lié à Text, mais comme il s'agit d'une apparence dynamique, cette propriété n'est pas utilisée. Il faut modifier la liaison pour pointer sur l'objet créé par défaut au moment du changement de type d'apparence : Text1.

Ceux qui ont la possibilité d'avoir le concepteur visuel de lien verront aisément la différence. Vous remarquerez que vous pouvez alors facilement lier le champ Bitmap1 à l'Item.Image2 ajouté.

(cliquez ici pour aggrandir l'image)

I-D-1 - Version Starter

Pour les détenteurs d'une version Starter, il faudra repasser sur le TBindingsList et retourner sur les expressions de remplissage (comme à l'étape 4) pour modifier la propriété ControlMembreName.

(cliquez ici pour aggrandir l'image)

Ajoutez un second élément de liaison entre Image2, pour ce qui est de la propriété ControlMembreName, et Bitmap1 pour la valeur de SourceMemberName.

I-D-2 - Résultat

à ce stade une couleur apparaît dans la liste, mais le nom de la couleur n'apparaît toujours pas !

La génération du jeu d'essai étant aléatoire, le rendu pourra vous surprendre : il suffit de fermer et de rouvrir l'application pour avoir des résultats différents.

(cliquez ici pour aggrandir l'image)

Cette absence de nom s'explique tout bêtement par le fait que l'image est au premier plan.

I-E - L'astuce

Visualisez le texte de la fiche : clic droit sur la fiche et option « Voir comme texte ».

Avant

object ListView1: TListView
ItemAppearanceClassName = 'TDynamicAppearance'
ItemEditAppearanceClassName = 'TDynamicAppearance'
HeaderAppearanceClassName = 'TListHeaderObjects'
FooterAppearanceClassName = 'TListHeaderObjects'
ItemIndex = 0
Align = Client
Size.Width = 278.000000000000000000
Size.Height = 460.000000000000000000
Size.PlatformDefault = False
TabOrder = 0
ItemAppearanceObjects.ItemObjects.ObjectsCollection = <
item
AppearanceObjectName = 'Text1'
AppearanceClassName = 'TTextObjectAppearance'
end
item
AppearanceObjectName = 'Image2'
AppearanceClassName = 'TImageObjectAppearance'
Appearance.ScalingMode = Stretch
end>
ItemAppearanceObjects.ItemEditObjects.ObjectsCollection = <
item
AppearanceObjectName = 'Text1'
AppearanceClassName = 'TTextObjectAppearance'
end>
end

Déplacez les éléments de façon à mettre l'item image avant l'item texte.

Après

object ListView1: TListView
ItemAppearanceClassName = 'TDynamicAppearance'
ItemEditAppearanceClassName = 'TDynamicAppearance'
HeaderAppearanceClassName = 'TListHeaderObjects'
FooterAppearanceClassName = 'TListHeaderObjects'
OnUpdatingObjects = ListView1UpdatingObjects
ItemIndex = 0
ItemSpaces.Left = 0.000000000000000000
ItemSpaces.Right = 0.000000000000000000
Align = Client
Size.Width = 278.000000000000000000
Size.Height = 460.000000000000000000
Size.PlatformDefault = False
TabOrder = 0
ItemAppearanceObjects.ItemObjects.ObjectsCollection = <
item
AppearanceObjectName = 'Image2'
AppearanceClassName = 'TImageObjectAppearance'
Appearance.ScalingMode = Stretch
end
item
AppearanceObjectName = 'Text1'
AppearanceClassName = 'TTextObjectAppearance'
end>
ItemAppearanceObjects.ItemEditObjects.ObjectsCollection = <
item
AppearanceObjectName = 'Text1'
AppearanceClassName = 'TTextObjectAppearance'
end>
end

Puis rebasculez en mode fiche : clic droit option « Voir comme fiche » ou Alt+F12. Le mode conception de l'item est désactivé, mais nous voyons bien la liste aléatoire de couleurs.

(cliquez ici pour aggrandir l'image)

I-F - Utiliser l'événement

Il est évident qu'il nous reste une dernière étape à accomplir, car l'image ne correspond absolument pas, sauf hasard heureux, à l'intitulé. Pour remplir notre image, il faut programmer l'événement de liste OnUpdatingObjects.

(cliquez ici pour aggrandir l'image)

Pour obtenir une valeur de couleur en fonction du nom de celle-ci, nous serons obligés d'utiliser l'unité System.UIConsts.

Programme

unit Unit1;

interface

uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
System.UIConsts,  // ajout de l'unité pour obtenir la valeur de la couleur
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.ListView.Types, FMX.ListView.Appearances, FMX.ListView.Adapters.Base,
Data.Bind.GenData, Data.Bind.EngExt, Fmx.Bind.DBEngExt, System.Rtti,
System.Bindings.Outputs, Fmx.Bind.Editors, Data.Bind.Components,
Data.Bind.ObjectScope, FMX.ListView;

type
TForm1 = class(TForm)
ListView1: TListView;
PrototypeBindSource1: TPrototypeBindSource;
BindingsList1: TBindingsList;
LinkListControlToField1: TLinkListControlToField;
procedure ListView1UpdatingObjects(const Sender: TObject;
const AItem: TListViewItem; var AHandled: Boolean);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;

var
Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.ListView1UpdatingObjects(const Sender: TObject;
const AItem: TListViewItem; var AHandled: Boolean);
var AListItemBitmap : TListItemImage;
AListItemText : TListItemText;
AColor : TAlphaColor;
begin
AListItemBitmap:=AItem.Objects.FindObjectT<TListItemImage>('Image2');
AListItemText:=AItem.Objects.FindObjectT<TListItemText>('Text1');

if assigned(AListItemBitmap) then
begin
AListItemBitmap.Bitmap:=TBitmap.Create(40,40);
try
AColor:=StringToAlphaColor(AListItemText.Text)
except // certaines couleurs sont inconnues! i.e. monneygreen, ltgrey
AColor:=TAlphaColorRec.Null;
end;
AListItemBitmap.Bitmap.Clear(AColor);
end;
end;

end.

I-F-1 - Quelques explications s'imposent

AListItemBitmap : il nous faut récupérer l'adresse de l'image dans la collection ; ce sera chose faite via FindObjectT.

AListItemText : idem ; cela nous permettra de récupérer le texte qui nous servira à obtenir la valeur de la couleur.

Enfin, étonnamment, certains noms de couleurs générées sont inconnus dans l'unité System.UIConsts (en particulier monneygreen et ltgrey) ; il m'a donc fallu mettre la conversion à l'intérieur d'un bloc d'exception. Échaudé, j'en ai profité pour vérifier l'existence de mon image, d'où le if assigned(AListItemBitMap).

Dernier petit point pour obtenir un bon rendu : vérifier ItemsSpaces. Les marges gauche et droite sont respectivement de 10 et 11 par défaut. Réglez-les en fonction de vos besoins.

(cliquez ici pour aggrandir l'image)

Et voilà le résultat attendu !

(cliquez ici pour aggrandir l'image)

II - Un peu plus loin

Le principe des couleurs de fond est posé. Mais qu'en est-il si l'on voulait faire de même à partir de données ? Pour la démonstration, nous avons juste besoin d'ajouter à notre source de données un entier (il faut alors choisir le générateur de type Integer).

(cliquez ici pour aggrandir l'image)

Seconde astuce de ce tutoriel : comment accéder à la valeur de cet entier ? À partir d'un PrototypeBindSource c'est loin d'être évident, aussi suis-je tout simplement passé par un lien entre ce champ et le Text de l'élément (je vous rappelle que celui-ci n'apparaît pas à cause de l'apparence dynamique).

(cliquez ici pour aggrandir l'image)

II-A - Version Starter

Vous devrez passer par TBindingsList pour ajouter la liaison. Sélectionnez ensuite le ListLinkContolToField1 pour accéder aux propriétés FillExpressions. Cliquez alors sur les … pour accéder aux expressions.

(cliquez ici pour aggrandir l'image)

Ajoutez alors la liaison entre Text et IntField1.

II-B - Code

Nous pouvons maintenant utiliser cette valeur selon notre besoin. Ici, j'ai décidé de tester le modulo 3 de cet entier pour manipuler trois couleurs.

Comme l'entier généré peut être négatif, il faut utiliser sa valeur absolue.

Step2

{.$DEFINE STEP2}

procedure TForm1.ListView1UpdatingObjects(const Sender: TObject;
const AItem: TListViewItem; var AHandled: Boolean);
var AListItemBitmap : TListItemImage;
AListItemText : TListItemText;
AColor : TAlphaColor;
i : word;
begin
AListItemBitmap:=AItem.Objects.FindObjectT<TListItemImage>('Image2');
{$IFNDEF STEP2}
AListItemText:=AItem.Objects.FindObjectT<TListItemText>('Text1');
if Assigned(AListItemBitmap) then
begin
AListItemBitmap.Bitmap:=TBitmap.Create(40,40);
try
AColor:=StringToAlphaColor(AListItemText.Text)
except // certaines couleurs sont inconnues! i.e. monneygreen, ltgrey
AColor:=TAlphaColorRec.Null;
end;
AListItemBitmap.Bitmap.Clear(AColor);
end;
{$ENDIF}
{$IFDEF STEP2}
if Assigned(AListItemBitmap) then
begin
i:=Abs(AItem.Text.ToInteger) mod 3;
case i of
0 : AColor:=TAlphaColorRec.Green;
1 : AColor:=TAlphaColorRec.Yellow;
2 : AColor:=TAlphaColorRec.Red;
end;
AListItemBitmap.Bitmap.Clear(AColor);
end;
{$ENDIF}

end;

Pour pouvoir tester cette option, il suffit de dé-commenter la clause $DEFINE STEP2 du code source.

(cliquez ici pour aggrandir l'image)

Les couleurs piquent-elles les yeux ? Optez pour une version plus light en les changeant, par exemple, en chartreuse, gold et indianred.

III - Conclusion

L'apparence dynamique d'un item de liste permet de coloriser rapidement les éléments en fonction des données, et ce sans passer par la création complexe d'éléments personnalisés via la création d'un package (démarche indiquée dans cet exemple).

Suggestion de lectures : Personnalisation de l'apparence de la vue.

Point de conclusion sans remerciements aux premiers lecteurs : Nabil et Thierry, au bêta-testeur free07 pour la partie OSX et bien sûr aux relecteurs officiels du forum. Que cela soit de la relecture technique patiente de Gilles au correcteur orthographique et grammatical Maxy35. Sans eux, point de publication, donc encore merci à eux !

Vous pourrez télécharger les sources de ce projet à cette adresse Lien de téléchargement.

Toutefois, pour une bonne maîtrise, je vous suggère plutôt de tenter de reproduire l'exemple.

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