Téléchargé 3 fois
Vote des utilisateurs


Détails
Licence : Non renseignée
Mise en ligne le 27 avril 2025
Plate-forme :
Windows
Langue : Français
Référencé dans
Navigation
[VCL] TConfFile, Une alternative aux fichiers INI.
[VCL] TConfFile, Une alternative aux fichiers INI.
J'ai souvent utilisé des fichiers .ini pour gérer divers paramètres de configuration.
Toutefois, les fichiers .ini n’offrent que des fonctionnalités très limitées ce qui conduisait souvent à surcharger l’application (en général des méthodes ReadConf et WriteConf) :
• Lecture / écriture d’objet graphiques : principalement TPen, TBrush et TFont
• Lecture / écriture d’éléments énumérés d’un tableau de chaînes.
• Lecture / écriture d’ensembles (comme le TStyles d’une fonte)
• Absence de sous-section (en standard) ce qui conduit à des paires clef-valeur lourdes et difficiles à spécifier.
• Pas de possibilité d’écrire la valeur d’une paire clé-valeur sur plusieurs lignes.
• Les commentaires sont possibles mais mono ligne et sont perdus lorsque l’on met à jour le fichier ini par le code de l’application.
D’autres formats existent qui permettent de décrire plus finement une grande variété de données tout en étant lisibles directement dans un éditeur avec ou sans coloration syntaxique (fichiers JSON, XML … ). Ces formats et les bibliothèques associées sont toutefois un peu lourds de mise en œuvre pour le but recherché.
TConfFile et son descendant TGDIConfFile permettent de remédier à cela avec les possibilités :
• De définir des sections et des sous-sections.
• Préserve les commentaires mono et multi-lignes.
• Permet les valeurs de paires clés-valeurs sur plusieurs lignes.
• Gère les éléments énumérés d’un tableau de chaînes.
• Gère les ensembles (comme le TStyles d’une fonte)
• Gères les objets graphiques TPen, TBrush et TFont
Toutefois, les fichiers .ini n’offrent que des fonctionnalités très limitées ce qui conduisait souvent à surcharger l’application (en général des méthodes ReadConf et WriteConf) :
• Lecture / écriture d’objet graphiques : principalement TPen, TBrush et TFont
• Lecture / écriture d’éléments énumérés d’un tableau de chaînes.
• Lecture / écriture d’ensembles (comme le TStyles d’une fonte)
• Absence de sous-section (en standard) ce qui conduit à des paires clef-valeur lourdes et difficiles à spécifier.
• Pas de possibilité d’écrire la valeur d’une paire clé-valeur sur plusieurs lignes.
• Les commentaires sont possibles mais mono ligne et sont perdus lorsque l’on met à jour le fichier ini par le code de l’application.
D’autres formats existent qui permettent de décrire plus finement une grande variété de données tout en étant lisibles directement dans un éditeur avec ou sans coloration syntaxique (fichiers JSON, XML … ). Ces formats et les bibliothèques associées sont toutefois un peu lourds de mise en œuvre pour le but recherché.
TConfFile et son descendant TGDIConfFile permettent de remédier à cela avec les possibilités :
• De définir des sections et des sous-sections.
• Préserve les commentaires mono et multi-lignes.
• Permet les valeurs de paires clés-valeurs sur plusieurs lignes.
• Gère les éléments énumérés d’un tableau de chaînes.
• Gère les ensembles (comme le TStyles d’une fonte)
• Gères les objets graphiques TPen, TBrush et TFont
Et le mode DFM fonctionne aussi ?
Delphi a finalement déjà son propre format de sérialisation d'objet GDI natif.
On peut facilement utiliser un TComponent en utilisant les fonctions de flux de Delphi pour serialiser beaucoup de descendant de TPersistent.
Le gestionnaire de Style VCL utilise un objet Layout qui est un container d'un ensemble de TPersistent dont TFont et plus.
Sinon, regarde un export de la Base de Registre, le format de cet export est aussi très intéressant, c'est un fichier ini a peu de chose près, cela montre comme mettre à plat l'arborescence en sous-section et la valeur est typée.
Delphi a finalement déjà son propre format de sérialisation d'objet GDI natif.
On peut facilement utiliser un TComponent en utilisant les fonctions de flux de Delphi pour serialiser beaucoup de descendant de TPersistent.
Le gestionnaire de Style VCL utilise un objet Layout qui est un container d'un ensemble de TPersistent dont TFont et plus.
Sinon, regarde un export de la Base de Registre, le format de cet export est aussi très intéressant, c'est un fichier ini a peu de chose près, cela montre comme mettre à plat l'arborescence en sous-section et la valeur est typée.
Oui le format export de la base de registre est très exactement un fichier ini étendu avec les notions de section\sous-section\sous-sous-section\sous-sous-sous... (stop
)
Je cherchais juste à faire quelque chose de plus simple, lisible et éditable manuellement.
Je n'ai pas bien compris votre question sur le mode DFM.
S'agirait-il d'utiliser les classes TReader et TWriter de l'unité System.Classes ?
J'y ai pensé mais j'avais l'impression de faire appel à un train de marchandises pour transporter une brouette de sable
La conscience de l'imperfection préserve de l'intégrisme

