FMX et les composants d'accès aux bases de données

Utilisation des LiveBindings

L'aspect le plus déstabilisant lorsque l'on aborde la création d'une application multiplate-forme utilisant des bases de données est l'absence des composants de contrôle de données bien connus des programmes VCL. Dans cet article, je propose de faire le tour de ces composants VCL pour trouver leurs équivalents FMX, s'ils existent !

Bien évidemment, la réponse « primaire » à ce problème est simple : il faut utiliser les LiveBindings. Plusieurs tutoriels, à commencer par les tutoriels d'Embarcadero, et plusieurs vidéos sur le Net montrent comme il est aisé de le faire, alors pourquoi m'embarquer dans un tel sujet ?

J'en vois deux raisons principales :

  • les démonstrations se font presque toutes en utilisant le concepteur de liaison visuelle ;
  • ces démonstrations ne présentent que les composants les plus évidents : libellés, zones d'édition, mémos ou contrôles image. Toutefois, elles laissent de côté d'autres composants bien utiles, mais d'utilisation plus complexe, comme les boîtes de choix.

Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. État des lieux

Lors de mes précédents tutoriels, j'ai déjà introduit quelques types de liaison (en particulier lors de l'épisode précédent). Encore une fois, je le répète, il est possible d'utiliser le codage pour remplir l'interface utilisateur, mais si pour un simple libellé une ligne de code suffit, pour une zone de saisie nous passerions déjà à deux lignes au minimum. Je laisse imaginer ce qu'il faudrait pour remplir une grille ou une vue liste (TListView) !

I-A. Composants de contrôle de données

La VCL propose quatorze composants de contrôle de données listés dans le tableau ci-dessous :

Composant VCL

Équivalent FMX

DBNavigator

BindNavigator

DBText

Label

DBEdit

Edit

DBMemo

Memo

DBImage

Image

DBGrid

Grid,StringGrid

DBCheckBox

CheckBox

DBListBox

Listbox, ListView

DBLookupListBox

ListBox

DBComboBox

Combobox, ComboEdit

DBLookupComboBox

Combobox, ComboEdit

DBRadioGroup

Pas d'ensemble de boutons radio

DBCtrlGrid

Pas d'équivalence

DBRichEdit

Pas de richview

D'expérience, la plupart des besoins d'un programme VCL d'informatique de gestion sont couverts par ces derniers. Toutefois, les interfaces utilisateur ont beaucoup évolué et les nouveaux composants tels que TListView n'ont aucun pendant lié aux données (par exemple un TDBListView). Les LiveBindings nous ouvrent heureusement de toutes nouvelles perspectives.

I-B. La base de données

J'aurais pu utiliser les bases de données fournies par Embarcadero pour son incontournable démonstration FishFactory, mais elles ne correspondent pas totalement à mes futurs besoins de démonstration. J'ai donc choisi une base exemple SQLite dont vous pourrez trouver la structure à cette adresse. À cette base, il manquait une colonne ou une table contenant des images et des descriptions longues (mémo texte). J'ai donc ajouté deux tables, pour ne pas changer la structure des tables existantes, dont voici les structures :

table Pochettes
Sélectionnez
CREATE TABLE Pochettes (
idPochette INTEGER PRIMARY KEY AUTOINCREMENT,
idAlbum INTEGER NOT NULL
REFERENCES albums (AlbumId),
Pochette BLOB
);

CREATE TABLE Paroles (
idParoles INTEGER PRIMARY KEY AUTOINCREMENT,
idTracks INTEGER REFERENCES tracks (TrackId),
extraits BLOB
);

Me pardonnerez-vous de n'avoir inclus dans la base de données, fournie ici, que les dix premières pochettes et seulement les cinq premiers extraits de chansons ?

I-C. Accès aux données

Ce paragraphe s'adresse plus particulièrement aux possesseurs d'une version starter ou professionnelle de Delphi. La version starter ne propose que le composant TClientDataSet comme accès aux données (donc des fichiers au format .xml ou .cds). Pour ceux de la version professionnelle sans le pack Firedac, je dois avouer ne pas trop savoir à quels composants de base de données ils ont réellement accès.

Toutefois, vous avez la possibilité d'ajouter (testé avec la version Tokyo Starter 10.2) des composants tiers comme ceux de la suite ZEOSLib et d'accéder ainsi au monde des SGBDRSystème de Gestion de Bases de Données Relationnelles .

Image non disponible

Pour étudier l'exemple proposé en téléchargement, vous devrez donc installer la bibliothèque ZeosLib.

II. Premières expérimentations

Le sous-titre de ce chapitre aurait pu être : comment en arriver là ?

Image non disponible

Et ce, sans une ligne de code ou presque !

 
Sélectionnez
unit UniteBase;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  Data.Bind.Controls, System.Rtti, FMX.Grid.Style, FMX.ScrollBox, FMX.Grid,
  FMX.Objects, FMX.Layouts, Fmx.Bind.Navigator, FMX.Edit,
  FMX.Controls.Presentation, FMX.StdCtrls, Data.DB, ZAbstractRODataset,
  ZAbstractDataset, ZAbstractTable, ZDataset, ZAbstractConnection, ZConnection,
  Data.Bind.EngExt, Fmx.Bind.DBEngExt, Data.Bind.Components, Data.Bind.DBScope,
  Fmx.Bind.Grid, System.Bindings.Outputs, Fmx.Bind.Editors, Data.Bind.Grid,
  FMX.Memo;

