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 :
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 :
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 :
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) :
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 :
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 :
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 :
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.
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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é :
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 :
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
;
// ...
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…