IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Conversions de mesures


précédentsommairesuivant

IV. Création de conversions personnalisées

Bien que l'unité StdConvs.pas recense un bon nombre de familles et d'unités de mesure, il est possible que vous ayez besoin de compléter cette collection.

IV-A. Conversion personnalisée simple

Imaginons un instant que suite à une nouvelle étude sur le continent imaginaire : Atlantide, il ait été découvert que l'unité de mesure atlante était le « pied atlante » et que celui-ci mesurait par exemple 18,36 cm.

Mesure totalement imaginaire…, en passant, si les mythes et légendes vous intéressent, allez faire un tour sur www.mythorama.com dont le webmaster est un delphinaute, vous ne serez pas déçus.

Nous allons créer une nouvelle application et recenser la nouvelle unité de mesure atlante dans la famille des distances.

Ci-dessous le source de l'unité de la fiche principale :

 
Sélectionnez
unit uCustomConv; 

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ConvUtils, StdConvs; 

type
  TfrmCustomConvert = class(TForm)
  private
    { Déclarations privées }
  public
    { Déclarations publiques }
  end;
 
var
  frmCustomConvert: TfrmCustomConvert;
  duPiedAtlante: TConvType;
 
implementation

{$R *.dfm}
 
initialization
  duPiedAtlante := RegisterConversionType(cbDistance,'Pieds Atlantes', 0.1836);
end.

Cette fois-ci, nous ajoutons les références aux unités de conversion (ConvUtils et StdConvs) dans la clause uses de la section interface et non implémentation. Pourquoi ? Car nous avons déclaré la nouvelle variable duPiedAtlante avant la clause implémentation de façon à ce que cette variable soit éventuellement accessible à d'autres unités.