type
  TForm3 = class(TForm)
    ChinookDB: TZConnection;
    Label1: TLabel;
    Edit1: TEdit;
    ListeLiens: TBindingsList;
    Chansons: TZQuery;
    LiaisonsDB: TBindSourceDB;
    NavigatorBindSourceDB12: TBindNavigator;
    ImageControlPochette: TImageControl;
    LinkControlToField2: TLinkControlToField;
    Ouvrir: TButton;
    Memo1: TMemo;
    StringGridLiaisonAlbums: TStringGrid;
    LinkGridToDataSource1: TLinkGridToDataSource;
    LinkControlToField1: TLinkControlToField;
    Edit2: TEdit;
    Label2: TLabel;
    LinkControlToField3: TLinkControlToField;
    LinkControlToField4: TLinkControlToField;
    ChansonsName: TWideStringField;
    ChansonsTitle: TWideStringField;
    ChansonsPochette: TBlobField;
    ChansonsExtrait: TWideMemoField;
    CheckBox1: TCheckBox;
    ChansonsMediaTypeId: TLargeintField;
    LinkControlToField5: TLinkControlToField;
    BindExpression1: TBindExpression;
    procedure OuvrirClick(Sender: TObject);
  private
    { Déclarations privées }
  public
    { Déclarations publiques }
  end;

var
  Form3: TForm3;

implementation

{$R *.fmx}

procedure TForm3.OuvrirClick(Sender: TObject);
begin
ChinookDB.Connect;
Chansons.Active:=True;
end;

end.

Ma seule concession au code a été de gérer l'événement du bouton « Ouvrir » pour me connecter à la base de données puis à l'ensemble de données interrogé.

Jusque-là vous pourriez faire remarquer qu'il n'y aurait pas plus de code dans un programme VCL et les composants TDBxxxxxxx. À cela, je répondrais qu'il s'agit d'un programme FMX et qu'une petite chose est cachée au niveau de la case à cocher. Les lecteurs qui maîtrisent déjà les liaisons rapides peuvent survoler le chapitre jusqu'à la section II.A.3

II-A. Réalisation

Je vais reprendre les différentes étapes me permettant d'arriver à ce résultat à partir d'une application multipériphérique (projet FMX) vierge. Il y a différentes manières de faire, et j'en propose au moins trois, mais il existe une base commune, à savoir la partie concernant l'ensemble de données. Donc, quelle que soit la démarche suivie par la suite, en premier lieu je pose les composants d'accès à mes données : un composant TZConnection et un composant TZQuery (ou TZReadOnlyQuery) si je n'ai pas autre chose comme connecteur aux bases de données. Dans le cas contraire, j'utiliserai leurs équivalents. Avec Firedac, il s'agit d'un TFDConnection et un TFDQuery (plus proche que cela, c'est difficile, non ?).

Je vais aussi poser sur ma forme un composant TBindingsList.

Pour ce composant TBindingList, seuls les possesseurs d'une version starter seront obligés de le poser à cet instant pour pouvoir accéder aux liaisons. Idem pour ceux qui voudront utiliser les experts de liaisons sans passer par la case « pose des composants visuels ».

Image non disponible

Pour des raisons pratiques évidentes, dès que l'on a affaire à plusieurs ensembles de données, il ne faut pas oublier de renommer les composants.

Je ne m'étendrai pas sur les propriétés à indiquer au niveau des composants de connexion, partie plus ou moins hors sujet, mais je fournirai juste une indication sur la requête nécessaire à la récupération des données.

 
Sélectionnez
SELECT T.Name,T.MediaTypeId,A.Title,P.Pochette,S.extrait  FROM TRACKS T
            JOIN ALBUMS A ON T.Albumid=A.Albumid 
            LEFT JOIN POCHETTES P ON A.ALBUMID=P.IDALBUM
            LEFT JOIN PAROLES S ON T.TrackId=S.idTracks

J'active ensuite l'ensemble de données (ce qui me connecte aussi à la base de données) afin d'avoir tout de suite accès à la structure et aux valeurs des colonnes de ma requête.

Ne pas oublier qu'il s'agit d'une base SQLite et donc à accès unique. L'ouverture de la connexion bloque par conséquent la base à tout autre programme. Il ne faudra pas non plus oublier de déconnecter le composant de connexion par la suite.

II-A-1. Première méthode : utiliser l'expert de liaison

Après avoir sélectionné le composant TBindingsList, en bas de l'inspecteur d'objet un menu contextuel propose deux options :

  • Liaison des composants…
  • Nouveau composant Livebindings…
Image non disponible

Pour cette méthode, je choisis la première option qui m'ouvre l'éditeur de liens.

Un double-clic sur le composant fait la même chose.

En cliquant ensuite sur le bouton « nouveau », un menu à trois options apparaît :

  • Nouvelle liaison… Inser
  • Expert LiveBindings… Maj+Inser
  • Nouveau xxxxxxxxxx… Ctrl+Inser (dépendant de la dernière liaison créée)

Je choisis alors la seconde option.

Les touches raccourcis, faciles à mémoriser, feront gagner du temps.

Il suffit ensuite de se laisser guider. J'ai choisi en premier lieu de poser une grille.

Image non disponible
Image non disponible
Image non disponible
Image non disponible

Comble du luxe, la dernière étape de l'expert me permet même d'ajouter un navigateur. Je m'empresse donc de cocher la case avant de terminer. J'obtiens alors les différents composants demandés avec en supplément un composant TBindSourceDB que je réutiliserai par la suite.

Image non disponible

Le tout est centré dans la forme. Reste alors à déplacer, retailler, renommer et, dans le cas de la grille, à modifier les colonnes.

Je peux dès lors répéter ces opérations pour ajouter d'autres composants, par exemple un TEdit.

