I. Mise en place▲
Ce tutoriel ne peut s'appliquer qu'à partir des versions 10.1 (Berlin) de Delphi. Le programme de démonstration a été fait avec la version 10.2.3 (Tokyo).
I-A. La source de données▲
L'intérêt du travail qui suit est bien d'afficher des données « réelles » dans une grille et non d'utiliser des données aléatoires comme peut le fournir un TPrototypeBindSource, trop théorique.
Le dilemme suivant qui s'est imposé était de trouver quel fichier, permettant de couvrir, à peu près, tous les types de colonnes que je pouvais utiliser. De surcroît, je voulais fournir un mode d’accès compatible avec une version starter de Delphi, c'est-à-dire avec un TClientDataset. Mes recherches dans les fichiers proposés en exemple n'aboutissant à rien d'exploitable, j'ai décidé d'en créer un propre autour d’un tableau de pêche. Cette application me sera ainsi utile pour vanter mes mérites quand je pratique mon passe-temps favori : la chasse sous-marine.
Sans plus tarder, en voici la structure :
Nom colonne |
Type/Taille |
Commentaire |
Nom |
String / 30 |
Prise faite |
DatePrise |
Date |
|
HeurePrise |
Time |
Plus pour le type de colonne que pour la précision des faits |
Poids |
Float |
Exprimé en kg. |
PrixduJour |
Currency |
Le prix chez le poissonnier, histoire de calculer si mon investissement en matériel est rentable. |
Quantité |
Word |
|
Catégorie |
Word |
Un entier qui va me permettre de classer mes prises entre :
Il me permettra par ailleurs d'afficher le glyphe correspondant. |
ModePrise |
Word |
Aussi un entier, mais je vais le relier à une liste des modes de prises possibles :
|
Vantardise |
Booléen |
Il me manquait un type booléen jusqu'à ce que je pense à l'adage « En tout pêcheur il y a un menteur » ! |
Circonstances |
Memo |
Description des lieux et autres anecdotes |
Images |
Graphic |
La preuve en photo |
if
not
FileExists('..\..\PecheurMenteur.cds'
) then
begin
// Création de la table de données
with
cdscreate.FieldDefs do
begin
with
AddFieldDef do
begin
Name := 'Nom'
;
DataType := ftString;
Size := 30
;
Required := False
;
end
;
with
AddFieldDef do
begin
Name := 'DatePrise'
;
DataType := ftDate;
Size := 0
;
Required := False
;
end
;
with
AddFieldDef do
begin
Name := 'Heure'
;
DataType := ftTime;
Size := 0
;
Required := False
;
end
;
with
AddFieldDef do
begin
Name := 'Categorie'
;
DataType := ftInteger;
Size := 0
;
Required := False
;
end
;
with
AddFieldDef do
begin
Name := 'Poids'
;
DataType := ftFloat;
Size := 0
;
Required := False
;
end
;
with
AddFieldDef do
begin
Name := 'PrixduJour'
;
DataType := ftCurrency;
Size := 0
;
Required := False
;
end
;
with
AddFieldDef do
begin
Name := 'Quantite'
;
DataType := ftWord;
Size := 0
;
Required := False
;
end
;
with
AddFieldDef do
begin
Name := 'ModePrise'
;
DataType := ftWord;
Size := 0
;
Required := False
;
end
;
with
AddFieldDef do
begin
Name := 'Vantardise'
;
DataType := ftBoolean;
Size := 0
;
Required := False
;
end
;
with
AddFieldDef do
begin
Name := 'Circonstances'
;
DataType := ftMemo;
Size := 0
;
Required := False
;
end
;
with
AddFieldDef do
begin
Name := 'Image'
;
DataType := ftGraphic;
Size := 0
;
Required := False
;
end
;
end
;
cdsCreate.CreateDataSet;
end
;
La création d'une table vide est incluse dans le programme, mais vous trouverez également une table préremplie (légèrement).
Je vous dévoile donc quelques-uns de mes coins de pêche, encore que, juste avec le nom du lieu-dit vous risquez d'avoir du mal à piller mes ressources !
I-B. Le programme de démonstration▲
Une fois n'est pas coutume, quelques explications s'imposent. Le programme a été écrit pour tester à la fois l'utilisation des TStringGrid et TGrid mais aussi le mode de liaison. Par conséquent, pour chacun de ces deux composants, il y a à la fois la liaison rapide (TLinkGridToDataSource) et une liaison plus manuelle (TBindGridLink).
Un TTabControl de quatre panneaux recoupe les différentes parties du tutoriel :
Le premier panneau concerne le TStringGrid, la grille du haut etant liée rapidement via les experts disponibles, la grille du bas étant quant à elle liée manuellement.
Le second panneau concerne le TGrid. Comme le premier panneau, la grille du haut est liée via les experts alors que la seconde est liée « à la main ».
Le troisième panneau permet d'introduire les différentes personnalisations possibles d’une TGrid (couleurs, tailles de colonnes, menus contextuels, etc.).
Le dernier panneau concerne la saisie d'un seul enregistrement de la table :
Chaque grille de l'interface utilisateur, de même que la forme de saisie des données, est traitée dans des cadres (Frames) distincts. Les raisons de ce choix ?
- En premier lieu le concepteur visuel de liaison : tout mettre sur une seule forme aboutit à un affreux salmigondis très difficile à embrasser d'un seul coup d’œil.
- En second argument, chaque cadre ayant son propre TBindingsList il est beaucoup plus simple alors de comparer les différents choix qui sont faits.
- Enfin cela peut permettre de comparer les différents sources .dfm, afin de voir ce qu'il est possible de faire à ce niveau.
Vous trouverez le code source de cet article Ici.
I-C. Démarche du tutoriel▲
Étant donné que les techniques de liaison peuvent s'appliquer aussi bien au TStringGrid qu'au TGrid, à quelques exceptions près ; je n'allais pas répéter à chaque fois les mêmes informations.
J'ai donc préféré parler des liaisons (expresse ou manuelle) puis des améliorations à apporter aux grilles elles-mêmes bien que cela soit imbriqué. Si je devais faire un classement, je hiérarchiserais par raffinement, rendu final et complexité :
- TStringGrid liaison expresse ;
- TGrid liaison expresse ;
- TStringGrid liaison manuelle ;
- TGrid liaison manuelle.
En fait, l'habitude aidant, le TGrid se révèle beaucoup plus avantageux ce qui explique peut-être qu'au fil de la rédaction j'aborde de plus en plus souvent TGrid au détriment de TStringGrid.
II. Approche avec la VCL▲
En guise de mise en bouche, j'ai d'abord voulu voir ce que les LiveBindings pouvaient apporter sur une grille en VCL. Il est à noter que seule la TStringGrid peut être liée.
En haut la classique TDBGrid, en bas une TStringGrid liée avec l'expert sans aucune modification. Les premiers constats figurent dans les encadrés rouges. Le plus déplorable est la perte de l'indicateur de l'enregistrement en cours. En second lieu nous remarquons que les colonnes ont toutes la même taille alors que la TDBGrid prenait en compte la taille des colonnes. Plus en détail, notons le format des colonnes date et heure et les valeurs pour un contenu booléen. Seul point en faveur des LiveBindings, une colonne de type mémo texte affiche une partie du contenu.
J'ai quand même voulu aller plus loin pour voir ce qu'il était possible de faire en modifiant quelque peu une grille liée, et c'est l'objet de la seconde image écran.
En haut, la même grille liée par l'expert (LinkGridToDataSource) mais avec quelques modifications sur les propriétés ParseFormat, en bas une liaison plus manuelle (BindGridLink) qui montre les quelques différences encadrées en rouge.
Croyez-moi, pour la liaison manuelle, le jeu n'en vaut pas la chandelle ! À tel point que je ne vous fournis même pas le source de ce programme.
N.B. L'encadré jaune montre un espace inesthétique sur lequel je reviendrai plus tard IV.B.2.h .
III. StringGrid ou Grid▲
Voici un piège dans lequel je suis tombé à pieds joints ! La VCL ne me proposant de lier que la TStringGrid et beaucoup de vidéos ne faisant les liaisons que sur cette grille, mon réflexe a été de poser d'abord ce composant plutôt qu'un TGrid. Quelques différences par rapport à la VCL sautent immédiatement aux yeux !
Des glyphes, des cases à cocher et des mémos qui semblent extensibles apparaissent, et ça sans une ligne de code !
Je l'avoue, pour les glyphes je triche un peu. Vous verrez dans le prochain chapitre comment j'ai pu afficher ces glyphes alors que la colonne est une donnée numérique.
Quand nous passons à la TGrid, c'est encore plus flagrant car même les photos apparaissent !
L'image est très petite ? C'est dû à la hauteur par défaut des lignes.
Changer la propriété RawHeight de la grille permet d'agrandir automatiquement.
IV. Liaison rapide ou manuelle▲
La liaison rapide est certes beaucoup plus facile à mettre en place que d'utiliser un TBindGridLink. Toutefois, même si cette dernière méthode est plus longue, elle offre des possibilités que la facilité de la liaison rapide occulte, à savoir pouvoir utiliser plusieurs expressions dans une collection.
Collection = Liste d’objets homogènes, dans notre cas, d'expressions à appliquer.
IV-A. Liaison rapide ▲
Que ce soit pour un TStringList ou un TGrid, rien de plus simple pour lier notre source de données à la grille. Par l'intermédiaire de l'expert un simple dialogue de trois pages suffit, la première page permettant même de créer la grille.
La quatrième étape de l'expert est optionnelle mais permet d’ajouter au besoin un navigateur :
Par le concepteur de liaisons visuel, c'est encore plus simple : une manipulation de glisser et déposer entre la grille et la source de données avec la souris !
Pas une ligne de code dans le source, pourtant nos grilles sont prêtes, comme avec la VCL et sa TDBGrid.
Dans le cas où nous posons une grille pour tout de suite la lier à une source de données ouverte,
Attention, notez bien : une source de données ouverte.
toutes les colonnes sont créées et taillées au mieux automatiquement. Ce n'est, ma foi, pas trop mal fait puisque les colonnes ainsi créées détectent le type de données autant que possible. Ainsi les mémos texte ou les images, uniquement dans un TGrid pour ces dernières, vont déjà s'afficher.
Pour rappel aux utilisateurs d'une version starter : vous devez préalablement poser un composant TBindingsList pour pouvoir utiliser l'expert de liaison.
De même, si vous voulez directement passer aux liaisons manuelles vous devez avoir déposé et renseigné un composant TBindScopeDB que l'expert de liaison rapide aurait créé de son propre chef.
Inconvénients des liaisons express, peu de modifications sont possibles et il est impossible d'ajouter des expressions à une collection.
IV-B. Liaison manuelle▲
Pas de secret, pour aborder ce type de procédé de liaison, il faut regarder ce qui se fait avec une liaison rapide puis avancer par touches successives. Il y a très peu de documentation sur ce sujet. Je vous propose, en premier lieu, quelques généralités à mémoriser.
IV-B-1. À mémoriser▲
IV-B-1-a. Le positionnement▲
Le positionnement est le plus important puisqu'il agit sur le déplacement dans la source de données.
Surtout ne pas oublier de renseigner ces deux expressions.
ControlExpression |
SourceExpression |
|
PosControlExpressions |
Selected |
Math_Max(0,DBUtils_ActiveRecord(Self)) |
PosSourceExpressions |
Math_Max(1,Selected+1) |
DBUtils_ValidRecNo(Self) |
IV-B-1-b. Le remplissage des colonnes d'une TStringList▲
Expression de contrôle |
Expression de la source |
|
CellFormat |
Cells[n] où n est le numéro de colonne |
Text |
CellParse |
SelectedText(Self) |
Value |
IV-B-1-c. Le remplissage des colonnes d'une TGrid▲
Expression de contrôle |
Expression de la source |
|
CellFormat |
Data |
Text ou Value |
CellParse |
SelectedText(Self) |
Value |
IV-B-2. En détail▲
Ce serait la simplicité même si les règles exposées ci-dessus s'appliquaient tout le temps et pour toutes les associations colonnes/données. Il y a, hélas, des exceptions dépendant généralement du type de champ à associer mais aussi, bien évidemment, de ce que nous désirons afficher.
IV-B-2-a. Expressions pour un TStringGrid▲
Sauf cas particulier, comme l'affichage d'un glyphe, une cellule de ce type de grille TStringGrid ne peut contenir que du texte. Nous accèdons à la cellule par l'index de colonne Cells[n] et la valeur de la donnée doit être transformée en chaîne, ce qui se traduit, dans l'expression de la source, par Text. Restent les cas particuliers correspondant au type de champ :
- pour un booléen il faut interpréter la valeur, ce qui sera fait par DBUtils_BoolValue(Self) ;
- pour un mémo de type texte il faut transformer le contenu du champ en chaîne par AsString ;
- pour un mémo de type graphique (une image), impossible à afficher dans un TStringGrid, nous utiliserons une constante, par exemple : '(Bitmap)'.
IV-B-2-b. Expressions pour un TGrid▲
Principale différence avec la TStringGrid, et, à mon avis, gros avantage, il n'est plus question de se référer à une cellule indexée avec un TGrid, nous nous contenterons d'indiquer qu'il s'agit d'une donnée (Data).
S’il n’y a pas de différence en ce qui concerne les champs booléens ou mémo texte, ce qui change est bien sûr la possibilité de traiter les mémos graphiques. Comme bien sûr il n'est pas question d'utiliser Text pour y accéder, nous mettrons Self.
IV-B-2-c. L'expression de la source DisplayText▲
Comme le typage de colonne n'a pas encore été vraiment abordé, sauf marginalement pour vous allécher avec les colonnes contenant les glyphes, je peux aborder la notion de DisplayText.
Prenons l'exemple de la colonne contenant une date. Par défaut, la date est affichée selon nos paramètres de poste (DefaultFormatSettings), dans mon cas sous la forme dd/mm/yyyy.
Que faire pour afficher sous une autre forme, par exemple nom du jour abrégé, jour nom du mois abrégé et année sur deux chiffres grâce au formatddd, dd mmm yy ? C'est ce que permet DisplayText dès que le format de la colonne de données est précisé dans la déclaration du champ.
|
Cependant, DisplayText est, selon moi, un faux ami. Effectivement la partie affichage dans la grille se fera selon nos directives, mais l'interprétation du contenu de la cellule (CellParse) ne va pas se faire correctement (à l'exception du format dd/mm/yy).
C'est encore plus flagrant dans le cas d'un champ monétaire que nous voudrions voir affiché avec la devise. Soit, par exemple, une valeur affichée de 120,50 € : pour modifier cette valeur l'utilisateur devra taper 110.25 donc effacer pratiquement toute la cellule, saisir les chiffres et faire attention à bien utiliser le point du clavier (et non le point décimal du pavé numérique, qui sera interprété comme une virgule !) et ce sous peine de se voir infliger une erreur du style : 110,25 n'est pas une valeur numérique !
Il est possible de pallier ces inconvénients en créant ses propres méthodes à ajouter aux moteurs d'expressions (Tutoriel : LiveBindings - l'évaluateur d'expressions).
Vous trouverez un mode opératoire dans l'unité UnitMethodes.pas contenu dans le fichier des sources.
Plus efficace sera de typer les colonnes comme nous le verrons chapitre V.
Mais typer les colonnes réduit à néant l'utilisation de DisplayText.
IV-B-2-d. À propos de SelectedText(Self)▲
Selon la documentation, utilisé dans les expressions permettant d'interpréter les données saisies de la cellule (CellParse),SelectedText(Self), où Self représente l'éditeur utilisé, peut être remplacé, en fonction du type de colonne, par :
SelectedDateTime(éditeur) |
SelectedItem(éditeur) |
SelectedLookupValue(éditeur) |
SelectedValue(éditeur) |
IV-B-2-e. Événements de liaison▲
S'il y a quelque chose de frustrant, c'est bien la colonne « Mode de prise » qui n’apparaît pas de manière lisible par l’utilisateur final. En effet, lors de la conception de la table j'ai préféré utiliser une valeur numérique entière, valeur de l'index dans une liste, plutôt qu'un libellé. Utiliser un événement sur la liaison peut être envisagé pour résoudre ce problème.
Les événements disponibles sont au nombre de cinq : OnActivated, OnActivating, OnAssignedValue, OnAssigniningValue et OnEvalError ; celui qui me semble le plus important est le OnAssigningValue.
Ayant déjà utilisé avec succès OnAssigningValue pour d'autres composants, j'ai pensé m'en servir. Toutefois les deux événements qui nous permettent d'accéder aux valeurs n'ont aucune indication simple sur l'objet concerné (comme l'index de la colonne), juste AssignValueRec qui nous renseigne sur l'expression, la propriété et l'objet.
Première difficulté, le type TGridDataObject n'est pas accessible, il faut le re-déclarer pour pouvoir travailler avec !
type
TGridDataObject = class
private
FColumn: Integer
;
[Weak] FOwner: TCustomGrid;
public
property
Owner: TCustomGrid read
FOwner;
property
Column: Integer
read
FColumn;
end
;
Second problème, l'objet n'est pas toujours le même, il faut donc avant toute chose tester le type d'objet pour pouvoir ensuite le transtyper correctement afin de l'utiliser, ce qui me permet, enfin, d'accéder au numéro de colonne.
procedure
TMainForm.BindGridLink1AssigningValue(Sender: TObject;
AssignValueRec: TBindingAssignValueRec; var
Value: TValue;
var
Handled: Boolean
);
var
AnObject : TObject;
colonne : integer
;
begin
AnObject:=AssignValueRec.OutObj;
if
AnObject.ClassNameIs('TGridDataObject'
) then
begin
colonne:=TGridDataObject(AnObject).Column;
if
colonne=7
then
// colonne 7 correspond à mode de prise
case
Value.AsInteger of
0
: Value := 'Casier'
;
1
: Value := 'Filet'
;
2
: Value := 'Drague'
;
3
: Value := 'A la ligne'
;
4
: Value := 'Pêche à pied'
;
5
: Value := 'Chasse sous-marine'
;
end
;
end
;
Il est donc possible de changer la valeur qui sera affichée. Cependant, changer la valeur affichée ne répondra pas à la question subsidiaire : comment obtenir l'inverse, c'est-à-dire obtenir une valeur par rapport au libellé ?
Cette méthode a quand même un inconvénient : ces valeurs affichées sont des constantes que je retrouverai aussi à d'autres endroits plus tard
soit dans l'utilisation d'un type de colonne TColumnPopup V.B.12, soit en utilisant un TPopupMenu VI.A.
Donc des sources potentielles d’erreurs orthographiques ou d’ordre dans la forme.
IV-B-2-f. L'utilisation de ScopeMappings du TBindSourceDB▲
Avant d'aborder ce que peuvent faire les collections d'expressions FormatControl et ColFormat, il me faut aborder cette notion.
Longtemps je me suis posé la question de savoir à quoi cette propriété pouvait servir. C'est presque par hasard, au cours de l'écriture de l'exemple associé à ce tutoriel, que j'ai découvert une de ses utilisations.
Je n'arrivais pas, dans l'expression de la source, à accéder aux composants visuels de ma forme. Or, j'avais deux objectifs en tête : remplacer la valeur de la colonne ModePrise, un entier, par son intitulé pris dans une liste (TPopupMenu) mais également accéder à la grille pour par exemple pouvoir retailler mes colonnes en largeur, avoir en quelque sorte une colonne « élastique ».
C'est là que la propriété ScopeMappings intervient.
J'ai défini deux observateurs (Scopes) supplémentaires à ce qui permet de lier les données aux composants : le TBindScopeDB.
|
Je peux ainsi accéder à ces deux éléments (ou plutôt à leurs propriétés) dans mes expressions de source, comme nous allons le voir dans les chapitres qui suivent.
IV-B-2-g. Récupération de données d'une liste▲
Comment alors faire en sorte d'afficher un libellé se trouvant, dans ce cas précis et pour des raisons qui seront exposées ultérieurement (voir VI.A), dans un PopupMenu ?
Le fait d'avoir créé un ScopeMappings avec le menu permet de fournir une solution LiveBindings. Il suffit de changer l'expression de la source de la collection CellFormat pour la colonne en Prise.Items[value].text.
Il est aisé de vérifier que cette expression fonctionne en utilisant le bouton « Évaluer la source ».
IV-B-2-h. Les expressions FormatControl et ColFormat▲
Ces deux collections, accessibles uniquement via des liaisons manuelles (TBindGridLink), permettent d'accéder à certaines propriétés des colonnes de la grille comme leur largeur ou leur intitulé.
Comme premier exemple d'utilisation, je choisirais la colonne « élastique ».
Je trouve, en effet, inesthétique la partie droite de la grille, nous retrouvons souvent un espace perdu entre la dernière colonne et la gouttière (encadré jaune).
Maintenant que je peux accéder, grâce aux ScopeMappings, aux propriétés de la grille, je peux très bien recalculer la taille de ma colonne « Circonstances » de façon à ce qu'elle récupère cet espace perdu.
Notez que j'ai utilisé la formule dans FormatControl plutôt que ColFormat. Car FormatControl s'applique à l'entête de colonne (donc une seule fois), alors que ColFormat s'appliquera à chaque cellule de la colonne.
Math_Max(30, // Minimum 30
Grille.Width - // taille de la grille
// - la taille de toutes les autres colonnes
-Grille.Columns[0].Width-Grille.Columns[1].Width-Grille.Columns[2].Width-Grille.Columns[3].Width-Grille.Columns[4].Width-Grille.Columns[5].Width-Grille.Columns[6].Width-Grille.Columns[7].Width-
Grille.Columns[9].Width-Grille.Columns[10].Width
-42 // largeur glissière et lignes verticales)
Pour que cela fonctionne, il me faudra alors faire appel à une fonction de la liaison entre la grille et les données BindGridLink.ResetColumns ; au sein de l’événement OnResize du parent de la grille (généralement une forme ou un cadre).
Encore une fois, il est tout à fait possible de coder dans le source.
Par exemple, le OnResize de la forme pourrait très bien gérer cette colonne « élastique ».
procedure
TForm.FormResize(Sender: TObject);
var
ltc : Single
;
begin
ltc:=Grid1.Width-Grid1.Columns[0
].Width
-Grid1.Columns[1
].Width
-Grid1.Columns[2
].Width-Grid1.Columns[3
].Width
-Grid1.Columns[4
].Width-Grid1.Columns[5
].Width
-Grid1.Columns[6
].Width-Grid1.Columns[7
].Width
-Grid1.Columns[9
].Width-Grid1.Columns[10
].Width-42
;
Grid1.Columns[8
].Width:=Max(30
,ltc);
end
;
À présent voici un autre exemple avec l'utilisation de ColFormat, qui va me permettre également d'introduire la notion d'ajout de méthodes au moteur d'expressions : la colonne taillée au plus juste.
L'objectif est de faire en sorte qu'une colonne (« Nom » dans le programme test) soit de la largeur maximum en fonction des données. Le problème est que, si je peux obtenir la taille en nombre de caractères cela ne me donne pas cette taille en pixels.
Dans mon tutoriel sur l'évaluateur d'expressions, j'ai introduit la possibilité d'ajout de méthodes personnelles (chapitre IV) : c'est ce que je vais mettre en pratique.
Je rajoute cette unité à ma liste des utilisations.
unit
UnitMethodes;
interface
implementation
uses
System.Classes, System.SysUtils, System.Rtti, System.StrUtils,
System.Bindings.EvalProtocol,System.Bindings.Methods, System.Bindings.Consts,
FMX.TextLayout, FMX.PlatForm
, FMX.Graphics;
function
TextSize: IInvokable;
begin
Result := MakeInvokable(function
(Args: TArray<IValue>): IValue
var
v1: IValue;
Ts : Single
;
ATLayout : TTextLayout;
FontService : IInterface;
FontName : String
;
FontSize : Single
;
begin
if
Length(Args) <> 1
then
raise
EEvaluatorError.Create(Format(sUnexpectedArgCount, [1
,Length(Args)]))
else
begin
// tentative de récupération de la fonte par défaut
if
TPlatformServices.Current.SupportsPlatformService(IFMXSystemFontService) then
begin
FontService:=TPlatformServices.Current.GetPlatformService(IFMXSystemFontService);
FontName:=IFMXSystemFontService(FontService).GetDefaultFontFamilyName;
FontSize:=IFMXSystemFontService(FontService).GetDefaultFontSize;
end
else
begin
// fonte par défaut Windows
FontName:='Segoe UI'
;
FontSize:=12
;
end
;
v1:=Args[0
];
ATLayout:=TTextLayoutManager.DefaultTextLayout.Create;
ATLayout.BeginUpdate;
ATlayout.Font.Size:=FontSize;
ATlayout.Font.Family :=FontName;
// 'H' est la lettre la plus large
// contruire la chaine StringofChar de la taille de la valeur
ATlayout.Text:=StringOfChar('H'
,Length(v1.GetValue.asString));
ATLayout.EndUpdate;
Ts:=ATLayout.TextWidth;
Exit(TValueWrapper.Create(Ts));
end
;
end
);
end
;
procedure
EnregistrerMesMethodes;
begin
TBindingMethodsFactory.RegisterMethod(
TMethodDescription.Create(
TextSize,
'TailleTexte'
,
'TailleTexte'
, 'MesMethodes'
, True
,
''
,
nil
));
end
;
procedure
LibererMesMethodes;
begin
TBindingMethodsFactory.UnRegisterMethod('TailleTexte'
);
end
;
initialization
EnregistrerMesMethodes;
finalization
LibererMesMethodes;
end
.
Je peux alors utiliser cette méthode à l'intérieur de mes liens !
Remarquez la taille de la colonne « Nom » encadrée en rouge, qui est réduite. Pourtant, à l'exécution j'obtiendrai des noms complets sans troncatures.
Seul inconvénient à l'ajout de méthodes « maison » : au moment du design les données n'apparaissent plus, la méthode étant inconnue dans l'environnement de développement.
Ce problème est contournable en ajoutant l'expression au début de l'exécution du programme de la manière suivante :
procedure
TForm15.FormCreate(Sender: TObject);
var
AnExpression : TExpressionItem;
begin
// Mémorisation PopupMenu par défaut
TFMXObject(Grid1).TagObject:=Grid1.PopupMenu;
// Ajout de l'expression pour retailler la colonne "Nom"
with
BindGridLink1.ColumnExpressions.Items[0
] do
begin
AnExpression:=FormatColumnExpressions.AddExpression;
AnExpression.ControlExpression:='Columns[0].Width'
;
AnExpression.SourceExpression:='Math_Max(Grille.Columns[0].Width,TailleTexte(Self.asString))'
;
end
;
// Chargement du fichier
cds.LoadFromFile('F:\Livebindings\FMX\DBGrid\Win32\Debug\PecheurMenteur.cds'
);
// Ajout d'un index, qui va mettre en ordre de Date
cds.IndexFieldNames:='DatePrise;Heure'
;
// Ouverture du fichier
cds.Active:=True
;
end
;
L'utilisation de cadres (TFrame) est, dans ce cas de figure, pénalisante car l'événement OnCreate n'existe pas, il faudra substituer cette méthode.
Voir l'unité framegridbis.pas.
V. Typer les colonnes▲
J'ai découvert le truc consistant à typer les colonnes par hasard, il ne me semble pas avoir vu d'informations à ce propos dans l'aide si ce n'est à travers une liste des possibilités Docwiki Embarcadero FMX.Grid.TColumn.
Tout va dépendre si la grille (que ce soit un TGrid ou un TStringGrid) est déjà liée par l'intermédiaire d'un lien rapide ou non.
V-A. À partir d'une liaison rapide▲
Dans le menu contextuel, obtenu par un clic droit sur la grille, peut-être déjà utilisé pour obtenir l'expert de liaison, se trouve une option permettant d'éditer les colonnes.
Pour l'instant non rempli, cet éditeur va permettre de définir les colonnes de la grille.
Avez-vous remarqué le titre de cet éditeur ? Nous sommes sur une modification d'un lien !
Le troisième bouton à partir de la droite permet d'ajouter tous les champs, sélectionner ensuite un élément de cette liste permet de modifier le type de colonne.
Par défaut le type est TColumn pour une TGrid, TStringColumn dans le cas d'un TStringGrid.
Je me suis positionné sur la colonne « Catégorie » et vais choisir le type GlyphColumn.
En dehors de la valeur qui disparaît, rien ne se passe ; pour obtenir le glyphe, il ne faut pas oublier de renseigner la propriété Images de la grille.
J'avais, auparavant, ajouté un TImageList à ma forme.
Une fois la propriété Images renseignée, le glyphe apparaît bien en lieu et place des valeurs.
V-B. Les différents types de colonne▲
Si la plupart des types n'ont pas besoin de précision particulière, il y a pour chacun d'eux des pour et des contre. Il faut considérer que typer une colonne permet dans la plupart des cas de lui associer un éditeur ou un comportement particulier. Je ne vais pas faire une liste exhaustive de toutes les propriétés de chaque type de colonne, mais plutôt indiquer les quelques propriétés intéressantes selon le cas. J'en profiterai également pour donner mon ressenti pour chacune.
V-B-1. TColumn▲
TColumn est La version colonne fourre-tout pour TGrid. Non liée, c'est par défaut une colonne de type chaîne de caractères.
Pour |
Contre |
Type par défaut, pratique lors des liaisons rapides. |
Colonne qui distingue les différents types de données mais applique des formats « standard », par exemple un réel sera affiché avec deux chiffres après la virgule. |
V-B-2. TStringColumn▲
Colonne de base pour TStringGrid, comme pour TColumn, il n'y a pas grand-chose à dire de TStringColumn si ce n'est qu’elle présente les mêmes avantages et inconvénients que sa consœur.
V-B-3. TCheckColumn▲
Colonne case à cocher, TCheckColumn est, par conséquant, à lier avec une valeur booléenne. Comme certaines bases de données (par exemple les versions de Firebird inférieures à 3) ne connaissent pas ce type de colonne, il faudra jouer sur la propriété ParseFormat de la liaison pour obtenir un bon comportement.
Pour |
Contre |
Le fait que ce soit une case à cocher. |
Outre le problème du traitement au niveau des liaisons, la case à cocher traite trois états : vrai, faux mais aussi non renseigné (null) - dans ce cas la case n'apparaît pas, ce qui peut être un peu perturbant ! |
V-B-4. TDateColumn▲
TDateColumn est une colonne permettant de saisir des dates.
|
Pour |
Contre |
Bien évidemment l'éditeur mais aussi la possibilité de formater la date. |
J'ai un avis mitigé sur la propriété ShowClearButton(1). |
V-B-5. THourColumn▲
THourColumn est une colonne permettant de saisir des heures.
Pour |
Contre |
Comme pour TDateColumn l'éditeur et la propriété Format. |
La propriété ShowClearButton. |
V-B-6. TGlyphColumn▲
TGlyphColumn permet d'afficher un glyphe contenu dans une liste d'images selon la valeur (index dans la liste).
Pour |
Contre |
L'affichage d'une image en fonction de la valeur. |
La colonne ne peut être qu'en lecture seule. |
Il est vraiment regrettable que la liste d'images soit liée à la grille et non une propriété de la colonne ! S'il devait y avoir plus d'une colonne glyphe il faudrait, soit revoir les valeurs possibles dans la base de données, soit jouer avec les valeurs pour obtenir les bons index.
Pour pallier le problème de la lecture seule, nous pouvons nous rabattre sur l'utilisation d'un menu contextuel (voir VI.A).
V-B-7. TFloatColumn▲
TFloatColumn est une colonne permettant la saisie de nombres.
Pour |
Contre |
Outre le fait que l'éditeur ne permette que la |
V-B-8. TCurrencyColumn▲
TCurrencyColumn permet la saisie de nombres représentant des valeurs monétaires.
Pour |
Contre |
Comme pour TFloatColumn |
On ne voit pas quelle est la différence avec TFloatColumn si ce n'est que le séparateur de milliers, activé par défaut. |
V-B-9. TInteger▲
TInteger permet de saisir un entier.
Pour |
Contre |
L'éditeur. |
V-B-10. TImageColumn▲
TImageColumn permet l’affichage d'images donc uniquement pour TGrid.
Pour |
Contre |
Une image dans une grille sans difficulté. |
Uniquement pour TGrid. |
V-B-11. TProgressColumn▲
Absent de la version Berlin, TProgressColumn est le seul type de colonne que je n'ai pas réussi à intégrer dans mon application, faute d'imagination. Les propriétés qu'il faut utiliser : Min et Max.
V-B-12. TPopupColumn▲
En théorie, le type de colonne TPopuColumn semble pratique pour tout ce qui ressemble à des listes prédéfinies.
Pour |
Contre |
Sélection dans une liste. |
Il ne semble pas possible de l'appliquer pour des couples valeur/intitulé ou index/intitulé. |
|
Je n'ai pas réussi à lier un index de liste à la cellule sauf en passant par des événements et encore uniquement pour la partie affichage, donc pour l'équivalent d'une colonne en lecture seule !
Vous préférerez utiliser un menu contextuel sur la colonne, comme indiqué au paragraphe VI.A, qui permettra de modifier facilement les données.
V-B-13. Ajout de ses propres types▲
Oui, vous avez bien lu, nous pouvons ajouter nos propres types de colonnes. Vous trouverez à cette adresse un exemple édifiant quoiqu'un peu daté (version XE3) des possibilités (source disponible ici).
La partie qui suit est un résumé et une traduction rapide de l'article que vous pourrez trouver à cette adresse.
Penchons-nous un peu sur ce qu'est un TColumn d'une TGrid. Une colonne contient des cellules faites avec un descendant quelconque d'un contrôle stylé, soit pratiquement tout contrôle visuel ! Il est donc relativement simple de créer nos propres types de colonnes. Prenons par exemple une boîte de sélection de couleur (TColorComboBox).
La première chose à faire est de déclarer une nouvelle classe pour notre cellule.
type
TColorComboCell = class
(TColorComboBox)
end
;
Puis il nous faut une classe pour notre colonne, mais aussi une fonction qui va changer la création par défaut de la cellule :
type
TColorComboColumn = class
(TColumn)
protected
function
CreateCellControl : TStyledControl ; override
;
end
;
implementation
function
TColorComboColumn.CreateCellControl:TStyledControl ;
begin
Result:=TColorComboCell.Create(Self
) ;
TColorComboBox(Result).onChange:=DoTextChanged ;
end
;
Une fois nos classes créées, il est alors aisé d'ajouter ce nouveau type de colonne à un TGrid.
Grid1.AddObject(TColorComboColumn.Create(Grid1)) ;
Il faudra alors ensuite établir le lien avec nos données comme j'ai pu le montrer au chapitre IV.B.2.h.
Donc, en théorie, il est possible de créer n'importe quel type de colonne mais il y a plusieurs bémols : il faut créer la colonne à la création du programme et, subséquemment, édifier la liaison par la suite.
À moins que vous ne trouviez l'astuce pour ajouter ce type de colonne personnalisées à l'IDEInterface de développement de Delphi, les colonnes personalisées ne sont donc pas aussi pratiques que celles qui nous sont proposées.
VI. Les événements pour aller plus loin▲
Il est temps de repousser encore les limites du TGrid en s'attaquant à la présentation des cellules. Pas de scoop, les Livebindings ne peuvent pas tout faire, impossible par exemple d'accéder au canvas par leur intermédiaire pour pouvoir « coloriser » nos grilles. La seule réponse à certains besoins restera bien le code par l'intermédiaire des événements des grilles.
VI-A. Un menu contextuel selon la colonne▲
Tout composant visuel ou presque a une propriété PopupMenu, permettant d'afficher un menu contextuel en utilisant un clic droit. Les deux composants grilles n'échappent pas à cette règle mais aussi les colonnes de ces dernières.
Chaque colonne de la grille peut donc avoir son propre menu contextuel (propriété PopupMenu). Toutefois celui-ci ne s'affiche pas automatiquement, seul celui lié à la grille fonctionne. Bogue ou fonction non implémentée ? Je penche plus vers la seconde hypothèse.
Comment faire alors ? Plusieurs solutions semblent possibles. L'utilisation de l'événement OnCellClick de la grille permet d'accéder facilement à l'objet TColumn, le OnSelectCell à l'index de la colonne.
Reste à résoudre la gestion du menu contextuel par défaut, celui de la grille, que j'ai besoin de mémoriser. Pour ce faire, j'utilise une spécificité des objets FMX, la propriété TagObject, qui sera renseignée à la création de la forme.
procedure
TMainForm.FormCreate(Sender: TObject);
begin
TFMXObject(Grid1).TagObject:=Grid1.PopupMenu; // mémoriser popupmenu par défaut
…
end
;
procedure
TMainForm.Grid1CellClick(const
Column: TColumn; const
Row: Integer
);
begin
if
Column.PopupMenu=null
then
// menu par défaut mémorisé
Grid1.PopupMenu:=TPopupMenu(TFMXObject(Grid).TagObject)
else
Grid1.PopupMenu:=Column.PopupMenu; // menu de la colonne
inherited
;
end
;
L'astuce est d'utiliser inherited permettant ainsi l'exécution de la méthode héritée et d'obtenir l'affichage du bon menu contextuel.
Cependant, je trouve que l'utilisation de l’événement OnSelectCell a un rendu plus fluide.
procedure
TGridLink.Grid1SelectCell(Sender: TObject; const
ACol, ARow: Integer
;
var
CanSelect: Boolean
);
begin
if
Assigned(Grid1.Columns[ACol].PopupMenu)
then
Grid1.PopupMenu:=Grid1.Columns[ACol].PopupMenu
else
Grid1.PopupMenu:=TPopupMenu(TFMXObject(Grid).TagObject);
end
;
VI-B. Mettre de la couleur▲
L’événement OnDrawColumnCell, bien connu de ceux qui utilisent la VCL, permet d'accéder au Canvas, au rectangle de dessin TRectF, à la colonne, à la ligne, etc.
Il est donc assez aisé de changer la couleur de fond d'une cellule ou la couleur du texte selon une condition.
VI-B-1. Premier exemple : mettre la colonne « Nom » en couleur▲
Une colonne peut être connue par son index (Column.Index) mais c'est une fausse bonne idée car les colonnes peuvent être déplaçables. Il vaut mieux les nommer de façon correcte et tester sur leur nom (Column.Name).
procedure
TForm15.Grid1DrawColumnCell(Sender: TObject; const
Canvas: TCanvas;
const
Column: TColumn; const
Bounds: TRectF; const
Row: Integer
;
const
Value: TValue; const
State: TGridDrawStates);
var
ARect : TRectF;
ABrush : TBrush;
begin
if
SameText(Column.Name,'Nom'
) then
begin
// initialiser une nouvelle brosse
ABrush:=TBrush.Create(TBrushKind.Solid,TAlphaColors.MoneyGreen);
try
// agrandir le rectangle initial
ARect:=TRectF.Create(Bounds);
InflateRect(ARect,3
,3
);
// remplir le rectangle
Canvas.FillRect(ARect,0
,0
,[],1
,ABrush);
finally
ABrush.Free;
end
;
// réécrire le texte
Canvas.Fill.Color:=TAlphaColors.Black;
Canvas.FillText(Bounds,Value.ToString,false
,1
,[],TTextAlign.Leading);
end
;
end
;
VI-B-2. Deuxième exemple : mettre en couleur les poissons « nobles » (Bar, Sole)▲
procedure
TForm15.Grid1DrawColumnCell(Sender: TObject; const
Canvas: TCanvas;
const
Column: TColumn; const
Bounds: TRectF; const
Row: Integer
;
const
Value: TValue; const
State: TGridDrawStates);
var
ARect : TRectF;
ABrush : TBrush;
begin
if
SameText(Column.Name,'Nom'
) then
begin
// initialiser une nouvelle brosse
ABrush:=TBrush.Create(TBrushKind.Solid,TAlphaColors.MoneyGreen);
try
// tester les poissons nobles bar ou sole et metter en rose)
if
(Value.ToString='Bar'
) OR
(Value.ToString='Sole'
)
then
ABrush.Color:=TAlphaColors.Pink;
// agrandir le rectangle initial
ARect:=TRectF.Create(Bounds);
InflateRect(ARect,3
,3
);
// remplir le rectangle
Canvas.FillRect(ARect,0
,0
,[],1
,ABrush);
finally
ABrush.Free;
end
;
// réécrire le texte
Canvas.Fill.Color:=TAlphaColors.Black;
Canvas.FillText(Bounds,Value.ToString,false
,1
,[],TTextAlign.Leading);
end
;
end
;
VI-B-3. Derniers retranchements : mettre en couleur selon la valeur d'une autre colonne▲
Ce cas va mettre en exergue quelque chose d'inattendu : TGrid ne stocke pas les valeurs des données mais plutôt un affichage des données !
Comment faire alors pour récupérer la valeur d'une cellule au sein d'une TGrid qui utilise les LiveBindings ? Par exemple, comment mettre la cellule de la colonne « Nom » en orange s'il s'agit de crustacés, soit la catégorie de valeur 2 ?
Je n'ai pu trouver aucun moyen d'accéder à cette valeur via les LiveBindings, il n'est pas possible non plus d'utiliser la source de données, contrairement au TDBGrid de la VCL. Les seules informations trouvée sur Internet n'abordent pas ce cas. Une seule piste est fournie dans la documentation : utiliser l'événement OnSetValue et un tableau dynamique de valeurs (Array of TValue). Sauf que si nous utilisons les LiveBindings l'événement est inaccessible !
Voilà comment résoudre le problème :
-
Tout d'abord ajouter sur la fiche un TStringGrid, qui sera invisible à l'affichage.
- Lier le TStringGrid car, contrairement au TGrid, TStringGrid mémorise les valeurs des cellules. Au niveau de l'ordre, mettre la liaison avec la TStringGrid avant celle de la TGrid.
Il n'est pas nécessaire de lier toutes les données, juste celles nécessaires aux tests.
- Il ne restera alors plus qu'à tester la valeur de la cellule de la TStringGrid en fonction de la ligne.
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.
procedure
TForm15.Grid1DrawColumnCell(Sender: TObject; const
Canvas: TCanvas;
const
Column: TColumn; const
Bounds: TRectF; const
Row: Integer
;
const
Value: TValue; const
State: TGridDrawStates);
var
ARect : TRectF;
ABrush : TBrush;
begin
if
SameText(Column.Index
,'Nom'
) then
begin
// initialiser une nouvelle brosse
ABrush:=TBrush.Create(TBrushKind.Solid,TAlphaColors.MoneyGreen);
try
// tester aussi sur la valeur (poissons nobles bar ou sole en rose)
if
(Value.ToString='Bar'
) OR
(Value.ToString='Sole'
)
then
ABrush.Color:=TAlphaColors.Pink;
// tester en fonction d'une autre cellule
if
StrToInt(StringGrid1.Cells[0
,Row])=2
then
ABrush.Color:=TAlphaColors.Orangered;
// agrandir le rectangle initial
ARect:=TRectF.Create(Bounds);
InflateRect(ARect,3
,3
);
// remplir le rectangle
Canvas.FillRect(ARect,0
,0
,[],1
,ABrush);
finally
ABrush.Free;
end
;
// réécrire le texte
Canvas.Fill.Color:=TAlphaColors.Black;
Canvas.FillText(Bounds,Value.ToString,false
,1
,[],TTextAlign.Leading);
end
;
end
;
J'obtiens alors le résultat escompté :
VII. Astuces, le coin du mécano▲
Au cours de l'élaboration de l'article et du programme exemple associé, j'ai pu découvrir quelques astuces que j'aimerais vous faire partager.
Ces astuces ne se retrouvent pas dans les sources proposées en téléchargement.
VII-A. Hauteur de l'en-tête de grille▲
Au préalable il faut déclarer l'utilisation de l'unité FMX.Header dans la liste des uses. Il est alors possible de récupérer dans le style ce qui concerne les en-têtes de grille.
uses
FMX.Header;
var
Header : THeader;
Header := THeader(Grid1.FindStyleResource('header'
));
if
Assigned(Header) then
Header.Height := 48
;
// simplifiable en (mais moins sûre)
// THeader(Grid1.FindStyleResource('header')).Height:=48;
VII-B. Une colonne indicateur d'enregistrement▲
Je déplorais, au début du tutoriel, la disparition du curseur d'enregistrement que la TDBGrid de la VCL nous proposait. Mes tentatives en ce sens via Livebindings se sont toutes révélées infructueuses. Cependant il est tout à fait possible de le faire par code.
Au préalable j'ajoute une colonne que je nomme Curseur, qui ne sera pas liée aux données.
|
Ensuite il faut également implémenter l'événement OnAfterScroll de la source de données.
procedure
TForm1.CdsAfterScroll(DataSet: TDataSet);
begin
Curseur.Changed; // force le dessin de la colonne
end
;
Reste alors à dessiner le triangle au cours de l'événement OnCellDraw de la grille.
VII-B-1. Dessin d'un triangle dans une cellule▲
Pour dessiner un triangle dans une cellule j'ai décidé d'écrire une fonction qui me permet non seulement de dessiner un triangle selon une couleur définie mais qui tient également compte de la taille de la cellule et de la direction de pointage.
procedure
DrawTriangle(Bounds : TRectF; Color : TAlphaColor;
PointeaDroite : Boolean
=True
;
Agrandir : Boolean
=False
);
var
Points: array
[0
..2
] of
TPointF;
Demi : Single
;
APath : TPath;
begin
if
Agrandir then
Demi:=Bounds.Height/2
else
Demi:=Min(Column.Width,Bounds.Height)/2
;
if
PointeADroite then
begin
Points[0
] := PointF(Bounds.Left,Bounds.Top+Bounds.Height/2
-Demi);
Points[1
] := PointF(Bounds.Left+Bounds.Width,Bounds.Top+(Bounds.Height/2
));
Points[2
] := PointF(Bounds.Left,Bounds.Top+(Bounds.Height/2
)+Demi);
end
else
begin
Points[0
] := PointF(Bounds.Left,Bounds.Top+Bounds.Height/2
);
Points[1
] := PointF(Bounds.Left+Bounds.Width,Bounds.Top+(Bounds.Height/2
)-Demi);
Points[2
] := PointF(Bounds.Left+Bounds.Width,Bounds.Top+(Bounds.Height/2
)+Demi);
end
;
ABrush:=TBrush.Create(TBrushkind.Solid,Color);
APath:=TPath.Create(Self
);
try
APath.Data.MoveTo(Points[0
]);
APath.Data.LineTo(Points[1
]);
APath.Data.LineTo(Points[2
]);
APath.Data.ClosePath;
Canvas.FillPath(APath.Data,1
,ABrush);
finally
FreeAndNil(ABrush);
FreeAndNil(APath);
end
;
end
;
Cela me permet d'obtenir ces différentes possibilités :
D’autres améliorations sont envisageables pour cette fonction comme la possibilité d’indiquer l’ordre de tri dans les entêtes de colonne.
VII-B-2. Récupérer les couleurs à utiliser▲
Utiliser FMX implique souvent les styles. Il fallait donc que ma colonne remplisse également le critère de couleur, à savoir : utiliser les mêmes couleurs de fond et de texte (couleur du triangle) que les en-têtes de la grille. Une fonction supplémentaire va me permettre d'obtenir ces indications. Cette fonction me permettra d'obtenir une brosse en cas d'utilisation d'un gradient de remplissage (la plupart des styles proposés de base par Embarcadero en utilisent).
Le cas des styles de grille est assez complexe car le style des en-têtes (header) renvoie à un autre style (headeritemstyle).
|
Il faut donc accéder à un élément de l'en-tête (ItemHeader) avant de pouvoir accéder aux éléments background et text.
function
GetHeaderColors(Sender: TObject; var
BackGroundColor,TextColor : TAlphaColor) : TBrush;
var
Header : THeader;
HeaderItem : THeaderItem;
aFMXObj : TFMXObject;
begin
// valeurs par défaut
BackGroundColor:=0
;
TextColor:=TAlphaColors.Black;
// recherche dans les styles
Header:=THeader((Sender as
TGrid).FindStyleResource('header'
));
if
Assigned(Header) then
begin
// obtention d'un élément
HeaderItem := Header.Items[1
];
// accès au style du texte
AFMXObj:=THeaderItem(HeaderItem).FindStyleResource('Text'
);
TextColor:=TText(AFMXObj).TextSettings.FontColor;
// accès au style du fond
AFMXObj:=THeaderItem(HeaderItem).FindStyleResource('background'
);
Result:=TRectangle(AFMXObj).Fill;
if
Assigned(result) then
begin
if
(Result.Kind=TBrushKind.Gradient)
then
begin
BackGroundColor:=Result.Gradient.InterpolateColor(50
);
// de horizontal à vertical ? (Pas concluant)
Result.Gradient.StartPosition.X:=1
;
Result.Gradient.StartPosition.Y:=1
;
end
else
BackGroundColor:=Result.Color;
end
;
end
;
end
;
J'utilise alors ces deux fonctions ainsi
procedure
TForm15.Grid1DrawColumnCell(Sender: TObject; const
Canvas: TCanvas;
const
Column: TColumn; const
Bounds: TRectF; const
Row: Integer
;
const
Value: TValue; const
State: TGridDrawStates);
var
ARect : TRectF;
ABrush : TBrush;
ABackColor,ATextColor : TAlphaColor;
begin
...
if
SameText(Column.Name,'Curseur'
) then
begin
// Obtention des couleurs
ABrush:=GetHeaderColors(Sender,ABackColor,ATextColor);
// redessiner le fond
ARect:=TRectF.Create(Bounds);
InflateRect(ARect,3
,3
);
Canvas.ClearRect(ARect,ABackColor);
if
Assigned(ABrush) then
Canvas.FillRect(ARect,0
,0
,[],1
,ABrush)
else
Canvas.FillRect(ARect,0
,0
,[],1
);
// si enregistrement en cours
if
Row=cds.RecNo-1
then
// dessin du curseur d'enregistrement
DrawTriangle(Bounds,ATextColor);
end
;
end
;
Pour obtenir cet écran :
VII-C. Remplacer FillText, les possibilités de TTextLayout▲
En recherchant dans les différents sources de l'unité FMX.Graphics quel ne fut pas mon étonnement de découvrir, en commentaire, que FillText était considérée comme dépréciée et qu'il fallait se tourner vers un TTextLayout.
{ deprecated, use TTextLayout }
procedure
FillText(const
ARect: TRectF; const
AText: string
;
const
WordWrap: Boolean
; const
AOpacity: Single
;
const
Flags: TFillTextFlags; const
ATextAlign: TTextAlign;
const
AVTextAlign: TTextAlign = TTextAlign.Center); virtual
;
Il ne m'en fallait pas plus pour ronger ce nouvel os, surtout qu'aucune indication supplémentaire ne semblait se trouver dans la documentation.
Pour pouvoir utiliser, comme recommandé, un TTextLayout, la première étape est d'inclure l'unité FMX.TextLayout dans la liste des uses.
Si j'applique ici mes recherches aux cellules d'une grille, ces essais sont valables pour tout composant visuel permettant d'accéder à un canvas.
Remplacer le FillText utilisé par exemple dans les codes source du chapitre VI.B, soit en tout deux malheureuses lignes,
Canvas.Fill.Color:=TAlphaColors.Black;
Canvas.FillText(Bounds,Value.ToString,false
,1
,[],TTextAlign.Leading);
revient à déclarer en premier lieu une variable TTextLayout puis à écrire les lignes suivantes :
var
AtextLayout : TTextLayout;
ATextLayout:=TTextLayoutManager.TextLayoutByCanvas(Canvas.ClassType).Create(Canvas);
ATextLayout.BeginUpdate;
ATextLayout.Text:=Value.ToString;
ATextLayout.Color:=TAlphaColors.Black;
ATextLayout.TopLeft:=Bounds.TopLeft;
ATextLayout.MaxSize := PointF(Bounds.Width, Bounds.Height);
ATextLayout.VerticalAlign:=TTextAlign.Center;
ATextLayout.EndUpdate;
ATextLayout.RenderLayout(Canvas);
Beaucoup de lignes pour le même résultat, l'avantage est bien caché ! Il ne se trouve que dans l'abondance de propriétés d'un TTextLayout, en particulier Font et Color.
Rassurez-vous, la fonction FillText est dépréciée mais cela ne l'empêche nullement d'être utilisable et ce sans erreur de compilation.
VII-C-1. Grille avec des valeurs de cellules en langues RTL▲
Une des propriétés de TTextLayout m'a induit en erreur : RightToLeft. Je pensais pouvoir l'utiliser pour écrire de l'arabe ou de l'hébreu simplement en mettant cette propriété à True.
En fait, c'est un peu plus complexe que cela. À ma connaissance FMX ne prend pas nativement en charge ces langues si bien que quelqu'un a développé un ensemble de composants dans ce but : FMXRTL. Loin de ma pensée l'idée de concurrencer cet auteur, je voulais juste utiliser cette propriété pour voir.
|
Que peut-on constater ? Utiliser la propriété RigthtoLeft ne suffit pas, car cette dernière ne fait qu'écraser l'alignement que l'on a pu indiquer, la langue utilisée pour le contenu de la cellule n'est pas détectée. Pour obtenir quelque chose de conforme à mon attente, j'ai créé une fonction permettant de tester le premier caractère de la chaîne (en prenant toutefois soin de ne pas tenir compte des chiffres et de quelques signes) :
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.
function
checkRtl (S : string
; Exceptions : String
= ''
) : TTextAlign;
var
carray : array
of
WideChar;
i : int64;
ws : String
;
begin
for
I := 0
to
9
do
S:=StringReplace(S,i.ToString,''
,[rfReplaceAll]);
// supprime autres caractères spéciaux
S:=StringReplace(S,'('
,''
,[rfReplaceAll]);
S:=StringReplace(S,')'
,''
,[rfReplaceAll]);
S:=StringReplace(S,'"'
,''
,[rfReplaceAll]);
S:=StringReplace(S,''''
,''
,[rfReplaceAll]);
S:=StringReplace(S,'-'
,''
,[rfReplaceAll]);
if
not
E.IsEmpty then
begin
for
I := 1
to
Length(Exceptions) do
S:=StringReplace(S,Exceptions[i],''
,[rfReplaceAll]);
end
;
S:=Trim(S);
// arabe + hébreux je n'ai pas inclus le farsi
SetLength(carray,$6ff
-$590
);
for
I := $590
to
$6ff
do
carray[i-$590
]:=Char
(I);
result:=TTextAlign.Trailing;
if
S.IsEmpty then
exit;
if
inOpArray(S[1
],carray) then
result:=TTextAlign.Leading;
end
;
Vous retrouverez le source au sein de cette discussion1.
VII-C-2. Mixer fontes et couleurs au sein d'un texte▲
L'exemple avec la propriété RightToLeft est peu représentatif des possibilités de TTextLayout. Il m'est donc venu en tête un exemple plus démonstratif.
ATextLayout:=TTextLayoutManager.TextLayoutByCanvas(Canvas.ClassType).Create(Canvas);
ATextLayout.BeginUpdate;
ATextLayout.Text:=Value.ToString;
ATextLayout.Color:=TAlphaColors.Black;
ATextLayout.TopLeft:=Bounds.TopLeft;
ATextLayout.MaxSize := PointF(Bounds.Width, Bounds.Height);
ATextLayout.VerticalAlign:=TTextAlign.Center;
// Changement de Fonte
AFont:=TFont.Create;
AFont.Family:='Algerian'
;
AFont.Size:=24
;
// 1° caractère Fonte Algerian, taille 24, en vert ATextLayout.AddAttribute(TTextRange.Create(0,1),TTextAttribute.Create(AFont,TAlphaColors.Green));
// les deux prochains caractères en rouge ATextLayout.AddAttribute(TTextRange.Create(1,3),TTextAttribute.Create(Grid1.TextSettings.Font,TAlphaColors.Red));
ATextLayout.HorizontalAlign:=TTextAlign.Leading;
ATextLayout.EndUpdate;
ATextLayout.RenderLayout(Canvas);
Le code ci-dessus me permet d'obtenir :
|
VIII. Conclusions▲
Au fil de ces pages, j'espère avoir démontré que l'utilisation des LiveBindings avec les grilles, qui de prime abord se révèle assez étrange, peut se révéler relativement facile dès que l'on acquiert certaines notions :
- La différence entre StringGrid et Grid.
- Les tenants et aboutissants de l’utilisation des liaisons manuelles plutôt que rapide.
- L’utilisation de colonnes typées.
Mes premiers remerciements vont aux intervenants du forum, et en particulier à ShaiLeTroll, qui ont pu répondre à certains de mes questionnements. Surtout que, les pauvres, n'avaient, lors de mes questions, qu'une toute petite fenêtre ouverte sur l'objectif final. C'est également grâce à leurs réponses que j'ai pu me faire une idée plus précise de la différence de raisonnement qu'il fallait opérer entre la vision VCL « classique » et celle des LiveBindings.
Mes remerciements vont ensuite à ceux sans qui ce tutoriel ne resterait qu'un brouillon, la « maison d'édition » du club, c'est-à-dire les correcteurs techniques Jean-Luc (Alcatîz) et Gilles (gvasseur58) puis orthographiques et grammaticaux Gérard (F6EEQ) et son mentor Fabien (f-leb).