Bien sûr tous le monde connait les fichiers de configuration (.ini), qui sont bien pratiques et simples à mettre à jour. Cependant pour moi ils présentent deux inconvénients :

  • les paramètres sont regroupés pas sections, ce qui est parfois insuffisant pour les classer correctement
  • ils sont facilement modifiables, y compris par l'utilisateur trop curieux qui peut facilement les modifier et perturber ainsi le programme

Quels seront les avantages proposés par la solution MyBase :

  • Je vais proposer un niveau de plus au dessus des sections pour classer les paramètres
  • le fichier est stocké en XML, il restera visible en clair, facilement modifiable mais tout de même moins pour un utilisateur non avertit
  • On pourra facilement modifier le source pour enregistrer ce fichier en binaire de façon à le rendre même illisible à l'écran.

Description de la classe :

 TJNParameters = class
 private
   FConfigFile : TClientDataSet; /// Dataset contenant les paramètres
   FFileName   : String; /// Chemin + nom du fichier de configuration
   procedure CreateNewConfigFile;
 public
   constructor Create( aFileConfig:string);
   Destructor Destroy; override;
   function ReadSections( aModule: String):TStrings;
   function ReadSectionValues( aModule, aSection: String):TStrings;
   function ReadString( aModule, aSection, aKey, aDefault: String): String;
   function SectionExist( aModule, aSection : string):Boolean;
   function KeyExist( aModule, aSection, aKey : string):Boolean;
   procedure WriteString( aModule, aSection, aKey, aValue: String);
   procedure DeleteKey( aModule, aSection, aKey: String);
   procedure DeleteSection( aModule, aSection: string);
   procedure DeleteModule( aModule: string);
   procedure UpdateFile;
 end;

Cette classe proposent plusieurs méthodes permettant de gérer notre configuration, nous allons voir les plus interessantes.

Le constructeur Create :

 constructor TJNParameters.Create( aFileConfig:string);
 begin
   inherited Create;
 
   FFileName := aFileConfig;
 
   if not( FileExists( aFileConfig)) then
     CreateNewConfigFile;
 
   FConfigFile := TClientDataSet.Create( nil);
   FConfigFile.LoadFromFile( aFileConfig);
   FConfigFile.Open;
   FConfigFile.IndexFieldNames := 'ModuleName;SectionName;Parameter';
 end;

Dans ce constructeur, nous vérifions l'existence du fichier de configuration dont nous avons passé le chemin complet en paramètre. Si ce fichier n'existe pas il s'agit surement d'un nouveau fichier, nous appelons donc la méthode CreateNewConfigFile pour le créer. Ensuite une instance de TClientDataset est créée : FConfigFile. On charge ce dataset à partir du fichier aFileConfig, on l'ouvre et on précise quels seront les index de cette table (Le nom du module, le nom de la section et le nom du paramètre.
Voyons maintenant la méthode CreateNewConfigFile qui me semble être la plus interessante de cette classe :

 procedure TJNParameters.CreateNewConfigFile;
 var
   wCdsConfig : TClientDataSet;
 begin
   ForceDirectories( ExtractFilePath( FFileName));
 
   wCdsConfig := TClientDataSet.Create( nil);
 
   try
     wCdsConfig.FieldDefs.Add( 'ModuleName', ftString, 100);
     wCdsConfig.FieldDefs.Add( 'SectionName', ftString, 50);
     wCdsConfig.FieldDefs.Add( 'Parameter', ftString, 100);
     wCdsConfig.FieldDefs.Add( 'ParameterValue', ftMemo);
     wCdsConfig.FieldDefs.Add( 'DateParametre', ftDateTime);
 
     wCdsConfig.CreateDataSet;
 
     wCdsConfig.Open;
 
     wCdsConfig.SaveToFile( FFileName, dfXML);
   finally
     wCdsConfig.Free;
   end;
 end;

Voilà, cette méthode nous montre comment créer dynamiquement une table Mybase. Pour ce faire, une instance de TClientDataset est créée, puis chaque champs est ajouté avec sa description à l'aide de la méthode FieldDefs.Add. Ainsi nous pouvons voir que les champs suivants seront présents dans notre table :

  1. ModuleName : champ alphanumérique de 100 caractères contenant le nom du module auquel seront rattachés les sections
  2. SectionName : champ alphanuymérique de 50 caractères contenant le nom de la section à laquelle seront rattachés les paramètres
  3. Parameter : champ alphanumérique de 100 caractères contenant le nom du paramètre
  4. ParameterValue : champ texte contenant la valeur du paramètre
  5. DateParametre : champ DateTime contenant la date et l'heure de modification du paramètre

La méthode CreateDataset est ensuite invoquée. C'est en fait ce que nous avons fait lors du précédent billet en cliquant droit sur le composant TClientDataset et en lui demandant de créer un ensemble de données. Nous ouvrons ensuite ce dataset et invoquons la méthode SaveToFile pour créer cette table physiquement sur le disque dur. Le format dfXML est utilisé, mais il est tout à fait possible de le changer, voir même de le rendre paramétrable. Bien entendu nous n'oublions pas de libérer notre instance de TClientDataset (wCdsConfig).