Image non disponible
Image non disponible

Profitez-en au passage pour découvrir la richesse du nombre de contrôles disponibles. Remarquez en particulier le TDateEdit qui n'a pas d'équivalent TDBDateEdit en VCL, hors ajout de composants tiers, ou encore tout ce qui concerne les couleurs.

Image non disponible

Prenez soin de sélectionner le BindSourceDB1 déjà créé et non l'ensemble de données « Chansons » sous peine d'obtenir un nouveau composant TBindSourceDB.

Image non disponible
Image non disponible

Encore un petit luxe : le dernier dialogue de la série propose d'ajouter un libellé au contrôle, ce que je fais. J'obtiens alors deux nouveaux composants : un TEdit et un TLabel enfant.

Très bonne petite astuce que ce TLabel enfant du TEdit, car cela permet de déplacer les deux contrôles en même temps lors du design. On n'y aurait peut-être pas pensé en posant les composants avant de faire les liaisons !

Image non disponible

Il y a cependant un inconvénient à cette méthode : les composants sont « en vrac ». On imagine facilement ce qu'un nombre important de contrôles tous centrés peut engendrer comme confusion ! La démarche n'est donc pas très naturelle. En général, on préférera dessiner en premier lieu l'interface. J'ai surtout présenté cette méthode pour la grille, la vision des composants qu'il est possible de lier et la petite astuce du TLabel enfant.

II-A-2. Deuxième méthode : préparer l'interface utilisateur

Cette seconde approche est certainement plus naturelle. Je pose tout d'abord tous les composants dont j'ai besoin avant de passer à la phase liaison.

Image non disponible

À ce stade, les possesseurs d'une version intégrant le concepteur de liens visuels ont un avantage certain puisque, en gros, il suffit d'opérations de glisser-déposer pour créer les liens.

Surtout, n'oubliez pas de renommer vos composants (en particulier ceux que vous allez lier) . En effet, avec des noms comme Edit1, Edit2 etEdit3 vous aurez quelque mal à savoir avec quel champ vous voulez faire la liaison !

II-A-2-a. Avec le concepteur visuel

Un simple clic droit sur la forme permet d'obtenir le menu contextuel. En sélectionnant l'option « Lier visuellement… », j'arrive alors dans mon concepteur. Glisser et déposer les éléments pour raccorder les propriétés entre elles permet d'obtenir toutes les liaisons nécessaires.

Image non disponible

II-A-2-b. Sans le concepteur visuel

Avec la version starter, point de concepteur ni de menu contextuel ,mais ce n'est guère plus compliqué que pour la première méthode. En effet, l'expert, appelé par un double-clic sur le TBindingList, présente, lors de la seconde étape du dialogue, les contrôles adéquats définis par la première étape.

Le processus est donc quasi identique à celui de la première méthode présentée. La différence est minime, avec une étape en moins dans le déroulement de l'expert, et porte essentiellement sur ce que j'ai présenté comme étant le « petit luxe supplémentaire ».

En suivant les mêmes étapes que dans la partie II.A.1, j'obtiens :

Image non disponible
Choix du type de contrôle
Image non disponible
Sélection de la grille existante
Image non disponible
Choix de la source de données

Le même principe est suivi pour les contrôles existants.

Image non disponible
Liaison à un contrôle
Image non disponible
Choix du contrôle

Vous remarquerez ici que ma suggestion de ne pas oublier de nommer mes contrôles explicitement s'explique. Par exemple, avec Edit1 et Edit2, vous ne pouvez pas savoir quelle zone de saisie correspond à quel champ !

Une fois les différentes liaisons faites, j'obtiens cette liste :

Image non disponible

Encore une fois, je ne peux que recommander de renommer les liens plus explicitement.

Il me manque la liaison avec le navigateur. Pour ce dernier, point besoin d'expert, le remplissage de la propriété DataSource avec la valeur LiaisonsDB (nom que j'avais donné à mon composant TBindSourceDB ) fera le nécessaire.

Une minute… Mais quand donc ce composant est-il apparu ? C'était pour voir si vous suiviez ! Plus sérieusement, tout à ma démonstration j'ai repris ce qui avait déjà été fait lors de la première méthode, et supprimé tous les liens existants dans la liste afin de dérouler de nouveau l'expert, mais ce composant est resté sur ma forme.

Comme il n'y a pas d'expert pour ça, faudrait-il alors déposer le composant sur la forme et remplir sa propriété DataSource (avec la valeur Chansons dans mon exemple), et ce avant toute autre chose ? En fait, non : lors de la liaison de la grille avec une source de données, troisième image de ce chapitre, seul Chansons apparaîtra. Le fait de sélectionner cette source conduira l'expert à la création nécessaire.

L'option « Lier le contrôle à un champ » ne fait pas apparaître tous les composants disponibles, mais uniquement ceux qui peuvent être bidirectionnels.

Pour lier les autres, vous sélectionnerez l'option « Lier une propriété de composant à un champ ».

II-A-3. La méthode des « experts »

Jusqu'à présent je n'ai utilisé que des liaisons classées dans la catégorie « liaisons rapides ». Généralement, cette catégorie est suffisante. Toutefois, ce type de liaison est quelquefois pénalisant. Par exemple, lorsque l'on veut vérifier la syntaxe des expressions, il est impossible de le faire.

C'est là où intervient l'option du menu « Nouveau composant Livebindings… » en bas de l'inspecteur d'objets ou de l'option « Nouvelle liaison … » de l'éditeur de TBindingsList. Un choix de type de liaisons est alors proposé.

Image non disponible

