LiveBindings de A à … Effets de Bord

Implémenter des méthodes

Serge Girard

Dans le premier épisode, nous avons vu comment faire une liaison manuelle pour lier deux composants. Nous avons appris que les LiveBindings étaient en fait des expressions, ces expressions pouvant appeler, entre autres, des méthodes. Le but de cet épisode est de découvrir comment ajouter nos propres méthodes et de mettre en lumière des fonctionnalités assez intéressantes liées à l'utilisation des LiveBindings.

- Titre : LiveBindings de A à … Effets de Bord

- Auteur : Serge Girard

- Parution : 6 mai 2017

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Serge Girard. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Piqûre de rappel : Liaison manuelle : TBindExpression

- I-A. Liaison Rapide 1

- I-B. Liaison manuelle

- I-C. Les propriétés de la liaison rapide

- II. Effets secondaires

- II-A. Avant l'apparition des LiveBindings

- II-B. Depuis les LiveBindings

- II-B-1. Création d'un objet

- II-B-2. À ce stade, comment se présentent les sources ?

- III. Conclusion

- IV. Finalisation du programme

- IV-A. Isoler la partie interface de la partie métier

- IV-B. Les taux de change

- IV-C. Récupérer un fichier

- IV-D. « XML, vous avez dit XML ? »

- IV-E. Ma solution pour exploiter le fichier

- V. Remerciements

- VI.

I - Piqûre de rappel : Liaison manuelle : TBindExpression

Dans l'épisode précédent, nous avons utilisé une propriété d'un composant TTrackBar pour déplacer un cercle. Dans ce premier chapitre, nous allons étudier d'un peu plus près ce type de lien.

Ce premier programme est un autre exemple d'utilisation basique et va nous permettre de comparer encore une fois lien rapide et lien manuel afin de plonger un peu plus dans l'univers des LiveBindings. Notre objectif est simple : saisir une phrase (par exemple, le fameux « Hello World ») pour répercuter cette saisie sur quelques zones de l'écran (trois composants TLabel nommés Capture1 à 3).

Mise en garde aux détenteurs d'une version starter !

Malheureusement pour les détenteurs de cette version, le concepteur visuel de liens n'est pas disponible (ainsi que les raccourcis y donnant accès) Pour eux, seule la méthode manuelle sera accessible ! Ces points ou visuels non accessibles seront indiqués par un astérisque (*).

I-A - Liaison Rapide 1

Concepteur visuel*