Je cherchais juste à faire quelque chose de plus simple, lisible et éditable manuellement.
Je n'ai pas bien compris votre question sur le mode DFM.
S'agirait-il d'utiliser les classes TReader et TWriter de l'unité System.Classes ?
J'y ai pensé mais j'avais l'impression de faire appel à un train de marchandises pour transporter une brouette de sable

La conscience de l'imperfection préserve de l'intégrisme
Euh, juste partir de l'exemple ComponentToString (Delphi) mais oui c'est l'idée, sans même aller jusqu'à définir les TReader et TWriter, on peut faire un truc assez trivial
Je n'ai pas regardé ton code en détail mais pour TFont, TPen, TBrush ... la sérialisation peut se faire en créant à la volée un TComponent qui ne sert que en tant que Container de sérialisation
Idem tout ce qui est à base TStrings c'est facile mais TCollection demande un DefineProperties et des ReadCollections et WriteCollections, disons qu'il faut connaitre.
Dans un vieux code, au lieu de sérialiser un record, j'avais utilisé un objet : [D5] Charger rapidement un type record
Un peu comme les Experts JSON ou XML, tient comme dans ce sujet : Comment utiliser la liaison de fichier JSON ou l'on peut écrire leJsonVille.VILLE.BATIMENTS.Add(jsonbat) en manipulant l'objet de façon simple, on a tous fait ce genre de chose en XML ou en SOAP quand c'était à la mode
De la même façon, on peut donc "figer" son objet config pour écrire un truc genre "Conf.Element.Champ.Valeur" ... en tout cas, j'utilise du JSON ou du XML pour ce genre de chose, et j'ai un code qui encapsule lecture et écriture du fichier pour que l'utilisation soit simple
Alors, j'ai utilisé SetSubComponent pour avoir un DFM compact mais en dehors de créer l'objet Conf et ses items, le reste c'est juste le code issu de la doc : ComponentToString (Delphi)
Avec des objets simples TPersistent connu et quelques autres types, la seule chose à faire c'est déclaratif, juste on décrit en code la structure du fichier soit la classe racine TMachin
- Unit1, la fiche avec un bouton
- Config, un objet structuré
- SampleComponentToString, le code exemple sur le Wiki Embarcadero
Je n'ai pas regardé ton code en détail mais pour TFont, TPen, TBrush ... la sérialisation peut se faire en créant à la volée un TComponent qui ne sert que en tant que Container de sérialisation
Idem tout ce qui est à base TStrings c'est facile mais TCollection demande un DefineProperties et des ReadCollections et WriteCollections, disons qu'il faut connaitre.
Dans un vieux code, au lieu de sérialiser un record, j'avais utilisé un objet : [D5] Charger rapidement un type record
Un peu comme les Experts JSON ou XML, tient comme dans ce sujet : Comment utiliser la liaison de fichier JSON ou l'on peut écrire leJsonVille.VILLE.BATIMENTS.Add(jsonbat) en manipulant l'objet de façon simple, on a tous fait ce genre de chose en XML ou en SOAP quand c'était à la mode
De la même façon, on peut donc "figer" son objet config pour écrire un truc genre "Conf.Element.Champ.Valeur" ... en tout cas, j'utilise du JSON ou du XML pour ce genre de chose, et j'ai un code qui encapsule lecture et écriture du fichier pour que l'utilisation soit simple
Alors, j'ai utilisé SetSubComponent pour avoir un DFM compact mais en dehors de créer l'objet Conf et ses items, le reste c'est juste le code issu de la doc : ComponentToString (Delphi)
Code dfm : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | object TMachin Text = 'Texte de Machine' Strings.Strings = ( 'Texte N'#176'1 de la Liste' 'Texte N'#176'2 de la Liste' 'Texte N'#176'3 de la Liste' 'Texte N'#176'4 de la Liste' 'Texte N'#176'5 de la Liste') Bidule.Font.Charset = DEFAULT_CHARSET Bidule.Font.Color = clWindowText Bidule.Font.Height = -11 Bidule.Font.Name = 'Tahoma' Bidule.Font.Style = [] Truc.Chose.Value = 666 Truc.Text = 'Texte du Truc dans Machine' end |
Avec des objets simples TPersistent connu et quelques autres types, la seule chose à faire c'est déclaratif, juste on décrit en code la structure du fichier soit la classe racine TMachin
- Unit1, la fiche avec un bouton
- Config, un objet structuré
- SampleComponentToString, le code exemple sur le Wiki Embarcadero
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Déclarations privées } public { Déclarations publiques } end; var Form1: TForm1; implementation {$R *.dfm} uses Config, SampleComponentToString; procedure TForm1.Button1Click(Sender: TObject); var Machin: TMachin; S: string; I: Integer; begin Machin := TMachin.Create(nil); try Machin.Text := 'Texte de Machine'; for I := 1 to 5 do Machin.Strings.Add(Format('Texte N°%d de la Liste', [i])); Machin.Truc.Text := 'Texte du Truc dans Machine'; Machin.Truc.Chose.Value := 666; Machin.Bidule.Font := Self.Font; S := ComponentToStringProc(Machin); finally Machin.Free(); end; ShowMessage(S); Machin := StringToComponentProc(S) as TMachin; try ShowMessage(Machin.Text + sLineBreak + Machin.Truc.Text + sLineBreak + Machin.Truc.Chose.Value.ToString() + sLineBreak + Machin.Strings.Text); ShowMessage(Machin.Bidule.Font.Name); finally Machin.Free(); end; end; end. |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | unit Config; interface uses System.Classes, Vcl.Graphics; type TMachin = class(TComponent) public type TBidule = class(TComponent) private FFont: TFont; procedure SetFont(const Value: TFont); public constructor Create(AOwner: TComponent); override; destructor Destroy(); override; published property Font: TFont read FFont write SetFont; end; TTruc = class(TComponent) private type TChose = class(TComponent) private FValue: Integer; published property Value: Integer read FValue write FValue; end; private private FChose: TChose; FText: string; public constructor Create(AOwner: TComponent); override; destructor Destroy(); override; published property Chose: TChose read FChose; property Text: string read FText write FText; end; private FText: string; FStrings: TStrings; FBidule: TBidule; FTruc: TTruc; protected procedure SetStrings(const Value: TStrings); public constructor Create(AOwner: TComponent); override; destructor Destroy(); override; published property Text: string read FText write FText; property Strings: TStrings read FStrings write SetStrings; property Bidule: TBidule read FBidule; property Truc: TTruc read FTruc; end; implementation uses System.SysUtils; { TMachin } constructor TMachin.Create(AOwner: TComponent); begin inherited Create(AOwner); FBidule := TBidule.Create(Self); FTruc := TTruc.Create(Self); FStrings := TStringList.Create(); FBidule.SetSubComponent(True); FTruc.SetSubComponent(True); end; destructor TMachin.Destroy(); begin FreeAndNil(FStrings); FTruc := nil; FBidule := nil; inherited Destroy(); end; procedure TMachin.SetStrings(const Value: TStrings); begin FStrings.Assign(Value); end; { TMachin.TTruc } constructor TMachin.TTruc.Create(AOwner: TComponent); begin inherited Create(AOwner); FChose := TChose.Create(Self); FChose.SetSubComponent(True); end; destructor TMachin.TTruc.Destroy(); begin FChose := nil; inherited Destroy(); end; { TMachin.TBidule } constructor TMachin.TBidule.Create(AOwner: TComponent); begin inherited Create(AOwner); FFont := TFont.Create(); end; destructor TMachin.TBidule.Destroy(); begin FreeAndNil(FFont); inherited Destroy(); end; procedure TMachin.TBidule.SetFont(const Value: TFont); begin FFont.Assign(Value); end; initialization RegisterClasses([TMachin, TMachin.TBidule, TMachin.TTruc, TMachin.TTruc.TChose]); end. |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | unit SampleComponentToString; interface uses System.Classes; function ComponentToStringProc(Component: TComponent): string; function StringToComponentProc(Value: string): TComponent; implementation function ComponentToStringProc(Component: TComponent): string; var BinStream:TMemoryStream; StrStream: TStringStream; s: string; begin BinStream := TMemoryStream.Create; try StrStream := TStringStream.Create(s); try BinStream.WriteComponent(Component); BinStream.Seek(0, soFromBeginning); ObjectBinaryToText(BinStream, StrStream); StrStream.Seek(0, soFromBeginning); Result:= StrStream.DataString; finally StrStream.Free; end; finally BinStream.Free end; end; function StringToComponentProc(Value: string): TComponent; var StrStream:TStringStream; BinStream: TMemoryStream; begin StrStream := TStringStream.Create(Value); try BinStream := TMemoryStream.Create; try ObjectTextToBinary(StrStream, BinStream); BinStream.Seek(0, soFromBeginning); Result:= BinStream.ReadComponent(nil); finally BinStream.Free; end; finally StrStream.Free; end; end; end. |
Merci beaucoup pour ces infos très intéressantes, j'ai appris quelque chose d'utile 
L'exemple que tu m'as obligeamment fourni fonctionne immédiatement. Du coup je me suis précipité pour y ajouter une encapsulation de TFont dans un descendant de TComponent (que j'ai nommé TFontCapsule, c'est original !).
Cela fonctionne parfaitement, la preuve :