Ignorez les liens DB puisque obsolètes et faites abstraction des liens rapides : autant passer par les autres méthodes qui les créent, car le champ des possibles se limite alors dans l'arborescence aux Liens et Listes.

Pourquoi et comment passerai-je alors par ce dialogue ? Le pourquoi est déjà exposé : le plus souvent, il s'agit de pouvoir vérifier les expressions utilisées dans la propriété CustomFormat. Un mauvais remplissage de cette propriété et, avec de la chance, rien ne s'affiche mais, au début de mes confrontations avec les LiveBindings, cela pouvait aller jusqu'à un plantage de l'EDI ! Quant au « comment », nous allons l'examiner ci-après.

II-A-3-a. Un TLabel

Comme de juste, j'examinerai le plus simple en premier ! J'ai pris un TLabel jusqu'alors non introduit, mais cela aurait pu être tout autre composant avec une propriété Text : un TPanel, un TButton, etc.

Pour l'exemple, qu'en serait-il si, au lieu d'utiliser des zones de saisie pour afficher le titre de la chanson et son album, je voulais afficher des données via une simple zone texte et si, de surcroît, je voulais que cela soit écrit en majuscules ?

Pour tester, j'ajoute à l'interface utilisateur un TLabel que je nomme « LibelléLié ».

Bien sûr, il y a toujours la possibilité du code, en codant l'événement AfterScroll par exemple.

 
Sélectionnez
procedure TForm3.ChansonsAfterScroll(DataSet: TDataSet);
begin
LibelléLié.Text:=UpperCase(Chansons.FieldByName('Name').AsString);
end;

Cependant l'utilisation de cet événement peut s'avérer gênante pour de nombreuses raisons, la première étant que même si le contrôle est désactivé l'instruction s'exécutera.

L'expression étant simple, une liaison rapide (TLinkPropertyToField) suffirait également, pour peu de remplir sa propriété CustomFormat correctement. Mais justement, et c'est là que le bât blesse lorsque l'on débute, quelle est la bonne expression ? Comme il est impossible de procéder à des tests avec une liaison rapide, ça passe ou ça casse.

Quel type de liaison choisir alors ?

Liens

 

TBindLink

Conçu pour définir un lien permettant de transmettre la valeur de la propriété d'objet spécifiée à une source de données sélectionnée

TBindListLink

Un lien spécialisé pour les objets List

TBindGridLink

Un lien dédié pour les objets Grid

TBindPosition

Lien qui permet de synchroniser les positions entre les objets travaillant avec des collections (telles que ListBox et Grid)

TBindControlValue

Lien qui permet de synchroniser les valeurs lorsqu'elles changent dans l'objet source

Listes

 

TBindList

Définit une liaison unidirectionnelle pour remplir des objets tels que ListBox, Combobox, etc.

TBindGridList

Définit une liaison unidirectionnelle pour remplir les objets Grid

Tableau : Types de liaisons

Un TBindLink fait donc l'affaire dans le cas étudié.

Image non disponible

Une fois le type de lien sélectionné, il me faut remplir les propriétés ControlComponent, SourceComponent et SourceMember.

Malgré ces renseignements, rien ne se passe, comme avec les liaisons rapides. Il va me falloir aussi remplir les expressions. Si l'on veut tester les expressions, le mieux est de double-cliquer sur le lien dans la liste des liens pour obtenir une fenêtre de modification de lien.

Image non disponible
Image non disponible
Image non disponible

Je demande à ajouter un formatage pour pouvoir indiquer quelle propriété du contrôle je vais modifier (ici Text). Je peux alors facilement vérifier la valeur et le type de celle-ci avec le bouton « Évaluer le contrôle ». Je ferai de même pour la source. C'est d'ailleurs là que je vais utiliser mon expression, en bref que j'écrirai mon code.

Image non disponible

Cette fois, non seulement j'ai pu évaluer l'expression, mais utiliser le bouton « Assigner au contrôle » va changer la valeur.

À la place de Value, je peux utiliser Text, %s ou encore DisplayText.

Ma préférence va à Value qui fait référence à la valeur du champ qui sera transformé en chaîne. Les trois autres (différence subtile) sont la représentation en chaîne de la valeur.

Pour une colonne texte, cela a peu d'importance, mais pas pour une colonne numérique… Si les Anglo-Saxons, qui utilisent le point comme séparateur de décimales, n'auront aucun problème pour une expression comme 1*1.25, les utilisateurs de la virgule se heurteront au message « ne peut calculer 1*1,25 » qui les fera rire jaune.

Image non disponible

Pour quelque chose d'aussi « simple », il n'y a pas de quoi sauter d'excitation ! Mais je vais pousser le bouchon un peu plus loin. Si, toujours pour l'exemple, je voulais que la couleur du texte soit rose si le type de média était du MPEG (MediaTypeId=1) ?

Image non disponible
Image non disponible

Quelques notes à propos de Dataset.FieldByName('MediaTypeId').Value.

Plusieurs autres écritures sont tout aussi valables :

Dataset.MediaTypeId.Value ;

DataSet.MediaTypeId.Text ;

Owner.DataSet.MediaTypeId.Value ;

Self.DataSet.MediaTypeId.Value ;

etc.

Ce n'est qu'une vieille habitude, héritée de la programmation d'avant les LiveBindings, qui me fait utiliser la première forme.

Quels sont les nombres utilisés ? Le premier (-60259) correspond à la couleur rose et le second (-16777216) au noir.
Comment les ai-je obtenus ? Simplement en changeant la propriété TextSettings.FontColor dans l'inspecteur d'objets puis en utilisant le bouton qui permet d'évaluer le contrôle.
Le problème le plus complexe a été de pouvoir accéder au champ MediaTypeId alors que l'on partait du membre Name de la source.

