jeudi 2 novembre 2006

Comment créer ses propres classes Tfield ?

L’intérêt de la chose est de pouvoir intégrer des traitements spécifiques associés à une données spécifiques. Exemple : mon champ de type float est un peu plus qu'un float, il s'agit d'un montant.
On peut lui associer une valeur de TVA (calculée) et une devise. Autre exemple je souhaite différencier les champs concernant les désignations. Il s'agit là d’un champ string particulier. Bien entendu, je souhaite pouvoir exploiter ce genre de champ de façon automatique au moment de l'ouverture d'un dataset. Les exemples que je fourni concernent le Tquery.

Rappels sur les composants de classe Tdataset ou descendant:
Pour commencer au moment de l’ouverture on la construction d’un tableau de champ de type Tfield ou descendants. (TstringField, TfloatField, etc….) Ce tableau décrit la structure d’un enregistrement. Exemple : Cette procédure remplie la « Liste » avec la description de « Dataset »


procedure ListeClasseField(Liste:TStrings;Dataset:TDataset);
var
i:integer;
begin
Liste.Clear;
for i:=0 to Dataset.FieldCount-1 do
Liste.add(Dataset.Fields[i].Classname);
end;

Pour retrouver les classes de Tfield qui correspondent aux champs de la base, le dataset s’appuie sur la méthode protégée function TDataSet.GetFieldClass(FieldType: TFieldType): TFieldClass; qui retourne la classe du champ à contruire en fonction du type de champ.

La construction effective se trouve dans la classe TfieldDef dans la méthode CreateFieldComponent. Cette méthode construit les champs en faisant appel à la méthode GetFieldClass pour retrouver la classe du champ (TStringField, TfloatField, etc….)

Pour cela cette méthode de TFields fait appel au TDataset pour retrouver sa classe grâce à la fonction GetFieldClass(FieldType: TFieldType): TFieldClass; virtual;

Le problème rencontré est le suivant : la fonction GetFieldClass n’a qu’un seul paramètre : le type du champ. Ceci est insuffisant si l’on souhaite avoir plusieurs type de float différents dépendant du nom du champ. Pour contourner ce problème on peut réimplémenter la méthode CreateFields; virtual; lors de la préparation des champs, stocker le nom du champ dans une variable privée, et exploiter cette valeur lors de la recherche de la classe de type TField par la méthode TDataSet.GetFieldClass. Le principe de fonctionnement de la méthode CreateFields est simple : Il s’agit simplement d’un parcourt de la liste des champs à créer, puis d’un appel à CreateField pour chacun de ces champs. C’est juste avant cet appel que l’on peut mémoriser dans un ou plusieurs champs privés les caractéristiques intéressantes du champ à créer, pour ensuite appeler CreateField Voici un exemple de source qui construit un objet de type TStringField en s’appuyant sur le nom du champ.


On part du principe que tous les champs de la base qui stocke un nom s’appellent ‘NAME’. Bien entendu, si vous utiliser des noms de construction plus complexe dans votre base (avec prefixe, suffixe, etc…rien ne vous empêche d’implémenter l’algo qui va bien dans la méthode GetFieldClass. Si vous souhaitez plusieurs classes en fonction du nom de champ vous pouvez aussi utiliser un case of. ;)

unit MyQuery;
interface
uses
Windows, Messages, SysUtils, Classes, DB, DBTables;
type
TNameField=class(TStringField);
TMyQuery = class(TQuery)
private
fName:string;
protected
procedure CreateFields; override;
function GetFieldClass(FieldType: TFieldType): TFieldClass; override;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('OT', [TMyQuery]);
end;
{ TMyQuery }
procedure TMyQuery.CreateFields;
var
I: Integer;
procedure SetKeyFields;
var
Pos, j: Integer;
KeyFields, FieldName: string;
begin
KeyFields := PSGetKeyFields;
Pos := 1;
while Pos <= Length(KeyFields) do
begin
FieldName := ExtractFieldName(KeyFields, Pos);
for j := 0 to FieldCount - 1 do
if AnsiCompareText(FieldName, Fields[j].FieldName) = 0 then
begin
Fields[j].ProviderFlags := Fields[j].ProviderFlags + [pfInKey];
break;
end;
end;
end;
begin
if ObjectView then
begin
for I := 0 to FieldDefs.Count - 1 do
with FieldDefs[I] do
if (DataType <> ftUnknown) and
not ((faHiddenCol in Attributes) and not FIeldDefs.HiddenFields) then
begin
fName:=FieldDefs[I].DisplayName;
CreateField(Self);
end;
end
else
begin
for I := 0 to FieldDefList.Count - 1 do
with FieldDefList[I] do
if (DataType <> ftUnknown) and not (DataType in ObjectFieldTypes) and
not ((faHiddenCol in Attributes) and not FIeldDefs.HiddenFields) then
begin
fName:=FieldDefList[I].DisplayName;
CreateField(Self, nil, FieldDefList.Strings[I]);
end;
end;
SetKeyFields;
end;
function TMyQuery.GetFieldClass(FieldType: TFieldType): TFieldClass;
begin
if uppercase(fName)='NAME' then
result:=TNameField
else
result:=inherited GetFieldClass(FieldType);
end;
initialization
RegisterClass(TNameField);
end.