LiveBindings de A à … Effets de bord

Implémenter des méthodes

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.

2 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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

Image non disponible
Concepteur visuel*

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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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... »*.

Image non disponible
Options contextuelles du TBindingsList*

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.

Image non disponible
Liaison manuelle Edit1 → Capture2*

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 :

 
Sélectionnez
procedure TForm8.Edit1Change(Sender: TObject);
begin
BindingsList1.Notify(Sender,'');
end;
  • Nouveau DFM :
DFM
Sélectionnez
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
Sélectionnez
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*.

Image non disponible
Liaison rapide Edit1 -> Capture3*

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
Sélectionnez
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
Sélectionnez
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.

Image non disponible
Convertisseur € -> USD

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
Sélectionnez
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.

Image non disponible
Ajout de la liaison *

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 *.

Image non disponible
Ajout d'expressions

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
Sélectionnez
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…

Image non disponible
Erreur à l'exécution

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 nombres, 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
Sélectionnez
 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
Sélectionnez
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 sûr 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
Sélectionnez
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
Sélectionnez
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

Vous pouvez retrouver le code ici. 

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
Sélectionnez
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.

Image non disponible
Modification rapide

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
Sélectionnez
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
Sélectionnez
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é
Sélectionnez
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 BCEBanque Centrale Européenne  :

BCE cours journalierCours de l'Euro

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 CETCommon European Time les jours ouvrables, sauf en cas de fermeture du TARGETSystème de gestion des échanges.

Image non disponible
Page de téléchargement des cours journaliers

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 IdHTTPFAQ Delphi sur IdHTTP

Récupération de fichier
Sélectionnez
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 affaire à 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 une discussion sur ce sujet1. 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.

 
Sélectionnez
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
Sélectionnez

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

 
Sélectionnez
 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
Sélectionnez
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 Image non disponibleici

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 vidéos de Cary-Jensen sur le sujet

Tous mes remerciements vont à Gilles (gvasseur58) et Jean-Luc (Alcatîz) pour leur relecture et leurs 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.

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.