Un TBindLink nous ouvre donc des perspectives non offertes par une liaison rapide.

Qu'en est-il s'il s'agissait non pas d'un texte classique, mais d'une date ou d'un nombre quelconque (flottant, monétaire, etc.) que je voudrais formater selon mes goûts ? Je propose deux solutions.

La première intervient si j'ai au préalable indiqué le format au niveau de mes déclarations de champs (propriété DisplayFormat du TField). Dans ce cas, je peux utiliser DisplayText au lieu de Value (plus parlant pour moi) ou Text utilisé précédemment pour l'expression de la source.

Image non disponible

Dans l'image ci-dessus, j'ai utilisé DisplayText pour afficher la date selon mon DisplayFormat. Pour le champ Total, j'ai indiqué qu'il était de type monétaire (propriété currency du champ à True).

La seconde solution, et elle paraît plus simple, consiste à ne pas déclarer les champs et leur format, et à utiliser directement une expression de Format (FormatDateTime en cas de date) incluse dans le moteur des LiveBindings.

II-A-3-b. Un TEdit

Pour une zone de saisie, il me faut appliquer le même raisonnement et le même type de lien que pour un TLabel. Bien sûr, il est inutile de jouer sur la casse puisque le composant TEdit permet de le faire. Par contre, jouer avec la propriété CustomFormat peut amener quelques problèmes.

Par exemple : je peux très bien afficher le nom complet d'un client.

Image non disponible

Mais, de base, il est impossible de faire la transformation inverse (ParseFormat) pour mettre à jour dans la base de données le prénom et le nom saisis.

Ne pas indiquer d'expression ParseFormat empêche la liaison d'être bidirectionnelle. Toute saisie faite dans la zone d'édition ne sera pas reportée dans la base de données.

Voici un autre exemple de ce phénomène. Reprenons les exemples utilisés lors du paragraphe précédent pour des colonnes de type date ou monétaire de date. J'ai utilisé des expressions dans la zone CustomFormat. Si, pour afficher la date, je respecte une forme « classique » comme jj/mm/aa, cela ne posera aucun problème.

Image non disponible

Par contre, si ma date était formatée quelque peu différemment, par exemple sous la forme jour, nom du mois, année (dd mmmm yy ou dd mmm yy), il me faudrait passer par une conversion du texte saisi dans une expression permettant d'analyser cette valeur (ParseFormat). La méthode de transformation de chaîne en date StrToDateTime fournie dans les méthodes « usine » semble à première vue être la bonne solution.

Image non disponible

Mais l'utilisation du bouton d'évaluation permet clairement de voir que ce n'est pas le cas !

Pour résoudre ce type de problème, il faudra définir sa propre fonction de conversion. Ce sujet a déjà été abordé dans les tutoriels de l'épisode 2 (parties a et b)

LiveBindings : Les effets de bord

Livebindings

II-A-3-c. Un TGrid

Pour lier une grille, bien évidemment, je passe par le lien spécialisé TBindGridLink (cf. ). Pour se convaincre de sa complexité, il suffit de regarder le nombre d'expressions qu'il faut mettre en place en regardant en détail une liaison faite selon l'une des deux méthodes précédentes.

Cette partie est très complexe et vous préférerez certainement botter en touche et n'utiliser que l'expert. Ce qu'il faut comprendre, c'est qu'en premier lieu, il y a deux expressions incontournables concernant le positionnement à écrire.

PosControl positionne l'enregistrement courant dans la grille.

(vers) Contrôle

(de) Source

Selected

Math_Max(0,DBUtils_ActiveRecord(Self))

Image non disponible
PosControl

PosSource positionne la ligne sélectionnée de la grille sur l'ensemble de données.

(de) Contrôle

(vers) Source

Math_Max(1,Selected+1)

DBUtils_ValidRecNo(Self)

Image non disponible
PosSource

Je dirais que ce sont deux expressions à tenter de mémoriser tant elles sont nécessaires !

Ensuite, il faut ajouter les colonnes, chacune ayant trois ensembles d'expressions.

ColFormat : expressions permettant de modifier les caractéristiques de la colonne (par exemple la largeur).

CellFormat : expressions qui vont renseigner le contenu de cellules.

ParseFormat : expressions qui permettront de modifier les données dans l'ensemble de données.

Remarquez bien l'utilisation du pluriel pour le terme expressions.

C'est ce que ne nous permet pas l'utilisation via l'expert : indiquer à l'interpréteur d'expressions de faire plusieurs opérations lorsqu'il renseigne une cellule.

Image non disponible
Colonne

Cette partie est si complexe que je préfère en limiter les explications à ces quelques indications et vous renvoyer à un futur tutoriel consacré uniquement à ce sujet.

À n'en pas douter, vous ferez comme moi et passerez par l'expert, quitte par la suite à modifier quelques expressions selon les besoins.

II-B. Mention particulière pour la case à cocher

La case à cocher amène au premier cas particulier. En effet, elle attend une valeur booléenne que je n'ai pas dans mes données. Je vais me donner un petit coup de pouce en décidant que la case sera cochée si et seulement si la valeur de la colonne MediaTypeId correspond à la valeur 1, c'est-à-dire à un fichier média MPEG.

J'aurais pu choisir d'indiquer via cette case d'autres informations. Par exemple : fichier audio ou pas, fichier protégé ou non, etc. Regardez dans la table Media_Types pour avoir une idée des possibilités.

J'utilise alors la propriété CustomFormat du lien afin de coder le comportement de la case.

IfThen(value=1,True,False)