Viennent ensuite les méthodes permettant de manipuler nos paramètres :

  • la fonction KeyExist permettant de vérifier qu'un paramètre existe bien
  • la fonction SectionExist permettant de vérifier l'existence d'une section
  • la fonction ReadSections retourne la liste des sections d'un module dans un TStrings
  • la fonction ReadSectionValues retourne la liste des paramètres d'une section et leur valeur dans un TStrings.
  • la fonction ReadString retourne la valeur d'un paramètre particulier
  • la procédure WriteString permet de créer ou de modifier un paramètre
  • la procédure DeleteKey permet de supprimer un paramètre
  • la procédure DeleteSection permet de supprimer un section et tous les paramètres qu'elle contient
  • la procédure DeleteModule permet de supprimer un module ainsi que toute les sections qu'il contient
  • la procédure UpdateFile permet de rendre persistante les modifications effectuées

Toute ces méthode ne présentent aucune difficultés, elle utilisent simplement les méthodes proposées par le TClientDataset.

La méthode SectionExist nous montre comment lire un enregistrement dans un TClientDataset :

 function TJNParameters.SectionExist(aModule, aSection: string): Boolean;
 begin
   Result := FConfigFile.Locate( 'ModuleName;SectionName',
                         VarArrayOf( [ aModule, aSection]), []);
 end;

La méthode writeString nous montre comment écrire un enregistrement :

 procedure TJNParameters.WriteString(aModule, aSection, aKey, aValue: String);
 begin
   if FConfigFile.Locate( 'ModuleName;SectionName;Parameter',
                         VarArrayOf( [ aModule, aSection, aKey]), []) then
     FConfigFile.Edit
   else
     FConfigFile.Append;
 
   FConfigFile.FieldByName( 'ModuleName').AsString       := aModule;
   FConfigFile.FieldByName( 'SectionName').AsString      := aSection;
   FConfigFile.FieldByName( 'Parameter').AsString        := aKey;
   FConfigFile.FieldByName( 'ParameterValue').AsString   := aValue;
   FConfigFile.FieldByName( 'DateParametre').AsDateTime  := Now;
   FConfigFile.Post;
 end;

On note ici les méthode Edit et Append. Edit permet de mettre un enregistrement en mode Edition et Append permet de positionner un nouvel enregistrement à la fin de la table.

La méthode Delete key nous montre la suppression d'un enregistrement de la table :

 procedure TJNParameters.DeleteKey(aModule, aSection, aKey: String);
 begin
   if FConfigFile.Locate( 'ModuleName;SectionName;Parameter',
                         VarArrayOf( [ aModule, aSection, aKey]), []) then
   begin
     FConfigFile.Delete;
   end;
 end;

Enfin la méthode UpdateFile nous montre comment les modifications sont sauvegardées :

 procedure TJNParameters.UpdateFile;
 var
   wConfigSav : TClientDataSet;
 begin
   wConfigSav := TClientDataSet.Create( nil);
 
   try
     wConfigSav.FieldDefs.Assign( FConfigFile.FieldDefs);
 
     wConfigSav.CreateDataSet;
     wConfigSav.Open;
 
     FConfigFile.First;
     while not( FConfigFile.Eof) do
     begin
       wConfigSav.Append;
       wConfigSav.FieldByName( 'ModuleName').AsString       := FConfigFile.FieldByName( 'ModuleName').AsString;
       wConfigSav.FieldByName( 'SectionName').AsString      := FConfigFile.FieldByName( 'SectionName').AsString;
       wConfigSav.FieldByName( 'Parameter').AsString        := FConfigFile.FieldByName( 'Parameter').AsString;
       wConfigSav.FieldByName( 'ParameterValue').AsString   := FConfigFile.FieldByName( 'ParameterValue').AsString;
       wConfigSav.FieldByName( 'DateParametre').AsDateTime  := FConfigFile.FieldByName( 'DateParametre').AsDateTime;
       wConfigSav.Post;
 
       FConfigFile.Next;
     end;
 
     try
       if FileExists( FFileName) then
         DeleteFile( FFileName);
 
       wConfigSav.SaveToFile( FFileName);
     except
       on e:exception do
        raise exception.Create( 'Impossible de créer le fichier sur le disque : '
                              + e.Message);
     end;
   finally
     wConfigSav.Free;
   end;
 end;

Rien de bien compliqué. On note simplement que le TClientDataset n'est pas directement stocké, il est dupliqué dans un autre TClientDataset, et si le fichier existait déjà sur le disque il est supprimé. Ceci a pour but d'avoir toujours un fichier bien organisé sur le disque.

Voici la source complète de cette petite classe : Source

Voilà, en espérant que cette petite classe sans prétention sera utile à quelqu'un.