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

[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
Avatar de ShaiLeTroll
Expert éminent sénior https://www.developpez.com
Le 27/04/2025 à 16:45
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.
Avatar de AbeBar27
Membre habitué https://www.developpez.com
Le 27/04/2025 à 18:38
Citation Envoyé par ShaiLeTroll Voir le message
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
Avatar de ShaiLeTroll
Expert éminent sénior https://www.developpez.com
Le 27/04/2025 à 20:14
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)

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.
Avatar de AbeBar27
Membre habitué https://www.developpez.com
Le 28/04/2025 à 14:44
Citation Envoyé par ShaiLeTroll Voir le message
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.
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,
  • 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
Avatar de ShaiLeTroll
Expert éminent sénior https://www.developpez.com
Le 28/04/2025 à 16:20
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

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
Tu peux modifier l'exemple ComponentToString ainsi

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.