La casse de la propriété est très importante (lire mon tutoriel sur l'interpréteur d'expressions pour plus d'informations).

Bien, mais qu'en serait-il si je devais modifier une colonne ? Je vous demande un peu d'imagination, car la base de données Chinook ne semble pas contenir de colonne booléenne ou une colonne ne pouvant contenir que deux valeurs (par exemple 'O' ou 'N').

Dans ce cas, j'utilise ces expressions :

Propriétés du lien

Expression

CustomFormat

IfThen(value='O',True,False)

ParseFormat

IfThen(True,'O','N')

III. Les listes : TListBox, TListView

Les remplissages de listes avec les LiveBindings sont déconcertants de simplicité. Sans une ligne de code, un remplissage simple se fait très rapidement. Des astuces que j'ai déjà dévoilées dans d'autres tutoriels (Livebindings : Données sans SGBD, Mettre de la couleur dans une ListView) permettent ensuite de personnaliser la présentation.

La seule différence ici par rapport à ces deux tutoriels réside dans le fait que l'on ne lie plus des objets internes au programme via un TlistLinkControlToField, mais une source de données pour laquelle on utilisera plutôt un lien TLinkFillControlToField.

Encore une fois, ceux qui ont une version professionnelle ou plus seront avantagés grâce au concepteur visuel. C'est d'ailleurs grâce à ce dernier que j'ai pu détecter que le type de lien était différent du remplissage fait lors de mes deux précédents tutoriels. Dans le cadre de ce tutoriel, je ne me vois pas débattre de l'utilisation de l'un plutôt que de l'autre, préférant porter mes efforts sur l'utilisation de ces listes.

III-A. Utilisation sur l'ensemble de données principal

On pourrait très bien remplacer la grille, aux données modifiables, par une liste.

Dans ce cas, le lien sera de type TListLinkControlToField.

Image non disponible

Ci-dessous, voici un simple TListBox, toujours rempli sans une ligne de code, que l'on peut facilement améliorer pour y ajouter recherche et regroupement (ici, regroupement par album).

Utiliser le regroupement donne des résultats assez désastreux au niveau de la synchronisation avec les autres zones d'affichage sur la même source de données. Bogue ou mauvaise liaison de ma part, je n'ai pas encore tranché. Après de nombreux tests, j'ai cependant l'impression que le positionnement est géré par l'index de l'élément et non par sa valeur.

III-A-1. Lier un TListBox avec le concepteur visuel

Toujours par simple glisser-déposer, je synchronise tout d'abord la liste à ma source de données, puis j'indique quel champ servira au remplissage de ma liste. Je crée le schéma suivant :

Image non disponible

Ajouter le groupement se fait aussi simplement. Il faut toutefois s'assurer de bien ajouter un TListBoxGroupHeader à la liste avant de procéder à la liaison.

Image non disponible

III-A-2. Lier un TListBox sans le concepteur visuel

Avec l'habitude, il est presque moins frustrant de ne pas utiliser le concepteur visuel, surtout sur un poste de travail un peu lent. Je procède toujours de la même manière, c'est-à-dire avec un double-clic sur le composant conteneur de liaisons : TBindingsList et l'ajout d'un TListLinkControlToField. Il suffit alors de remplir les quelques propriétés nécessaires pour obtenir le même résultat qu'avec le concepteur visuel.

Image non disponible

Surlignées en jaune, ce sont les propriétés nécessaires pour la liste. Les points rouges indiquent les propriétés nécessaires pour un regroupement (ici, par titre d'album).

III-A-3. Ajout d'un système de recherche

Aucune liaison n'est nécessaire pour un système de recherche : il suffit d'ajouter un élément TSearchBox au contrôle via le menu contextuel (clic droit sur le contrôle de liste).

III-A-4. Opter pour un TListView

Image non disponible

L'inconvénient d'une ListBox est que le texte de l'élément reste simple, sauf à utiliser des styles personnalisés qui rendront la codification superflue. Passer alors par un TListView (apparence dynamique) à la place du TListBox peut encore éviter l'utilisation d'un style.

Ci-dessous un exemple de ce que l'on peut faire :

C'est loin d'être parfait, à cause de mes choix de StyleBook. Pour ce type de composant, le style Transparent serait plus adapté. Toutefois, a contrario du TListBox, regroupements comme recherches ne posent aucun problème de synchronisation.

Image non disponible

Le processus de liaison est identique à celui précédemment indiqué pour le TListBox.

Les « secrets » de l'apparence dynamique, vous les retrouverez dans mon tutoriel : Mettre de la couleur dans un TListView.

III-B. Utilisation sur une table « référence »

Il peut être utile d'utiliser une table « référence », par exemple pour avoir une liste des types de médias (table media_types) et mettre en exergue le libellé correspondant en fonction de la valeur de la colonne media_type de la requête Chansons.

Il faut alors envisager l'utilisation d'un lien de type TLinkFillControlToField.

Image non disponible
Version Firedac

En haut de l'image, vous voyez un TListBox, en bas, un TListView. À l'exécution, l'élément est sélectionné.

Image non disponible

Bien sûr, les modes de recherche dans les listes et même les regroupements sont possibles.

III-B-1. Méthode manuelle

Une fois n'est pas coutume, c'est par la méthode manuelle que je commence mon explication. Cette fois-ci, j'ajoute un TLinkFillControlToField à mon conteneur de liens. Toutefois, avant de procéder, je me dois d'ajouter une source de données (un composant table pointant sur media_type) et un moyen pour y accéder (un TBindScope).

Je vais, une fois de plus, ne jouer que sur les propriétés de ce lien pour avoir mon remplissage et ma liaison avec la source de données principale.

Image non disponible

Les points importants sont marqués d'un point rouge.

Ce qu'il faut comprendre, c'est que le Datasource fait référence à la source de données principale (Chansons) et que le champ qui fait la liaison (FieldName) est la colonne MediaTypeId de celle-ci. Le FillDataSource, comme tout ce qui commence par Fill d'ailleurs, concerne la source de données référence (table media_types). FillDisplayName indique quelle colonne sert à l'affichage, et FillValueFieldName la colonne de référence (foreign key) de la table.

III-B-2. Avec le concepteur visuel

Le piège avec le concepteur visuel, si tant est que l'on puisse parler de piège, réside dans la tentation d'utiliser synch comme dans le chapitre III.A. En fait, on doit construire le schéma suivant :

Image non disponible
Listes Références

Vous remarquerez que le schéma est le même qu'il s'agisse d'un TListBox ou d'un TListView.

IV. Les Boîtes de choix TComboBox et TEditComboBox

Dans des bases de données classiques, on a souvent affaire à des colonnes faisant référence à d'autres tables connexes, ce qui est indiqué par des relations de clés étrangères (FOREIGN KEY). La table Tracks de la base de données exemple (chinook.db) en est une représentation typique.

Image non disponible

L'objectif est donc de montrer sur l'interface utilisateur une liste de choix possibles et, de préférence, compréhensibles.

IV-A. TComboBox

Sans conteste, TComboBox est le contrôle qui ressemble le plus au TDBLookupComboBox de la VCL lorsqu'il est correctement lié aux données. En revanche, sa prise en main a été dans mon cas difficile. En fait, je n'avais pas fait attention à quelque chose de tout bête : ce n'est ni plus ni moins qu'une liste améliorée au niveau de l'interface graphique, mais réduite à sa plus simple expression.

Image non disponible

IV-A-1. Avec le concepteur

Les possesseurs des versions ayant le concepteur de liens visuels seront fortement aidés pour cette partie. Une fois la source de données référence posée, voilà la représentation de ce qu'il faut obtenir par des opérations simples de glisser-déposer :

Image non disponible

Ce qu'il faut comprendre :

  • SelectedValue lie la colonne MediaTypeId à la source Chansons (la table que je veux modifier), cette valeur étant recherchée ou renseignée via la source de données Médias par l'intermédiaire de Item.LookUpData ;
  • Item.LookUpData permet de chercher ou renseigner la valeur MediaTypeId de la source de données Médias ;
  • L'affichage est fait par l'intermédiaire de Item.Text, ici lié à la colonne Name de la source de données Médias.

Tout paraît évident et simple, une fois que le principe est explicité ! Pour faire la même chose sans le concepteur visuel de liaisons, c'est beaucoup moins facile…

IV-A-2. Méthode manuelle

Tout d'abord, quel type de lien choisir ? Si j'ai fait l'apologie de la non-utilisation des liaisons rapides afin de pouvoir tester les expressions, il n'y a hélas aucune liaison autre qu'une liaison TLinkFillControlToField pour faire ce que je veux !

Premièrement, je dépose un composant d'accès à ma table (ZTable,ZQuery ou ZReadOnlyQuery ; FDTable ou FDQuery). Dans le cas de la table Tracks, l'accès à la table entière est parfait. Ensuite, je dépose un composant TBindSourceDB que je relie ,grâce à sa propriété Dataset, à ma source de données (ZTable ou FDTable) renommée Médias pour faciliter sa lecture.

Image non disponible

Malheureusement, ce BindSourceDB ne se crée pas automatiquement. Faites attention à bien le créer avant l'étape suivante.

Je double-clique ensuite sur le contrôle TBindListLink pour accéder aux experts de liaisons et j'ajoute une nouvelle liaison TLinkFillControlToField. Tout se joue ensuite sur le remplissage des propriétés de ce lien.

Image non disponible
Image non disponible

Propriété

Valeur à mettre

Control

Le contrôle combobox que je veux lier

DataSource

La source de données principale, c'est-à-dire celle où je veux faire à l'occasion une modification

FieldName

La colonne concernée

FillDataSource

La source de données qui va remplir la liste de la boite de choix

FillDisplayFieldName

La colonne qui contient les données à afficher dans la liste

FillValueFieldName

La valeur que je veux récupérer

C'est fait ! Malheureusement, au moment du design, rien n'est visible dans la boîte de choix. Il faudra donc attendre une exécution pour voir si le lien fonctionne correctement.

IV-B. TComboEdit

De mon point de vue, le nom de ce composant induit en erreur. Je m'attendais à la même chose que le TComboBox, mais avec une possibilité de saisie supplémentaire. C'est en partie vrai, mais il semble impossible d'utiliser, sans de grandes astuces, le même système de couple libellé-valeur. Ce composant sera principalement à utiliser pour des colonnes avec tables connexes, mais où la valeur de référence sert également de libellé.

Par exemple, je récupère via une requête les valeurs distinctes d'une colonne Civilité d'une table Clients et je les utilise comme éléments de choix. Pour peu de rafraîchir régulièrement la requête, j'aurai toutes les valeurs en cas de saisie d'une nouvelle. Si dans ma table Clients je n'ai que des personnes physiques francophones, je me retrouverai très certainement avec les choix M., Mme, Mlle. Imaginons que j'ajoute un client anglophone, peut être alors mettrai-je Mr. Un rafraîchissement de ma requête, et le choix sera alors complété.

Pour illustrer cet exemple, j'ai ajouté une colonne à la table customers de la base. Plutôt que de le faire directement dans la base de départ, j'ai préféré le faire par code.

Avec Firedac
Sélectionnez
procedure TForm1.ConnexionClick(Sender: TObject);
var CiviliteExist : Boolean; // colonne civilité existe ? 
begin
CiviliteExist:=False;
ChinookDB.Connected:=True;
// utilisation des métadonnées pour détecter l'existence de la colonne
FDMetaInfoClients.Active:=True;
while not FDMetaInfoClients.EOF  do
 begin
   CiviliteExist:=CiviliteExist OR (CompareText(FDMetaInfoClients.FieldByName('COLUMN_NAME').AsString,'civilities')=0);
   FDMetaInfoClients.Next;
 end;
FDMetaInfoClients.Active:=False;
// Ajout au besoin de la colonne
if not civiliteexist then ChinookDB.ExecSQL('ALTER TABLE customers ADD civilities CHAR(5)');
// réinitialisation de la colonne à chaque ouverture pour les tests
ChinookDB.ExecSQL('UPDATE customers SET civilities=NULL');
FDQCivilites.Active:=True;
FDTableClients.Active:=True;
end;

J'ai créé une nouvelle fiche pour interroger et modifier ma table customers.

Image non disponible
Utilisation avec FireDac

La gestion de l'événement AfterPost de la table va me permettre de remplir la boîte de choix en cas de nouvelle saisie.

Avec Firedac
Sélectionnez
procedure TForm1.FDTableClientsAfterPost(DataSet: TDataSet);
begin
if ComboEditCivilité.Items.IndexOf(FDTableClients.FieldByName('Civilities').AsString)<0 then
 begin
   FDQCivilites.Active:=False;
   FDQCivilites.Active:=True;
   FDTableClients.RefreshRecord; // nécessaire
 end;
end;

Comme il existe plusieurs possibilités pour la requête permettant de recenser les civilités utilisées, j'ai opté pour un simple SELECT DISTINCT civilities FROM customers ORDER BY civilities .

La seule contrainte est qu'il faut faire un rafraîchissement de l'enregistrement en cours pour que la boîte de choix réagisse correctement.

Pour l'ordre de la liste, une autre solution est envisageable : déclarer la colonne comme indexée (propriété IndexFieldNames).

IV-B-1. Liaison avec le concepteur visuel

C'est avec le concepteur visuel que la pauvreté de TComboEdit par rapport au TComboBox est la plus criante. Seuls deux membres sont accessibles : SelectedValue et ItemText.

Image non disponible

V. En cas d'erreur

Oui, il arrivera de commettre des erreurs, aussi bien avec le concepteur visuel que sans lui ! Deux cas sont possibles :

  • une erreur dans l'EDI, avec un message peu sympathique, indiquant qu'un paquet ne peut plus être chargé ;
  • une erreur à l'exécution avec une adresse pas toujours explicite.
Image non disponible

Pas de panique : en général, la suppression d'un lien suffit à régler la situation.

VI. Conclusion

Je me suis attaché aux liaisons, sans me soucier de la partie bidirectionnelle de celles-ci. C'est un choix assumé, car il s'agit plus d'un problème d'utilisation des composants de base de données que de liaison au sens strict, dépendant donc du choix des composants d'accès aux données (utiliser une table rend la chose facile ; utiliser une requête telle celle proposée dans le programme d'illustration rendra obligatoire l'ajout d'un composant UpdateSQL, ou équivalent).

À propos des composants d'accès aux bases de données, vous m'excuserez d'avoir utilisé FireDac ou ZEOSDBO indifféremment pour mes illustrations. La faute en revient au fait que mes tests de la version Starter ne sont pas faits sur le même poste de travail. Néanmoins, cela n'a que peu d'impact, l'important restant l'utilisation du BindSourceDB à chaque fois.

Que conclure de cette technique utilisant les LiveBindings ? Si l'on se contente de faire une comparaison avec les composants VCL d'accès aux données, que j'aurais tendance à utiliser pour des programmes Windows, seul le remplissage de listes est, à première vue, le point positif. L'utilisation des LiveBindings dans un programme VCL prend plus de sens si l'on utilise les fonctionnalités CustomFormat et ParseFormat que je n'ai que très légèrement abordées. Pour les programmes multiplates-formes, donc utilisant FMX, la question ne se pose pas puisque c'est la seule solution. Cependant, il est possible, grâce aux Livebindings, de lier bien plus de composants, et pas uniquement ceux présentés.

Ce qui est regrettable, c'est que l'utilisation de cette technique, qui évite beaucoup de codification, ne laisse aucune place à la documentation technique du programme nécessaire par la suite. De même, il semble évident que les Livebindings pénalisent le programme en temps d'exécution et en taille.

Tous mes remerciements vont aux différents intervenants sur cet article, des simples lecteurs des versions successives (Nabil) aux relecteurs et correcteurs techniques gbgreg, skywaukers , avec mention particulière pour la patience du responsable Delphi Gilles. Je n'oublie pas, dans mes remerciements, le correcteur orthographique et grammatical Claude Leloup sans qui mes fautes auraient fait tache.

VII. Bibliographie

Je vous recommande la lecture de ce document Understanding Rad Studio Livebindings de Cary Jensen : bien qu'un peu daté (version XE2), c'est une bonne base de travail.

Vous trouverez également quelques vidéos sur le Net mais, hélas, les démonstrations sont souvent faites avec le concepteur ou l'expert.

Vous pouvez charger les sources des programmes :

à cette adresse pour ceux qui ont une version avec Firedac inclus ;

à cette adresse pour ceux qui ont la version Starter (ZEOSDBO installé).

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

  

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.