Ceci étant,
Mais encore merci, la technique que tu proposes ouvre de nouveau horizons

L'exemple que tu m'as obligeamment fourni fonctionne immédiatement. Du coup je me suis précipité pour y ajouter une encapsulation de TFont dans un descendant de TComponent (que j'ai nommé TFontCapsule, c'est original !).
Cela fonctionne parfaitement, la preuve :
Ceci étant,
- La seule amélioration obtenue par rapport à ma méthode est d'écrire le style en clair [fsBold,fsUnderline] plutôt que [05]. Mais si j'écris un TOpenDialog avec toutes les options cochées (cas d'école) je vais avoir un truc genre OpenDialog1.Options = [ofReadOnly, ofOverwritePrompt, ofHideReadOnly,ofNoChangeDir, ofShowHelp, ofNoValidate, ofAllowMultiSelect,ofExtensionDifferent, ofPathMustExist, ofFileMustExist, ofCreatePrompt,ofShareAware, ofNoReadOnlyReturn, ofNoTestFileCreate, ofNoNetworkButton,ofNoLongNames, ofOldStyleDialog, ofNoDereferenceLinks, ofEnableIncludeNotify,ofEnableSizing, ofDontAddToRecent, ofForceShowHidden]. Pas vraiment mon but !
- Le code compilé me parait vite un peu lourd : 2*451*968 octets contre 2*760*192 octets pour mon appli ConfEditor qui utilise en plus des composants TSyntaxMemo, TGutter, TTreeView, TMenu, TOpenDialog, TPopUpMenu...
Mais encore merci, la technique que tu proposes ouvre de nouveau horizons

Ah oui, le format DFM texte, c'est verbeux, et même le mode DFM Binaire ... surprise c'est aussi du texte
C'est pour anticiper à mon avis un changement de l'ordre de l'énumération, si un petit malin insère une valeur en plein milieu, ça casserait la compatibilité ascendante en stockant le set sous forme de binaire
C'est une chaine non affichable car c'est un binaire donc très vite on a des zéros qui stoppe les chaines dans les API Windows
Tu peux modifier l'exemple ComponentToString ainsi
C'est pour anticiper à mon avis un changement de l'ordre de l'énumération, si un petit malin insère une valeur en plein milieu, ça casserait la compatibilité ascendante en stockant le set sous forme de binaire
C'est une chaine non affichable car c'est un binaire donc très vite on a des zéros qui stoppe les chaines dans les API Windows
Code : | Sélectionner tout |
'TPF0'#7'TMachin'#0#4'Text'#6#$10'Texte de Machine'#$F'Strings.Strings'#1#$14#$16#0#0#0'Texte N°1 de la Liste'#$14#$16#0#0#0'Texte N°2 de la Liste'#$14#$16#0#0#0'Texte N°3 de la Liste'#$14#$16#0#0#0'Texte N°4 de la Liste'#$14#$16#0#0#0'Texte N°5 de la Liste'#0#$13'Bidule.Font.Charset'#7#$F'DEFAULT_CHARSET'#$11'Bidule.Font.Color'#7#$C'clWindowText'#$12'Bidule.Font.Height'#2'õ'#$10'Bidule.Font.Name'#6#6'Tahoma'#$11'Bidule.Font.Style'#$B#6'fsBold'#$B'fsUnderline'#0#$10'Truc.Chose.Value'#3''#2#9'Truc.Text'#6#$1A'Texte du Truc dans Machine'#0#0
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | unit SampleComponentToString; interface uses System.Classes; function ComponentToStringProc(Component: TComponent; ADFMText: Boolean): string; function StringToComponentProc(Value: string; ADFMText: Boolean): TComponent; implementation function ComponentToStringProc(Component: TComponent; ADFMText: Boolean): string; var BinStream:TMemoryStream; StrStream: TStringStream; s: string; begin BinStream := TMemoryStream.Create; try StrStream := TStringStream.Create(s); try BinStream.WriteComponent(Component); BinStream.Seek(0, soFromBeginning); if ADFMText then begin ObjectBinaryToText(BinStream, StrStream); StrStream.Seek(0, soFromBeginning); end else StrStream.CopyFrom(BinStream, BinStream.Size); Result:= StrStream.DataString; finally StrStream.Free; end; finally BinStream.Free end; end; function StringToComponentProc(Value: string; ADFMText: Boolean): TComponent; var StrStream:TStringStream; BinStream: TMemoryStream; begin StrStream := TStringStream.Create(Value); try BinStream := TMemoryStream.Create; try if ADFMText then ObjectTextToBinary(StrStream, BinStream) else BinStream.CopyFrom(StrStream, StrStream.Size); BinStream.Seek(0, soFromBeginning); Result:= BinStream.ReadComponent(nil); finally BinStream.Free; end; finally StrStream.Free; end; end; end. |
Developpez.com décline toute responsabilité quant à l'utilisation des différents éléments téléchargés.