Le facteur de conversion est calculé par rapport à l'unité de la famille des distances qui a un facteur 1, ici le mètre. Nous le vérifions dans l'unité StdConvs, extrait :

 
Sélectionnez
cbDistance := RegisterConversionFamily(SDistanceDescription);
 
  { Distance's various conversion types }
  duMicromicrons := RegisterConversionType(cbDistance, SMicromicronsDescription, 1E-12);
  duAngstroms := RegisterConversionType(cbDistance, SAngstromsDescription, 1E-10);
  duMillimicrons := RegisterConversionType(cbDistance, SMillimicronsDescription, 1E-9);
  duMicrons := RegisterConversionType(cbDistance, SMicronsDescription, 1E-6);
  duMillimeters := RegisterConversionType(cbDistance, SMillimetersDescription, 0.001);
  duCentimeters := RegisterConversionType(cbDistance, SCentimetersDescription, 0.01);
  duDecimeters := RegisterConversionType(cbDistance, SDecimetersDescription, 0.1);
  duMeters := RegisterConversionType(cbDistance, SMetersDescription, 1);

On peut également noter que le dérecensement, bien que prévu et exécuté logiquement dans la clause finalization, n'est pas obligatoire. Le cas échéant, on aurait :

 
Sélectionnez
initialization
 duPiedAtlante := RegisterConversionType(cbDistance,'Pieds Atlantes', 0.1836);
finalization
 UnregisterConversionType(duPiedAtlante);
end.

Maintenant, passons à la pratique et posons deux TLabeledEdit de façon à ce que l'on puisse implémenter une conversion des mètres vers des pieds atlantes et inversement (dans les événements OnKeyDown des TLabeledEdit) :

 
Sélectionnez
procedure TfrmCustomConvert.edMetresKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
 metres, piedsatlantes: double;
begin
 if key <> VK_RETURN then exit;
 metres := StrToFloat(edMetres.Text);
 piedsatlantes := Convert(metres, duMeters, duPiedAtlante);
 edPiedsAtlantes.Text := FloatToStr(piedsatlantes);
end;

Le code dans le TLabeledEdit des « Pieds Atlantes » est sensiblement le même en intervertissant les variables.

IV-B. Conversion personnalisée procédurale

Nous allons continuer avec des conversions personnalisées un peu plus compliquées. Écartons-nous du domaine scientifique et dirigeons-nous du côté du domaine sportif (quoique le sport prenne de plus en plus un caractère scientifique, dans le bon comme dans le mauvais…) et plus précisément des cotations de performances. En athlétisme par exemple, les performances de chacune des dix épreuves du décathlon sont cotées à la table des épreuves combinées de l'IAAF (International Association of Athletics Federations). À une performance correspond un nombre de points.

Des formules existent pour trouver les points correspondants à une performance.

Nous allons prendre l'exemple du saut à la perche et la formule de calcul des points dédiée :

 
Sélectionnez
points = 0.2797.((perf en cm - 100)^1.35)

Nous allons recenser une nouvelle famille de conversion : cbDecathlon ainsi que le premier type : decaPerche. Nous continuons avec la même unité que pour l'exemple précédent.

La déclaration de la famille cbDecathlon et du type duPerche :

 
Sélectionnez
var
  frmCustomConvert: TfrmCustomConvert;
  duPiedAtlante: TConvType;
  cbDecathlon: TConvFamily;
  decaPerche: TconvType;
  decaPoint: TConvType;
 
implementation
//...
//...
 
initialization
 duPiedAtlante := RegisterConversionType(cbDistance,'Pieds Atlantes', 0.1836)
 
 cbDecathlon := RegisterConversionFamily('Décathlon');
 decaPerche := RegisterConversionType(cbDecathlon, 'Perche', PerfPercheToPoints, PointsToPerfPerche);
 decaPoint := RegisterConversionType(cbDecathlon, 'Points', 1);

Pour decaPoint, l'unité du point de cotation est 1 donc rien de bien compliqué.

Concernant decaPerche, au lieu de passer en paramètre un facteur multiplicateur, nous avons passé deux variables de type TConversionProc qui en fait pointeront sur les fonctions respectives chargées de la conversion.

Rappelons la définition de TConversionProc :

 
Sélectionnez
TConversionProc = function(const AValue: Double): Double;

PerfPercheToPoints et PointsToPerfPerche doivent donc être deux fonctions de ce type. Nous les implémentons dans la section… implémentation.

 
Sélectionnez
implementation

{$R *.dfm}
 
uses Math;
 
function PerfPercheToPoints(const AValue: double): double;
begin
  result := trunc(0.2797 * (Power(AValue - 100, 1.35)));
end;
 
function PointsToPerfPerche(const AValue: double): double;
begin
  result := 0; // ??? à calculer... aie faut que je révise mes maths :-(
end;

Comme pour les autres exemples, pour calculer les points d'une perf à la perche en cm, vous implémenterez la conversion :

 
Sélectionnez
points := Convert(perf, decaPerche, decaPoint);

L'inconvénient de cette méthode est que l'on doit implémenter deux fonctions de conversion pour chaque épreuve du décathlon ce qui est assez lourd sachant que les formules respectives sont quasi identiques et paramétrables.

Voici la liste des formules pour les épreuves du décathlon :

100m

Points = 25.4347.((18 - temps en sec)^1.81)

Longueur

Points = 0.14354.((perf en cm - 220)^1.4)

Poids

Points = 51.39.((perf en m - 1.5)^1.05)

Hauteur

Points = 0.8465.((perf en cm - 75)^1.42)

400m

Points = 1.53775.((82 - temps en sec)^1.81)

110m haies

Points = 5.74352.((28.5 - temps en sec)^1.92)

Disque

Points = 12.91.((perf en m - 4)^1.1)

Perche

Points = 0.2797.((perf en m - 100)^1.35)

Javelot

Points = 10.14.((perf en cm - 7)^1.08)

1500m

Points = 0.03768.((480 - temps en sec)^1.85)

On constate que la structure est similaire pour toutes les formules :

 
Sélectionnez
points = const1 . ( ( perf - const2 ) ^ const3 )

et que selon la famille de la performance (Temps ou Distance), le signe des paramètres perf et const2 sont inversés :

 
Sélectionnez
points = const1 . ( ( const2 - perf ) ^ const3 )

D'autre part, la perf est exprimée en centimètres lorsqu'il s'agit d'un saut et en mètres lorsqu'il s'agit d'un lancer. Pour simplifier, nous indiquerons à l'utilisateur dans quelle unité de mesure il doit saisir la performance.

Il serait intéressant de créer une classe générique, qui permettrait d'implémenter une seule fois le calcul de conversion perf --> points quelle que soit l'épreuve et d'implémenter une méthode de recensement de cette classe en prenant en compte les paramètres propres à chaque épreuve. C'est ce que nous allons faire dans la rubrique suivante.

IV-C. Création de la classe de conversion Décathlon

Dans ce cas, nous devons diriger les méthodes de conversion ToCommon et FromCommon vers des fonctions implémentant nos formules de calcul dédiées utilisant les paramètres const1, const2 et const3. Nous ajoutons également une variable qui permettra d'indiquer si la valeur à convertir fait partie de la famille des distances ou de la famille des temps, car la formule de calcul diffère en fonction de cette particularité.

Nous allons créer une nouvelle application intégrant une classe héritant de TConvTypeFactor que l'on appellera TConvTypeDecathlon puis surcharger l'implémentation du constructeur et des fonctions ToCommon et FromCommon.

Interface :

 
Sélectionnez
TConvTypeDecathlon = class(TConvTypeFactor)
  private
    fConst1,
      fConst2,
      fConst3: double;
    fTypePerf : TConvFamily;
  public
    constructor Create(const AFamily: TConvFamily; const ADescription: string;
      Const1, Const2, Const3: double; TypePerf : TConvFamily);
    function ToCommon(const AValue: Double): Double; override;
    function FromCommon(const AValue: Double): Double; override;
  end;

Implémentation :

 
Sélectionnez
constructor TConvTypeDecathlon.Create(const AFamily: TConvFamily;
  const ADescription: string; Const1, Const2, Const3: double;
  TypePerf : TConvFamily);
begin
  inherited Create(AFamily, ADescription, 1);
  fConst1 := Const1;
  fConst2 := Const2;
  fConst3 := Const3;
  fTypeEpreuve := TypeEpreuve;
end;
 
function TConvTypeDecathlon.ToCommon(const AValue: Double): Double;
begin
  case fTypePerf of
    cbDistance:
      result := trunc(fConst1 * (Power(AValue - fConst2, fConst3)));
    cbTime:
      result := trunc(fConst1 * (Power(fConst2 - AValue, fConst3)));
  end;  
end;
 
function TConvTypeDecathlon.FromCommon(const AValue: Double): Double;
begin
   // à vérifier
   Result := Avalue ;
end;

La classe générique de conversion pour toutes les épreuves du décathlon est prête. Il reste à recenser la famille et chacune des épreuves du décathlon.

Nous recenserons chaque épreuve du décathlon en spécifiant les paramètres const1, const2 et const3 et le type de la performance respective (temps ou distance). Nous implémenterons donc les fonctions de recensement adaptées.

Interface :

 
Sélectionnez
function RegisterDecathlonConversionType(const AFamily: TConvFamily;
  const ADescription: string;
  Const1, Const2, Const3: double; TypePerf: TConvFamily): TConvType; overload;
 
function RegisterDecathlonConversionType(const AFamily: TConvFamily;
  const ADescription: string; const AToCommonProc,
  AFromCommonProc: TConversionProc;
  Const1, Const2, Const3: double; TypePerf: TConvFamily): TConvType; overload;

Implémentation :

 
Sélectionnez
function RegisterDecathlonConversionType(const AFamily: TConvFamily;
  const ADescription: string;
  Const1, Const2, Const3: double; TypePerf: TConvFamily): TConvType; overload;
var
  EpreuveInfo: TConvTypeInfo;
begin
  EpreuveInfo := TConvTypeDecathlon.Create(AFamily, ADescription,
    Const1, Const2, Const3, TypePerf);
  if not RegisterConversionType(EpreuveInfo, Result) then
  begin
    EpreuveInfo.Free;
    RaiseConversionRegError(AFamily, ADescription);
  end;
end;
 
function RegisterDecathlonConversionType(const AFamily: TConvFamily;
  const ADescription: string; const AToCommonProc,
  AFromCommonProc: TConversionProc): TConvType; overload;
begin
  Result := RegisterConversionType(AFamily, ADescription,
    AToCommonProc, AFromCommonProc);
end;

Notre classe et nos méthodes de recensement sont prêtes, déclarons la famille du décathlon et les épreuves en tant que variables globales de l'unité et recensons-les dans la clause initialization de l'unité :

 
Sélectionnez
unit uDecathlon;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ConvUtils;

type
  // ...
 
var
  frmDecathlon: TfrmDecathlon;
 
  cbDecathlon: TConvFamily;
  deca100m,
  decaLongueur,
  decaPoids,
  decaHauteur,
  deca400m,
  deca110haies,
  decaDisque,
  decaPerche,
  decaJavelot,
  deca1500m: TConvType;
  decaPoint: TConvType;
 
implementation

// ...
 
initialization
  cbDecathlon := RegisterConversionFamily('Decathlon');
  deca100m := RegisterDecathlonConversionType(cbDecathlon, '100M', 25.4347, 18, 1.81, cbTime);
  decaLongueur := RegisterDecathlonConversionType(cbDecathlon, 'LONGUEUR', 0.14354, 220, 1.4, cbDistance);
  decaPoids := RegisterDecathlonConversionType(cbDecathlon, 'POIDS', 51.39, 1.5, 1.05, cbDistance);
  decaHauteur := RegisterDecathlonConversionType(cbDecathlon, 'HAUTEUR', 25.4347, 18, 1.81, cbDistance);
  deca400m := RegisterDecathlonConversionType(cbDecathlon, '400M', 25.4347, 18, 1.81, cbTime);
  deca110haies := RegisterDecathlonConversionType(cbDecathlon, '110MHAIES', 5.74352, 28.5, 1.92, cbTime);
  decaDisque := RegisterDecathlonConversionType(cbDecathlon, 'DISQUE', 12.91, 4, 1.1, cbDistance);
  decaPerche := RegisterDecathlonConversionType(cbDecathlon, 'PERCHE', 0.2797, 100, 1.35, cbDistance);
  decaJavelot := RegisterDecathlonConversionType(cbDecathlon, 'JAVELOT', 10.14, 7, 1.08, cbDistance);
  deca1500m := RegisterDecathlonConversionType(cbDecathlon, '1500M', 0.03768, 480, 1.85, cbTime);
  decaPoint := RegisterConversionType(cbDecathlon, 'POINTS', 1);
end.

Ensuite nous disposons un couple de TLabeledEdit pour saisir la performance et d'un TLabel pour afficher le nombre de points, et ce pour chaque épreuve (fig. 13). Les saisies de performances se feront en mètres pour les distances et en secondes pour les temps, pour des raisons de simplicité.

Il aurait été judicieux de créer une frame (TFrame) pour représenter chaque épreuve ainsi que de fournir un composant d'édition des performances affichant un masque de saisie en fonction de l'épreuve, ce que nous écartons afin de nous concentrer plutôt sur la conversion.

Nous implémentons la conversion de la performance vers le nombre de points correspondant lorsque l'on quitte le champ d'édition, donc en gérant l'événement OnExit. Voici un extrait pour les trois premières épreuves :

 
Sélectionnez
procedure TfrmDecathlon.ed100mExit(Sender: TObject);
begin
  lbl100m.Caption := FloatToStr(Convert(StrToFloat(ed100m.Text), deca100m, decaPoint));
end;
 
procedure TfrmDecathlon.edLongueurExit(Sender: TObject);
begin
  lblLongueur.Caption :=FloatToStr(Convert(StrToFloat(edLongueur.Text), decaLongueur, decaPoint));
end;
 
procedure TfrmDecathlon.edPoidsExit(Sender: TObject);
begin
  lblPoids.Caption := FloatToStr(Convert(StrToFloat(edPoids.Text), decaPoids, decaPoint));
end;
 
// ...
Image non disponible
Figure 13

Les points correspondant aux performances sont maintenant calculés avec un seul appel à la fonction générique Convert. Notre objectif est atteint.

Bien sûr il nous reste du travail quant à l'élégance du code, et aussi au niveau du traitement des exceptions ici volontairement éludé.

Le framework de conversions Delphi propose aussi quelques fonctions utilitaires. Découverte…


précédentsommairesuivant

Copyright © 2002 Sylvain James. Aucune reproduction, même partielle, ne peut être faite de ce site ni 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.