(cliquez ici pour aggrandir l'image)

Sur cette image écran, j'ai déjà établi un lien rapide entre la zone de saisie (TEdit1) et la première zone de « capture ».

Avant d'aller plus loin, voici les sources à ce stade :

- Le DFM :

DFM programme 1

object Form8: TForm8
Left = 0
Top = 0
Caption = 'Liaison rapide vs Liaison manuelle'
ClientHeight = 128
ClientWidth = 433
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Capture1: TLabel
Left = 280
Top = 27
Width = 45
Height = 13
Caption = 'Capture1'
end
object Capture2: TLabel
Left = 280
Top = 56
Width = 45
Height = 13
Caption = 'Capture2'
end
object Label3: TLabel
Left = 184
Top = 27
Width = 55
Height = 13
Caption = 'Lien Rapide'
end
object Label4: TLabel
Left = 184
Top = 56
Width = 56
Height = 13
Caption = 'Lien Manuel'
end
object Capture3: TLabel
Left = 280
Top = 88
Width = 45
Height = 13
Caption = 'Capture3'
end
object Edit1: TEdit
Left = 32
Top = 24
Width = 121
Height = 21
TabOrder = 0
Text = 'Label1'
OnChange = Edit1Change
end
object CheckBox1: TCheckBox
Left = 126
Top = 87
Width = 114
Height = 17
Caption = 'Pister le lien (track)'
TabOrder = 1
end
object BindingsList1: TBindingsList
Methods = <>
OutputConverters = <>
Left = 372
Top = 13
object LinkControlToPropertyCaption: TLinkControlToProperty
Category = 'Liaisons rapides'
Control = Edit1
Track = True
Component = Capture1
ComponentProperty = 'Caption'
end
end
end

- Le programme PAS :

Source Programme 1

unit U_Prog2_1;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
Data.Bind.EngExt, Vcl.Bind.DBEngExt,
System.Rtti, System.Bindings.Outputs, Vcl.Bind.Editors, Data.Bind.Components;

type
TForm8 = class(TForm)
Edit1: TEdit;
Capture1: TLabel;
Capture2: TLabel;
Capture3: TLabel;
CheckBox1: TCheckBox;
Label3: TLabel;
Label4: TLabel;
BindingsList1: TBindingsList;
LinkControlToPropertyCaption: TLinkControlToProperty;
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;

var
Form8: TForm8;

implementation

{$R *.dfm}

end.

À ce stade, si l'on exécute le programme, tant que l'on frappe quelque chose dans la zone de saisie, rien ne se passe ! Ce n'est qu'en appuyant sur la touche [Entrée] ou [Tab] qui permet de passer à la zone active suivante que la capture récupère la valeur saisie. Pas vraiment l'effet escompté, n'est-ce pas ? Vous auriez certainement préféré quelque chose de plus réactif où chaque caractère saisi se répercute automatiquement dans la capture. Donc il n'y a pas de quoi pavoiser, surtout que, sans les LiveBindings, vous auriez pu faire beaucoup plus simplement grâce à la méthode OnChange de la zone de saisie :

"Bonne vieille" méthode

procedure TForm8.Edit1Change(Sender: TObject);
begin
Capture1.Caption:=TEdit(Sender).text;
end;

Bien évidemment, comme nous avons trois zones de capture, il aurait fallu ajouter deux autres instructions dans cette procédure, une pour chaque capture.

I-B - Liaison manuelle

Dans l'épisode précédent, nous avons déjà vu que, pour avoir quelque chose de plus réactif, il fallait passer par un TBindExpression.

Petit rappel : sélectionnez le composant conteneur des liaisons (BindingsList1) et, dans l'inspecteur d'objets, l'option du menu « Nouveau composant LiveBinding... »*.

Options contextuelles du TBindingsList*

(cliquez ici pour aggrandir l'image)

Après sélection du type de lien, il ne reste qu'à remplir les propriétés de lien. Pour rappel, nous allons remplir les propriétés ControlComponent, ControlExpression, Direction, SourceComponent, SourceExpression. Les deux premières font référence au contrôle que nous voulons lier (c'est-à-dire Controle2), les deux dernières à la provenance (ou source, c'est-à-dire la zone de saisie), la direction étant déjà renseignée dans le bon sens.

Liaison manuelle Edit1 → Capture2*

(cliquez ici pour aggrandir l'image)

Les modifications de propriétés se répercutent sur l'écran du concepteur visuel de liaisons*. Il nous reste à faire en sorte que l'expression soit prise en compte. Nous allons donc ajouter l'événement OnChange du composant TEdit de la forme avec le code suivant :

procedure TForm8.Edit1Change(Sender: TObject);
begin
BindingsList1.Notify(Sender,'');
end;

- Nouveau DFM :

DFM

object Form8: TForm8
Left = 0
Top = 0
Caption = 'Liaison rapide vs Liaison manuelle'
ClientHeight = 128
ClientWidth = 433
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Capture1: TLabel
Left = 280
Top = 27
Width = 45
Height = 13
Caption = 'Capture1'
end
object Capture2: TLabel
Left = 280
Top = 56
Width = 45
Height = 13
Caption = 'Capture2'
end
object Label3: TLabel
Left = 184
Top = 27
Width = 55
Height = 13
Caption = 'Lien Rapide'
end
object Label4: TLabel
Left = 184
Top = 56
Width = 56
Height = 13
Caption = 'Lien Manuel'
end
object Capture3: TLabel
Left = 280
Top = 88
Width = 45
Height = 13
Caption = 'Capture3'
end
object Edit1: TEdit
Left = 32
Top = 24
Width = 121
Height = 21
TabOrder = 0
Text = 'Capture1'
OnChange = Edit1Change
end
object CheckBox1: TCheckBox
Left = 126
Top = 87
Width = 114
Height = 17
Caption = 'Pister le lien (track)'
TabOrder = 1
end
object BindingsList1: TBindingsList
Methods = <>
OutputConverters = <>
Left = 372
Top = 13
object LinkControlToPropertyCaption: TLinkControlToProperty
Category = 'Liaisons rapides'
Control = Edit1
Track = True
Component = Capture1
ComponentProperty = 'Caption'
end
object BindExpression1: TBindExpression
Category = 'Expressions de liaison'
ControlComponent = Capture2
SourceComponent = Edit1
SourceExpression = 'Text'
ControlExpression = 'Caption'
NotifyOutputs = False
Direction = dirSourceToControl
end
end

- Nouveau programme :

Modifs apportées

unit U_Prog2_1;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
Data.Bind.EngExt, Vcl.Bind.DBEngExt,
System.Rtti, System.Bindings.Outputs, Vcl.Bind.Editors, Data.Bind.Components;

type
TForm8 = class(TForm)
Edit1: TEdit;
Capture1: TLabel;
Capture2: TLabel;
Capture3: TLabel;
CheckBox1: TCheckBox;
Label3: TLabel;
Label4: TLabel;
BindingsList1: TBindingsList;
LinkControlToPropertyCaption: TLinkControlToProperty;
BindExpression1: TBindExpression;
procedure Edit1Change(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;

var
Form8: TForm8;

implementation

{$R *.dfm}

procedure TForm8.Edit1Change(Sender: TObject);
begin
BindingsList1.Notify(Sender,'');
end;

end.

À l'exécution, Capture2 réagit au doigt et à l'œil alors que Capture1 attend toujours le « signal » émis par les touches [Entrée] ou [Tab].

I-C - Les propriétés de la liaison rapide

Pour faire le tour complet de ce programme (sinon, pourquoi aurais-je ajouté une case à cocher et une troisième capture ?), nous allons nous attarder sur quelques propriétés supplémentaires de la liaison rapide.

La première étape consiste à créer un lien rapide entre Edit1 et Capture3 : c'est très facile à faire à l'aide de la souris et une opération de glisser-déposer*.

Liaison rapide Edit1 -> Capture3*

(cliquez ici pour aggrandir l'image)

Les propriétés suivantes sont à prendre en compte :

AutoActivate : il tombe sous le sens qu'elle doit être activée , sinon la liaison ne fonctionnera pas. Bien sûr, cette propriété peut être modifiée lors de l'exécution du programme via l'instruction :

Activer une liaison

LinkControlToPropertyCaption2.AutoActivate:=True ; //ou False
// mais nous pouvons aussi activer ou désactiver le lien ainsi
LinkControlToPropertyCaption2.Active:=True ; // ou False au runtime

Category : par défaut, « Liaisons rapides » avec un environnement de travail en français. Vous pouvez y écrire autre chose : cela ne se répercutera que sur le BindingsList1 et permettra de ranger un peu les choses lors de l'utilisation de ce dernier.

Component, ComponentProperty, Control : les propriétés sont remplies directement lors de la création via le glisser-déposer.

CustomFormat : nous y avons déjà fait appel dans l'épisode précédent pour y mettre notre formule (ou expression) de calcul. Le sens de fonctionnement est de Control vers Component, c'est-à-dire d'Edit1 vers Capture3.

Oui, je sais, c'est très frustrant et donc pénible, cette histoire de nom de propriétés !

Pour illustrer tout cela, je vais demander que la phrase saisie soit mise en majuscules en utilisant la méthode UpperCase.

CustomParse : pour faire simple, c'est l'inverse de CustomFormat, c'est-à-dire que le sens de l'expression est inversé (donc de Control3 vers Edit1). Je vais aussi utiliser la méthode Uppercase. Vous pourrez voir l'effet de ceci un peu plus loin lors de l'activation de la propriété suivante.

Mais où trouver ces méthodes ? Ayez la curiosité de regarder les propriétés de BindingsList1 et vous y verrez un (TMethods). Cliquez sur les « … » pour avoir la liste des méthodes recensées.

Attention, même si les noms sont les mêmes, il ne s'agit pas des fonctions des unités du genre StrUtils.

InitializeControlValue : par défaut, la case est décochée ; cochez-la et vous verrez tout de suite la zone de saisie se remplir avec UpperCase('Controle3') soit « CONTROLE3 », l'expression de CustomParse étant bien appliquée.

Name : un bon conseil, prenez le temps de mettre un nom à chaque lien, car ce qui est proposé par défaut, « LinkControlToPropertyCaption2 », est loin d'être clair quand on a des applications contenant des dizaines de liens.

Tag : pas de commentaire.

Track : cette propriété mérite que l'on s'y attarde un peu, et c'est d'ailleurs pour cela que, lors du design de l'écran, j'ai mis une case à cocher. Implémentons l'événement OnClick de ce composant qui va nous permettre d'activer ou de désactiver cette fonction :

Case à Cocher

procedure TForm8.CheckBox1Click(Sender: TObject);
begin
LinkControlToPropertyCaption2.Track:=CheckBox1.Checked;
end;

À ma connaissance, on ne peut pas lier une propriété d'un LiveBinding à un composant. On est donc obligé de coder un peu.

Auparavant, décochez la propriété Track du lien pour être en cohérence avec la case à cocher.

Exécutons maintenant le programme.

Saisissez une phrase du genre « Hello World », puis appuyez sur [Entrée] :

1. le curseur reste en fin de saisie ;

2. le contrôle 1 s'est mis à jour ;

3. le contrôle 2 a suivi la saisie lettre par lettre ;

4. le contrôle 3 n'a pas bougé !

5. Appuyez sur [Tab], ça y est ! Le contrôle 3 est à jour en contenant « HELLO WORLD » tandis que la case à cocher devient le contrôle actif.

Comme le contrôle actif est maintenant sur la case à cocher, activez-la puis repassez dans la zone de saisie pour modifier la phrase en « Bonjour à tous ».

Après appui sur la touche [Entrée], les étapes 1,2,3 sont identiques. Par contre, à l'étape 4, le contrôle 3 a changé lui aussi. La touche [Tab] ne fait que passer au prochain contrôle actif, à savoir la case à cocher.

Grâce à la propriété Track, on peut donc un peu changer le comportement de la saisie.

Ce programme n'est pas fourni dans les exemples accompagnant le tutoriel. Tout d'abord parce qu'il est loin d'être remarquable, mais aussi parce que la meilleure manière d'aborder les LiveBindings est bien de tester par soi-même ces quelques lignes. De plus, tout le code source est indiqué dans ce chapitre.

II - Effets secondaires

Baptisés comme tels par Cary Jensen, je vous livre d'abord la vision qu'il a des effets secondaires, soit le pourquoi des termes :

- un effet secondaire est une opération supplémentaire réalisée à la suite de l'exécution d'une méthode ;

- les effets secondaires sont normalement associés aux méthodes d'accessibilité de la propriété ;

- les effets secondaires peuvent également être initiés à partir de l'invocation de la méthode de l'objet, et ce à la suite de l'évaluation d'une expression par le moteur d'expression.

Bon, tout cela est très théorique et comme rien ne vaut la pratique, je vous propose de créer un petit convertisseur de monnaie. Le dessin d'écran en est très simple, car il ne comporte que quelques libellés et deux zones de saisie (une pour un montant en Euros l'autre pour un montant en dollars US). On améliorera ensuite ce premier jet afin d'avoir le choix de la devise de conversion, toujours par rapport à la monnaie de référence, c'est-à-dire l'euro.

Convertisseur € -> USD

(cliquez ici pour aggrandir l'image)

Toutefois, on veut pouvoir saisir un montant dans l'une de ces deux monnaies. Comment procéder ?

II-A - Avant l'apparition des LiveBindings

Challenge : sans composants supplémentaires, boutons ou autres, seuls les événements des deux TEdit étant disponibles, il va falloir jongler avec quelques variables ou tout autre moyen si l'on veut utiliser les événements OnChange de ces deux composants, sous peine de boucler.

Je vais utiliser la propriété Tag pour cela.

Taux est pour l'instant une constante (=1.10).

Avant les LiveBindings

procedure TForm9.EurosChange(Sender: TObject);
var V : Extended; // récupère la valeur de transformation
begin
case TEdit(Sender).Tag  of
0 : Begin
if TryStrToFloat(TEdit(Sender).Text,V)
then Conversion.Text:=FloatToStr(V*taux)
else begin
TEdit(Sender).Text:='';
Conversion.Text:='';
end;
End;
1 : Begin
if TryStrToFloat(TEdit(Sender).Text,V)
then Euros.Text:=FloatToStr(V/taux)
else begin
TEdit(Sender).Text:='';
Euros.Text:='';
end;
End;
end;

II-B - Depuis les LiveBindings

Avec les LiveBindings, nous allons utiliser ces effets secondaires. Pour cela, nous allons créer un nouveau lien d'un nouveau type : un TBindExprItems.

Rappel : la méthode pour ajouter un lien est déjà indiquée au chapitre I.B Liaison manuelle, la seule différence résidant dans le choix du type de lien.

Ajout de la liaison *

(cliquez ici pour aggrandir l'image)

Il vous faut ensuite exprimer les expressions qui vont être cette fois au nombre de deux !

La première sera dans le sens contrôle vers source, l'autre en direction inverse. Cela vous rappelle quelque chose ? Non ? Refaites un tour au chapitre Les propriétés de la liaison rapide, vous y êtes, les « fameux » CustomFormat et CustomParse !

Pour procéder à cela, vous remarquerez cette fois que, tous les boutons de la fenêtre de modification d'expressions sont disponibles.

Rappel, pour accéder à la fenêtre de modification d'expressions le plus rapidement possible, sélectionnez « Expressions » dans l'inspecteur d'objets *.

Ajout d'expressions

(cliquez ici pour aggrandir l'image)

Chaque expression de format créée n'a que trois propriétés :ControlExpression, Direction et SourceExpression. Il ne nous reste donc qu'à remplir ces deux expressions.

Format[0]

Format[1]

ControlExpression

Text

Text/1.10

Direction

dirSourceToControl

dirControlToSource

SourceExpression

Text*1.10

Text

N'oublions pas de gérer les événements des deux TEdit. De manière classique, une procédure OnChange suffira pour ces derniers. Il faut juste ne pas oublier de bien l'indiquer !

OnChange

procedure TForm9.EurosChange(Sender: TObject);
begin
BindingsList1.Notify(Sender,'');
end;

Exécutons le programme et saisissons 1 dans la zone de saisie des euros.

Aïe ! Nous venons de nous faire jeter comme des malpropres...

Erreur à l'exécution

(cliquez ici pour aggrandir l'image)

Et la surprise est de taille, l'erreur plutôt étrange : tout le monde sait diviser 1,1 par 1,1 ! Pourquoi pas notre interpréteur d'expressions ?

Tout d'abord, parce qu'il ne s'agit pas à proprement parler des chiffres, mais de deux chaînes de caractères. Dans la formule, nous avons écrit 1.10 pour le taux de conversion, pas 1,10.

Ensuite, parce que notre séparateur décimal est la virgule et non le point comme nos amis anglo-saxons qui n'auraient pas eu cette erreur (dommage de passer à côté quand même;-) ).

Pour vous en convaincre, ajoutez, dans l'événement OnCreate de la forme, l'instruction FormatSettings.DecimalSeparator:='.';

Il faut donc procéder autrement, mais n'est-ce pas un peu le sujet de l'article ? De toute façon, cette solution n'était pas très propre : écrire « en dur » le taux de conversion dans l'expression n'est pas vraiment professionnel !

II-B-1 - Création d'un objet

Pour résoudre le problème rencontré, nous allons créer un objet ayant deux fonctions :

TConvertisseur

TConvertisseur = Class(TComponent)
private
public
function ConvertirEUR2USD(const Valeur : String) : String;
function ConvertirUSD2EUR(const Valeur : String) : String;
end;

…
function TConvertisseur.ConvertirEUR2USD(const Valeur: String): String;
var f : Extended;
begin
result:='';
if TryStrToFloat(Valeur,f)
then result:=FloatToStr(f*taux);
end;

function TConvertisseur.ConvertirUSD2EUR(const Valeur: String): String;
var f : Extended;
begin
result:='';
if TryStrToFloat(Valeur,f)
then result:= FloatToStr(f/taux);
end;

Un objet public de ce type sera créé au moment de la création de la forme :

Form OnCreate

procedure TForm9.FormCreate(Sender: TObject);
begin
Convertisseur:=TConvertisseur.Create(Self);
end;

Ensuite, nous appellerons ces fonctions pour effectuer nos conversions dans les expressions de format.

Format[0]

Format[1]

ControlExpression

Text

Owner.Convertisseur.ConvertirUSD2EUR(Text)

Direction

dirSourceToControl

dirControlToSource

SourceExpression

Owner.Convertisseur.ConvertirEUR2USD(Text)

Text

Étudions de plus près une de ces formules.

Owner : fait référence au propriétaire du composant lié, c'est-à-dire le ControlComponent, soit le TEdit nommé « Conversion ».

Convertisseur : est bien sur l'objet TConvertisseur créé un peu plus haut.

Ensuite vient l'appel de la fonction qui va faire le traitement dépendant de la direction :

- La valeur de la propriété Text du TEdit « Euros » pour l'expression Format[0] ;

- La valeur de la propriété Text du TEdit « Conversion » pour l'expression Format[1].

Rappelons, si besoin, que cette propriété est de type String.

II-B-2 - À ce stade, comment se présentent les sources ?

DFM

object Form9: TForm9
Left = 0
Top = 0
Caption = 'Convertisseur'
ClientHeight = 134
ClientWidth = 343
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 24
Top = 43
Width = 27
Height = 13
Caption = 'Euros'
end
object DateXML: TLabel
Left = 24
Top = 8
Width = 23
Height = 13
Caption = 'Date'
end
object Label2: TLabel
Left = 24
Top = 80
Width = 48
Height = 13
Caption = 'Dollars US'
end
object Euros: TEdit
Left = 80
Top = 40
Width = 121
Height = 21
TabOrder = 0
Text = '0'
OnChange = EurosChange
end
object Conversion: TEdit
Tag = 1
Left = 80
Top = 77
Width = 121
Height = 21
TabOrder = 1
Text = '0'
OnChange = EurosChange
end
object BindingsList1: TBindingsList
Methods = <>
OutputConverters = <>
Left = 252
Top = 13
object BindExprItems1: TBindExprItems
Category = 'Expressions de liaison'
ControlComponent = Conversion
SourceComponent = Euros
FormatExpressions = <
item
ControlExpression = 'Text'
SourceExpression = 'Owner.Convertisseur.ConvertirEUR2USD(Text)'
end
item
ControlExpression = 'Owner.Convertisseur.ConvertirUSD2EUR(Text)'
SourceExpression = 'text'
Direction = dirControlToSource
end>
ClearExpressions = <>
NotifyOutputs = False
end
end
end

PAS

unit U_MainConvertisseur;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.Bind.EngExt, Vcl.Bind.DBEngExt,
System.Rtti, System.Bindings.Outputs, Vcl.Bind.Editors, Data.Bind.Components;

type
TConvertisseur = Class(TComponent)
private
public
function ConvertirEUR2USD(const Valeur : String) : String;
function ConvertirUSD2EUR(const Valeur : String) : String;
end;

TForm9 = class(TForm)
Euros: TEdit;
Label1: TLabel;
Conversion: TEdit;
BindingsList1: TBindingsList;
BindExprItems1: TBindExprItems;
DateXML: TLabel;
Label2: TLabel;
procedure EurosChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
Convertisseur : TConvertisseur;
end;

var
Form9: TForm9;
Const taux = 1.10;

implementation

{$R *.dfm}

procedure TForm9.EurosChange(Sender: TObject);
begin
BindingsList1.Notify(Sender,'');
end;

{ TConvertisseur }

function TConvertisseur.ConvertirEUR2USD(const Valeur: String): String;
var f : Extended;
begin
result:='';
if TryStrToFloat(Valeur,f)
then result:=FloatToStr(f*taux);

end;

function TConvertisseur.ConvertirUSD2EUR(const Valeur: String): String;
var f : Extended;
begin
result:='';
if TryStrToFloat(Valeur,f)
then result:= FloatToStr(f/taux);
end;

procedure TForm9.FormCreate(Sender: TObject);
begin
Convertisseur:=TConvertisseur.Create(Self);
end;

end.

Plusieurs constatations s'imposent :

1. n'avoir qu'un taux fixe en constante est pratique pour les essais, mais frustrant dans le cadre d'un programme plus abouti ;

2. n'avoir qu'une seule possibilité de conversion EURUSD manque un peu de charme ;

3. le code source est loin d'être très lisible et c'est en partie dû à l'EDI, mais quand même ! La partie concernant l'objet TConvertisseur se situe entre deux constituants de l'interface graphique ;

4. je n'ai introduit aucun commentaire dans le code ;

5. j'ai fait l'impasse sur les quelques noms de composants non renseignés et donc fournis par défaut, tels que Label1, Label2, Form8...

III - Conclusion

Nous avons vu qu'une liaison peut être bidirectionnelle, que l'on pouvait utiliser des méthodes internes mais aussi, avec un peu de code, des fonctions et propriétés d'objets. C'est une introduction toute trouvée vers des liaisons à des données. La seconde partie sur les effets de bord abordera l'utilisation de l'interpréteur d'expressions.

Toutefois, je suis sûr que vous en êtes encore sur votre faim ! Cette expérimentation est très jolie, mais il est frustrant de ne pas avoir un programme plus abouti, surtout après les constations de la dernière partie.

La mise en forme du programme final débordant stricto sensu le cadre des LiveBindings, j'ai préféré en faire un chapitre à part.

IV - Finalisation du programme

IV-A - Isoler la partie interface de la partie métier

En fait, il s'agit d'isoler la partie concernant l'objet TConvertisseur de la partie du programme concernant la forme. Cela aura un effet immédiat : alléger la lecture du programme.

Nous allons donc créer une nouvelle unité qui va contenir tout ce qui s'y rattache. Quelques opérations de couper-coller plus tard, nous obtenons :

Unité Métier

unit Convertisseur_Objet;

interface

uses System.SysUtils, System.Classes;

type
TConvertisseur = Class(TComponent)
private
public
function ConvertirEUR2USD(const Valeur : String) : String;
function ConvertirUSD2EUR(const Valeur : String) : String;
end;

implementation

function TConvertisseur.ConvertirEUR2USD(const Valeur: String): String;
var f : Extended;
begin
result:='';
if TryStrToFloat(Valeur,f)
then result:=FloatToStr(f*taux);
end;

function TConvertisseur.ConvertirUSD2EUR(const Valeur: String): String;
var f : Extended;
begin
result:='';
if TryStrToFloat(Valeur,f)
then result:= FloatToStr(f/taux);
end;

end.

la variable Taux est encore la constante de l'unité principale, mais je vais revenir dessus un peu plus tard.

Le code source de l'unité principale est réduit. Avant d'enregistrer mon travail sous un nouveau nom, j'en profite pour utiliser une des nouvelles fonctionnalités (depuis Berlin 10.1 update 2 je crois) de l'EDI : les modifications rapides, et ainsi tordre le cou du point 5 sur les noms par défaut laissés tels quels.

Modification rapide

(cliquez ici pour aggrandir l'image)

Je remplace également le libellé « Dollar US » et le remplace par un TComboBox qui permettra de choisir la devise (point numéro 2). Il y a donc peu de changements concernant la partie interface.

Nouveau DFM

object InterfaceUtilisateur: TInterfaceUtilisateur
Left = 0
Top = 0
Caption = 'Convertisseur'
ClientHeight = 134
ClientWidth = 343
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object LabelEuro: TLabel
Left = 24
Top = 43
Width = 27
Height = 13
Caption = 'Euros'
end
object DateXML: TLabel
Left = 24
Top = 8
Width = 23
Height = 13
Caption = 'Date'
end
object Euros: TEdit
Left = 80
Top = 40
Width = 121
Height = 21
TabOrder = 0
Text = '0'
OnChange = EurosChange
end
object Conversion: TEdit
Tag = 1
Left = 80
Top = 77
Width = 121
Height = 21
TabOrder = 1
Text = '0'
OnChange = EurosChange
end
object Devise: TComboBox
Left = 24
Top = 77
Width = 50
Height = 21
TabOrder = 2
Text = 'USD'
end
object ListeDesLiaisons: TBindingsList
Methods = <>
OutputConverters = <>
Left = 252
Top = 13
object LiaisonConversion_Euro: TBindExprItems
Category = 'Expressions de liaison'
ControlComponent = Conversion
SourceComponent = Euros
FormatExpressions = <
item
ControlExpression = 'Text'
SourceExpression = 'Owner.Convertisseur.ConvertirEUR2USD(Text)'
end
item
ControlExpression = 'Owner.Convertisseur.ConvertirUSD2EUR(Text)'
SourceExpression = 'text'
Direction = dirControlToSource
end>
ClearExpressions = <>
NotifyOutputs = False
end
end
end

Les modifications sont un peu plus notables dans le source de l'unité principale :

Nouveau PAS

unit Convertisseur_MainUI;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.Bind.EngExt, Vcl.Bind.DBEngExt,
System.Rtti, System.Bindings.Outputs, Vcl.Bind.Editors, Data.Bind.Components,
Vcl.StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
IdHTTP, Xml.xmldom, Xml.XMLIntf, Xml.XMLDoc,
System.RegularExpressions, Convertisseur_Objet;

type
TInterfaceUtilisateur = class(TForm)
Euros: TEdit;
LabelEuro: TLabel;
Conversion: TEdit;
ListeDesLiaisons: TBindingsList;
LiaisonConversion_Euro: TBindExprItems;
DateXML: TLabel;
Devise: TComboBox;
procedure EurosChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
Convertisseur : TConvertisseur;
end;

var
InterfaceUtilisateur: TInterfaceUtilisateur;
Const taux = 1.10;

implementation

{$R *.dfm}

procedure TInterfaceUtilisateur.EurosChange(Sender: TObject);
begin
ListeDesLiaisons.Notify(Sender,'');
end;

procedure TInterfaceUtilisateur.FormCreate(Sender: TObject);
begin
Convertisseur:=TConvertisseur.Create(Self);
end;

end.

Le plus important est ne pas oublier d'indiquer, dans la partie interface, l'utilisation de notre unité métier !

IV-B - Les taux de change

Jusqu'à présent, Taux était une constante de l'unité principale. Nous pourrions bien sûr mettre cette constante dans l'unité métier, mais il est préférable d'améliorer notre objet en lui ajoutant une propriété qui contiendra le taux de conversion Euro vers Dollar.

Ajout Propriété

unit Convertisseur_Objet;

interface

uses System.SysUtils, System.Classes;

type
TConvertisseur = Class(TComponent)
private
function GetConversionEuro2USD : Extended;
public
function ConvertirEUR2USD(const Valeur : String) : String;
function ConvertirUSD2EUR(const Valeur : String) : String;
property CoursEuro: Extended read GetConversionEuro2USD;
end;

implementation

function TConvertisseur.ConvertirEUR2USD(const Valeur: String): String;
var f : Extended;
begin
result:='';
if TryStrToFloat(Valeur,f)
then result:=FloatToStr(f*CoursEuro);
end;

function TConvertisseur.ConvertirUSD2EUR(const Valeur: String): String;
var f : Extended;
begin
result:='';
if TryStrToFloat(Valeur,f)
then result:= FloatToStr(f/CoursEuro);
end;

function TConvertisseur.GetConversionEuro2USD: Extended;
begin
Result:=1.10; // notre constante
end;

end.

Nous avançons sauf que, pour l'instant, nous ne répondons pas aux points 1 et 2 soulevés juste avant la conclusion : le taux est fixe et ne concerne que les échanges entre dollars et euros.

Comme on peut trouver les cours journaliers sur internet, il devrait donc être possible de récupérer ces derniers. Je vais utiliser le site officiel de la BCE :

BCE cours journalier

On nous y propose quatre choix de téléchargement, dont deux vont plus particulièrement attirer notre attention : les formats XML et CSV. Notez que ces fichiers sont mis à jour aux environs de 16 h 00 CET les jours ouvrables, sauf en cas de fermeture du TARGET.

Page de téléchargement des cours journaliers

(cliquez ici pour aggrandir l'image)

IV-C - Récupérer un fichier

Je vais rapidement passer sur cet aspect de l'application, car ce n'est pas le sujet du tutoriel. Je vais utiliser les composants Indy fournis, et plus particulièrement TidHttp. FAQ Delphi sur IdHTTP

Récupération de fichier

function GetFichierCours: String;
var FStream : TFileStream;
Emplacement : String;
begin
Emplacement:=TPath.GetPublicPath; //obtenir le dossier public
FStream:=TFileStream.Create(TPath.Combine(Emplacement,'eurofxref-daily.xml'), fmCreate);
try
With TIdHttp.Create(nil) do
try
try
// adresse constante, qui pourrait être pourtant sujette
// à changements. Prévoir une adresse passée par paramètre
// serait peut-être mieux
Get('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml', FStream);
result:=FormatDateTime('aaaa-mm-jj',date); // attention il ne s'agit pas de la date du cours
except
on e: Exception do
Result:='Pas de Connexion à la BCE';
end;
finally
Free;
end;
finally
FStream.Free;
end;
end;

Récupérer un fichier de cette manière permet de toujours avoir un fichier de référence au cas où il serait impossible de charger le fichier du jour pour une raison quelconque. Une vérification de la date du fichier pourrait éviter un téléchargement à chaque lancement de l'application. Attention toutefois : la date du cours n'est pas forcément la date du fichier.

A contrario, nous avons donc ce que je traiterais d'une « petite pollution » dans notre espace de stockage.

Bien sûr, la même chose peut se faire pour le format CSV. Toutefois, nous avons à faire à un fichier compressé (ZIP) qu'il va donc falloir décompresser pour obtenir ce fameux fichier CSV. Depuis peu, les versions de Delphi ont une unité System.Zip qui permettra de le faire. Pour la suite, je me contenterai du fichier XML.

IV-D - « XML, vous avez dit XML ? »

Premier face à face ou pas avec ce genre de fichier ? RAD Studio peut nous aider à traiter les documents XML et nous fournir, par l'intermédiaire d'un « Expert de liaison de données XML », tout le code nécessaire pour traiter le fichier téléchargé.

Pour obtenir la génération de l'unité de traitement :

1. créez une fiche vierge ;

2. posez un composant TXMLDocument ;

3. indiquez le nom du fichier préalablement téléchargé ;

4. cliquez avec le bouton droit de la souris sur le composant et utilisez l'expert.

Malheureusement, l'expert ne réussit pas à nous fournir une unité totalement utilisable pour le document eurofxref-daily.xml. Si l'on arrive facilement à accéder aux informations générales du document (envoyeur, sujet), il est impossible d'accéder à ce qui nous intéresse le plus : les cours de conversion. Qui est le coupable ? Soit le gabarit utilisé par la BCE, soit l'expert qui n'arrive pas à comprendre ce gabarit. Je vous laisse le choix du verdict.

Notons que cela aurait fait très propre et très professionnel !

Sans m'étendre trop sur le sujet, il est toujours possible de modifier un peu le fichier puis de réutiliser l'expert pour obtenir une unité qui exploitera parfaitement le XML. Voir Commentez . Toutefois, cela déborde largement d'un niveau débutant, voire confirmé.

IV-E - Ma solution pour exploiter le fichier

L'objectif est d'obtenir une liste de couples de valeurs (Devise, Cours) et au passage la date de publication de cette liste, soit, en étudiant le document XML, tout ce qui est compris entre les balises <Code> et </Code>. Il me faut donc trouver un moyen pour n'avoir que cette partie en mémoire.

Au lieu d'utiliser un TfileStream, je vais utiliser un TMemoryStream au moment du téléchargement.

Après récupération du fichier, je fais une recherche de position des deux balises pour modifier le contenu de mon fichier.

var MStream : TMemoryStream;
EuroDaily : IXMLDocument;
posDebutcube,posFinCube : Integer;
// ...
begin
EuroDaily:=TXMLDocument.Create(Self);

MStream:=TMemoryStream.Create;
try
With TIdHttp.Create(nil) do
try
try
// récupération du fichier via internet
Get('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml', MStream);
fStream.Position:=0;
EuroDaily.LoadFromStream(MStream);
// recherche de position des balises
posDebutCube:=Pos('<Cube>',EuroDaily.XML.Text);
posFinCube:=Pos('</Cube>',EuroDaily.XML.Text)+7;
// modification du contenu (uniquement ce qui est entre les deux balises EuroDaily.XML.Text:=Copy(EuroDaily.XML.Text,posDebutCube,posFinCube-PosDebutCube);
// ...
finally
Free;
end;
finally
EuroDaily.Free;
MStream.Free;
End ;

À ce stade, j'obtiens quelque chose de plus compact, mais d'encore peu exploitable.

résultat

<Cube time="2017-03-31">
<Cube currency="USD" rate="1.0691"/>
<Cube currency="JPY" rate="119.55"/>
<Cube currency="BGN" rate="1.9558"/>
<Cube currency="CZK" rate="27.030"/>
<Cube currency="DKK" rate="7.4379"/>
<Cube currency="GBP" rate="0.85553"/>
<Cube currency="HUF" rate="307.62"/>
<Cube currency="PLN" rate="4.2265"/>
<Cube currency="RON" rate="4.5525"/>
<Cube currency="SEK" rate="9.5322"/>
<Cube currency="CHF" rate="1.0696"/>
<Cube currency="NOK" rate="9.1683"/>
<Cube currency="HRK" rate="7.4465"/>
<Cube currency="RUB" rate="60.3130"/>
<Cube currency="TRY" rate="3.8894"/>
<Cube currency="AUD" rate="1.3982"/>
<Cube currency="BRL" rate="3.3800"/>
<Cube currency="CAD" rate="1.4265"/>
<Cube currency="CNY" rate="7.3642"/>
<Cube currency="HKD" rate="8.3074"/>
<Cube currency="IDR" rate="14237.94"/>
<Cube currency="ILS" rate="3.8853"/>
<Cube currency="INR" rate="69.3965"/>
<Cube currency="KRW" rate="1194.54"/>
<Cube currency="MXN" rate="20.0175"/>
<Cube currency="MYR" rate="4.7313"/>
<Cube currency="NZD" rate="1.5309"/>
<Cube currency="PHP" rate="53.658"/>
<Cube currency="SGD" rate="1.4940"/>
<Cube currency="THB" rate="36.724"/>
<Cube currency="ZAR" rate="14.2404"/>

J'ai encore de nombreuses balises, ce qui est normal, mais aussi une mise en forme (tabulations, sauts de lignes). Fort de mes tentatives du chapitre II.B, je vais tout d'abord remplacer les points décimaux par le séparateur décimal utilisé par le poste :

EuroDaily.XML.Text:=StringReplace(EuroDaily.XML.Text,'.',FormatSettings.DecimalSeparator,[rfReplaceAll]);

Reste à exploiter ce résultat ! Depuis plusieurs années déjà, nous avons la possibilité d'utiliser les expressions régulières. Récemment, c'est devenu une de mes marottes, et c'est ce que je vais utiliser pour récupérer les informations en tant que résultat de ma fonction pour la date et dans un TStringList pour les cours :

Utilisation RegEx

regExp:=TRegEx.Create('.*<Cube\stime="(?<Date>.{10}).*|.*<Cube\scurrency="(?<Devise>.{3}).*="(?<Cours>.*)"/>.*');
Matches:=regExp.Matches(EuroDaily.XML.Text);
for unMatch in Matches do
begin
if Trim(unMatch.Groups.Item['Date'].Value)<>''
then result:=unMatch.Groups.Item['Date'].Value
else           Liste.AddPair(unMatch.Groups.Item['Devise'].Value,unMatch.Groups.Item['Cours'].Value);

end;

Contenu de Liste au 31 Mars 2017

USD=1,0691
JPY=119,55
BGN=1,9558
CZK=27,030
DKK=7,4379
GBP=0,85553
HUF=307,62
PLN=4,2265
RON=4,5525
SEK=9,5322
CHF=1,0696
NOK=9,1683
HRK=7,4465
RUB=60,3130
TRY=3,8894
AUD=1,3982
BRL=3,3800
CAD=1,4265
CNY=7,3642
HKD=8,3074
IDR=14237,94
ILS=3,8853
INR=69,3965
KRW=1194,54
MXN=20,0175
MYR=4,7313
NZD=1,5309
PHP=53,658
SGD=1,4940
THB=36,724
ZAR=14,2404

Un tri s'impose pour avoir un accès rapide aux couples de valeurs (Liste.Sort).

Toutes ces étapes peuvent être testées dans un premier temps dans l'unité principale à l'aide de composants (mémo et bouton), mais pour la finalisation du projet, c'est l'unité métier qui va récupérer ce code.

Il nous faudra pour cela ajouter à l'objet :

1. une propriété Liste de type TStringList (ne pas oublier de créer/libérer l'objet) ;

2. une propriété qui contiendra le texte de la date (<Cube time="2017-03-31">).

À la création de l'interface, après la création de l'objet TConvertisseur, nous pourrons facilement mettre à jour les zones de notre interface utilisateur : la ComboBox et l'indication de la date du cours.

Le choix d'une TStringList s'impose en première instance si l'on regarde le contenu obtenu : un ensemble de couples « nom=valeur » que cette classe sait parfaitement gérer. Toutefois, j'utiliserai dans mon programme final un TDictionnary tout aussi efficace et qui me permettra « d'oublier » de libérer son utilisation.

Tout est maintenant en place pour finaliser le programme que vous pourrez retrouver Sources Developpez

V - Remerciements

Je tiens tout d'abord à citer Cary Jensen et ses différentes vidéos sur ce sujet, sources de référence principales de ce tutoriel.

CodeRage 7 - Cary Jensen - LiveBindings Expressions and Side Effects

Différentes videos de Cary-Jensen sur le sujet

Tous mes remerciements vont à Gilles (gvasseur58) et Jean-Luc (Alcatîz) pour leur relecture et conseils techniques et à Claude (ClaudeLeloup) pour ses corrections orthographiques et grammaticales. Enfin une pensée particulière à mon « cobaye » Nabil (Nabil74) qui, tel le Candide de Voltaire, par ses questions, m'a permis d'améliorer mes arguments et programmes.

VI -

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article

Retour à l'article