Mécanisme de lecture et d'écriture de la composante de Delphi (I)
1. Introduction aux objets en streaming (flux) et objets en lecture-écriture (déclarants)
Dans la programmation orientée objet, la gestion des données basée sur des objets occupe une position très importante. Dans Delphi, la méthode de support de la gestion des données basée sur des objets est l'une de ses principales fonctionnalités.
Delphi est un environnement de développement intégré qui combine la conception visuelle orientée objet avec des langues orientées objet. Le noyau de Delphi est les composants. Les composants sont un type d'objet. Les applications Delphi sont entièrement construites par des composants, donc le développement d'applications Delphi de haute performance impliquera inévitablement une technologie de gestion des données basée sur des objets.
La gestion des données basée sur des objets comprend deux aspects:
● Utiliser des objets pour gérer les données
● Gestion de divers objets de données (y compris les objets et les composants)
Delphi attribue des classes de gestion des données basées sur des objets pour diffuser des objets (Stream) et des objets Fichier (Filers), et les applique à tous les aspects de la bibliothèque de classe de composants visuels (VCL). Ils fournissent des fonctions riches pour gérer les objets en mémoire, la mémoire externe et les ressources Windows.
Stream Object, également connu sous le nom d'objet en streaming, est un terme général pour TSTREAM, Thandlestream, TFileStream, TmemoryStream, TresourceStream et TblobStream. Ils représentent la possibilité de stocker des données sur divers supports, de résumé les opérations de gestion de divers types de données (y compris des objets et des composants) en mémoire, hors de la mémoire et des champs de base de données dans les méthodes d'objet et utilisent pleinement les avantages technologiques orientés objet De cela, les applications peuvent copier des données dans divers objets de flux assez facilement.
Lire et écrire des objets (déclarants) incluent des objets TFiler, des objets Treader et des objets Twriter. L'objet TFiler est l'objet de base pour la lecture et l'écriture de fichiers, et les principales utilisations de Treader et Twriter dans les applications. Les objets Treader et Twriter sont hérités directement des objets TFiler. L'objet TFiler définit les propriétés de base et les méthodes de l'objet filer.
Les objets de filer remplissent principalement deux fonctions majeures:
● Accédez aux fichiers et composants du formulaire dans les fichiers de formulaire
● Fournir une mise en mémoire tampon des données pour accélérer les opérations de lecture et d'écriture des données
Afin d'avoir une compréhension perceptuelle du streaming des objets et des objets de lecture et d'écriture, examinons d'abord un exemple.
a) Écrivez un fichier
Procédure tfomr1.writedata (expéditeur: tobject);
Var
FileStream: TFileStream;
MyWriter: Twriter;
I: entier
Commencer
FileStream: = tFileStream.Create ('c: /test.txt',fmopenwrite); // Créer un objet de flux de fichiers
MyWriter: = Twriter.Create (FileStream, 1024);
MyWriter.WriteListBegin;
Pour i: = 0 à memo1.lines.count-1 do
MyWriter.WriteString (Memo1.lines [i]);
MyWriter.WriteListetend;
FileStream.seek (0, sofrombeginning);
MyWriter.Free;
FileStream.Free;
Fin;
b) Lisez le fichier
Procédure tform1.readdata (expéditeur: tobject);
Var
FileStream: TFileStream;
Myreader: Treader;
Commencer
FileStream: = tFileStream.Create ('c: /test.txt',fmOpenRead);
MyReader: = Trreader.Create (FileStream, 1024);
MyReader.readlistBeg
Memo1.lines.clear;
Bien que non MyReader.endoFlist DO // note une méthode de Treader: Endoflist
Commencer
Memo1.lines.add (myReader.readString);
Fin;
MyReader.ReadListend; // Lire le drapeau final de la liste écrite
MyReader.Free;
FileStream.Free;
Fin;
Les deux processus ci-dessus sont l'un pour le processus d'écriture et l'autre pour le processus de lecture. Le processus d'écriture utilise Twriter pour enregistrer le contenu (informations texte) dans un mémo en tant que fichier binaire enregistré sur le disque à l'aide de TFileStream. Le processus de lecture est juste l'opposé du processus d'écriture. L'exécution du programme peut voir que le processus de lecture restaure fidèlement les informations enregistrées dans le processus d'écriture.
La figure suivante décrit la relation entre les objets de données (y compris les objets et les composants), le streaming des objets et les objets de lecture et d'écriture.
Figure (1)
Il convient de noter que les objets de lecture et d'écriture tels que les objets TFiler, les objets Treader et les objets twriter sont rarement appelés directement par les rédacteurs d'applications. et écrire le mécanisme des composants.
Pour le flux d'objets en streaming, de nombreux matériaux de référence sont introduits en détail, tandis que les matériaux de référence pour les objets TFiler, les objets Treader et les objets twriter, en particulier les mécanismes de lecture et d'écriture des composants sont rares. .
2. Mécanisme de lecture et d'écriture de lecture (filer) et de lecture des composants
L'objet Fichier est principalement utilisé pour accéder aux fichiers et composants de formulaires de Delphi dans les fichiers de formulaire.
Les fichiers DFM sont utilisés pour les formulaires de stockage Delphi. Les formes sont le cœur de la programmation visuelle Delphi. Le formulaire correspond à la fenêtre de l'application Delphi, les composants visuels du formulaire correspondent aux éléments d'interface de la fenêtre et aux composants non visuels tels que Ttimer et Topendialog, correspondant à une certaine fonction de l'application Delphi. La conception de l'application Delphi est en fait centrée sur la conception du formulaire. Par conséquent, les fichiers DFM occupent également une position très importante dans la conception de l'application Delphi. Tous les éléments du formulaire, y compris les propres propriétés du formulaire, sont inclus dans le fichier DFM.
Dans la fenêtre d'application Delphi, les éléments d'interface sont interconnectés par les relations de propriété, donc la structure des arbres est l'expression la plus naturelle; Les fichiers DFM sont stockés physiquement dans le texte (précédemment stockés sous forme de fichiers binaires dans Delphi2.0), et logiquement, ils organisent les relations de chaque composant dans une structure d'arbre. À partir de ce texte, vous pouvez voir la structure des arbres de la forme. Voici le contenu du fichier DFM:
objet Form1: TForm1
Gauche = 197
TOP = 124
...
Pixelsperinch = 96
Textheight = 13
Button objet 1: TBUTTON
Gauche = 272
...
Légende = 'Button1'
TabOrder = 0
fin
Panneau d'objet1: tpanel
Gauche = 120
...
Légende = 'Panel1'
TabOrder = 1
OBJECTIF OBJECT BOX1: TCHECKBOX
Gauche = 104
...
Légende = 'Checkbox1'
TabOrder = 0
fin
fin
fin
Ce fichier DFM est généré par Twriter via le flux d'objets en streaming. .
Lorsque le programme commence à s'exécuter, Treader lit le formulaire et les composants via le flux d'objet Stream, car lorsque Delphi compile le programme, il utilise l'instruction de compilation {$ r * .dfm} pour compiler les informations de fichier DFM dans le fichier exécutable à l'aide de la compilation instruction {$ r * .dfm}.
Treader et Twriter peuvent non seulement lire et écrire la plupart des types de données standard dans Object Pascal, mais également lire et écrire des types avancés tels que la liste et la variante, et même lire et écrire des perperties et des composants. Cependant, Treader et Twriter eux-mêmes offrent en fait des fonctions très limitées, et la plupart des travaux réels sont effectués par TSTream, une classe très puissante. En d'autres termes, Treader et Twriter ne sont en fait que des outils, qui ne sont responsables que de la façon de lire et d'écrire des composants.
Puisque TFiler est une classe publique d'ancêtre de Treader et de Twriter, pour comprendre Treader et Twriter, commencez par TFiler.
Tfiler
Examinons d'abord la définition de la classe TFiler:
TFiler = classe (tobject)
Privé
Fstream: tStream;
Fbuffer: pointeur;
FbufSize: entier;
Fbufpos: entier;
Fbufend: entier;
Froot: tComponent;
Flookuproot: tComponent;
Fancestor: tpersistent;
Figorechildren: booléen;
protégé
procédure setroot (valeur: tcomponent);
publique
Concluteur Create (Stream: TStream; BufSize: Integer);
Destructor détruire;
Procédure DefineProperty (nom const: String;
ReadData: TreaterProc;
Hasdata: Boolean);
Procédure DefineBinaryProperty (Nom de const: String;
Readdata, wretedata: tStreamproc;
Hasdata: Boolean);
Procédure FlushBuffer;
Root de la propriété: TComponent Lire Froot Write Setroot;
Lookuproot de la propriété: TComponent Read Flookuproot;
Propriété Ancestor: Tpersistent Lire Fancestor Écrivez Fancestor;
Propriété Ignorechildren: booléen lu fignorechildren écrire fignorechildren;
fin;
L'objet TFiler est une classe abstraite de Treader et Twriter, qui définit les propriétés et méthodes de base utilisées pour le stockage des composants. Il définit l'attribut racine. Fabriqué par l'objet Stream. Par conséquent, tant que les supports accessibles à l'objet Stream, le composant est accessible par l'objet Fichier.
L'objet TFiler fournit également deux méthodes publiques qui définissent les propriétés: définir laProperty et DefinebinaryProperty, qui permettent à l'objet de lire et d'écrire des propriétés qui ne sont pas définies dans la partie publiée du composant. Concentrons-nous sur ces deux méthodes ci-dessous.
La méthode DefineProperty () est utilisée pour persister des types de données standard tels que les chaînes, les entiers, les booléens, les caractères, les points flottants et les énumériques.
Dans la méthode DefineProperty. Le paramètre de nom spécifie le nom de l'attribut qui doit être écrit dans le fichier DFM, qui n'est pas défini dans la partie publiée de la classe.
Les paramètres ReadData et WriteData spécifient la méthode pour lire et écrire les données requises lors de l'accès à un objet. Les types de paramètres ReadData et les paramètres de writeData sont respectivement TreerProc et TwriterProc. Ces deux types sont déclarés comme ceci:
TreaterProc = Procédure (lecteur: Treader) de l'objet;
TwriterProc = procédure (écrivain: twriter) de l'objet;
Le paramètre Hasdata détermine si la propriété a des données à stocker au moment de l'exécution.
La méthode DefinebinaryProperty propose de nombreuses similitudes avec DefineProperty.
Expliquons les utilisations de ces deux méthodes ci-dessous.
Nous mettons un composant non visuel tel que Ttimer sur le formulaire. ?
Ouvrez le fichier DFM de ce formulaire et vous pouvez voir plusieurs lignes similaires à ce qui suit:
Objet Timer1: Ttimer
Gauche = 184
En haut = 149
fin
Le système de streaming de Delphi ne peut enregistrer que des données publiées, mais Ttimer n'a pas publié les attributs gauche et supérieur, alors comment ces données sont-elles enregistrées?
Ttimemer est une classe dérivée de tcomponent.
Procédure tComponent.DefineProperties (filer: TFiler);
var
Ancêtre: tcomposant;
Info: Longint;
Commencer
Info: = 0;
Ancêtre: = tComponent (filer.ancestor);
Si ancêtre <> nul alors info: = ancêtre.fdesignInfo;
Filer.defineproperty ('gauche', readleft, writeleft,
Longrec (fdeSignInfo) .lo <> longrec (info) .lo);
Filer.defineproperty ('top', readtop, writeTop,
Longrec (fdeSignInfo) .hi <> longrec (info) .hi);
fin;
DefineProperties de TComponent est une méthode virtuelle qui écrase sa classe ancêtre tppersistent, et dans la classe tPesistent, cette méthode est une méthode virtuelle vide.
Dans la méthode DefineProperties, nous pouvons voir qu'il y a un objet Fichier comme paramètre. valeur. Il appelle la méthode DefineProperty de TFiler et définit Readleft, Writeleft, ReadTop, WriteTop Methods to Lire and Write Propriétés gauche et supérieure.
Par conséquent, tout composant dérivé de TComponent, même s'il n'a pas d'attributs gauche et supérieur, aura deux de ces propriétés lors du streaming vers un fichier DFM.
Dans le processus de recherche de données, il a été constaté que peu de données impliquent des mécanismes de lecture et d'écriture des composants. Étant donné que le processus d'écriture des composants est terminé par l'IDE de Delphi pendant la phase de conception, il ne peut pas être suivi pour son processus de fonctionnement. Par conséquent, l'auteur comprend le mécanisme de lecture du composant en suivant le code VCL d'origine pendant l'opération du programme et analyse le mécanisme d'écriture du composant via le mécanisme de lecture et Twriter. Par conséquent, ce qui suit expliquera le mécanisme de lecture et d'écriture des composants en fonction de ce processus de réflexion, parlant d'abord de Treader, puis de Twriter.
Fouler
Regardez d'abord les fichiers du projet de Delphi et vous trouverez quelques lignes de code comme celle-ci:
Commencer
application.Initialize;
Application.CreateForm (TForm1, Form1);
Application.run;
fin.
Il s'agit de l'entrée du programme Delphi. Mettez simplement la signification de ces lignes de code: Application.Initialize effectue des travaux d'initialisation nécessaires sur le début des applications en cours d'exécution. .
Ce que nous nous soucions le plus maintenant, c'est la phrase de la création d'une forme. Comment les formulaires et les composants sur les formulaires sont-ils créés? Comme mentionné précédemment: tous les composants du formulaire, y compris les propriétés du formulaire lui-même, sont inclus dans le fichier DFM. dans le fichier exécutable. Par conséquent, on peut conclure que lors de la création d'un formulaire, vous devez lire les informations DFM.
En suivant le programme étape par étape, vous pouvez constater que le programme appelle la méthode ReadrootComponent de Treader pendant la création du formulaire. Le but de cette méthode est de lire le composant racine et tous les composants qu'il a. Jetons un coup d'œil à la mise en œuvre de cette méthode:
Function Treader.ReadrootComponent (root: tComponent): tComponent;
...
Commencer
Lecture de lecture;
Résultat: = nil;
GlobalNamespace.beginwrite;
essayer
essayer
ReadPrefix (Flags, i);
Si root = nil alors
Commencer
Résultat: = tComponentClass (findClass (readstr)). Create (nil);
Result.name: = readstr;
fin d'autre
Commencer
Résultats: = root;
Readstr; {ignorer le nom de la classe}
Si csdesigning dans le résultat.componentState alors
Readstr Else
Commencer
Include (result.fComponentState, CSloading);
Include (result.fcomponentState, csreading);
Result.name: = findUniqueName (readstr);
fin;
fin;
Froot: = résultats;
Ffinder: = tClassfinder.Create (tPeSistentClass (result.classtype), true);
essayer
Flookuproot: = résultats;
G: = globalloaded;
Si g <> nil alors
Floadé: = g d'autre
FLOADED: = tlist.create;
essayer
si flotté.indexof (Froot) <0 alors
Floaded.add (Froot);
FOWNER: = Froot;
Include (Froot.fComponentState, CSloading);
Inclure (Froot.fComponentState, CSReading);
Froot.readstate (self);
Exclure (Froot.fComponentState, CSReading);
Si g = nil alors
pour i: = 0 à Floaded.Count - 1 do tComponent (Floaded [i]). chargé;
Enfin
si g = nil alors flottant.free;
Floade: = NIL;
fin;
Enfin
Ffinder.free;
fin;
...
Enfin
GlobalNamespace.endwrite;
fin;
fin;
ReadRootComponent premier appelle ReadSignature pour lire la balise d'objet Filer («TPF0»). La détection des balises avant de charger des objets peut empêcher la négligence et la lecture de données inefficaces ou obsolètes.
Jetons un coup d'œil à ReadPrefix (Flags, I). Lorsqu'un objet d'écriture écrit un composant sur un flux, il prédisse deux valeurs devant le composant. La deuxième valeur indique l'ordre qu'il a été créé sous la forme des ancêtres.
Ensuite, si le paramètre racine est nul, un nouveau composant est créé avec le nom de classe lu par Readstr, et la propriété du nom du composant est lue à partir du flux; .
Froot.readstate (self);
Il s'agit d'une phrase très critique. Bien que cette méthode de lecture est une méthode TComponent, un suivi supplémentaire peut être constaté qu'il a finalement localisé la méthode ReadDatainner de Treader.
Procédure Treader.ReadDatainner (instance: tComponent);
var
Oldparent, Oldowner: Tomponent;
Commencer
Bien que non endoflist ne liseproperty (instance);
ReadListend;
Oldparent: = parent;
Oldowner: = propriétaire;
Parent: = instance.getChildParent;
essayer
Propriétaire: = instance.getChildOwner;
Si ce n'est pas affecté (propriétaire) alors propriétaire: = root;
Bien qu'il ne soit pas endoflist do ReadComponent (nil);
ReadListend;
Enfin
Parent: = oldparent;
Propriétaire: = Oldowner;
fin;
fin;
Il y a cette ligne de code:
Bien que non endoflist ne liseproperty (instance);
Ceci est utilisé pour lire les propriétés de la composante racine. Pour ces deux propriétés différentes, il devrait y avoir deux méthodes de lecture différentes.
Procédure Treader.ReadProperty (Ainstance: tPesistent);
...
Commencer
...
Propinfo: = getPropInfo (instance.classInfo, fpropName);
Si ProPinfo <> NIL alors ReadPropValue (instance, propinfo) else
Commencer
{Ne peut pas se remettre de manière fiable d'une erreur dans une propriété définie}
FcanHandleExcepts: = false;
Instance.defineProperties (self);
FcanHandleExcepts: = true;
Si fpropname <> '' alors
PropertyError (fpropName);
fin;
...
fin;
Pour économiser de l'espace, un code a été omis.
Propinfo: = getPropInfo (instance.classInfo, fpropName);
Ce code vise à obtenir les informations de la propriété publiée fpropName. À partir du code suivant, nous pouvons voir que si les informations d'attribut ne sont pas vides, la valeur d'attribut est lue par la méthode ReadPropValue, et la méthode ReadPropValue lit la valeur d'attribut via la fonction RTTI, qui ne sera pas introduite en détail ici. Si les informations d'attribut sont vides, cela signifie que l'attribut fpropname n'est pas publié et qu'il doit être lu via un autre mécanisme. Il s'agit de la méthode DefineProperties mentionnée ci-dessus, comme suit:
Instance.defineProperties (self);
Cette méthode appelle en fait la méthode DefineProperty de Treader:
Procédure Treader.DefineProperty (Nom de const: String;
ReadData: TreaterProc;
Commencer
Si sameText (nom, fpropname) et affecté (ReadData) alors
Commencer
Readdata (soi);
Fpropname: = '';
fin;
fin;
Il compare d'abord si le nom d'attribut de lecture est le même que le nom d'attribut prédéfini.
Ok, le composant racine a été lu et l'étape suivante devrait être de lire les composants appartenant à la composante racine. Regardons à nouveau la méthode:
Procédure Treader.ReadDatainner (instance: tComponent);
Il y a un code qui suit cette méthode:
Bien qu'il ne soit pas endoflist do ReadComponent (nil);
C'est exactement ce qui est utilisé pour lire les composants de l'enfant. Le mécanisme de lecture du composant enfant est le même que la lecture de la composante racine introduite ci-dessus, qui est une traversée profonde d'un arbre.
Jusqu'à présent, le mécanisme de lecture des composants a été introduit.
Examinons le mécanisme d'écriture des composants. Lorsque nous ajoutons un composant au formulaire, ses propriétés connexes seront enregistrées dans le fichier DFM, et ce processus est effectué par Twriter.
Ø Twriter
Un objet Twriter est un objet Fichier instanable qui écrit des données dans un flux. L'objet Twriter est hérité directement de TFiler.
L'objet Twriter fournit de nombreuses méthodes pour écrire différents types de données dans le flux. Par conséquent, pour maîtriser les méthodes de mise en œuvre et d'application des objets à twriter, vous devez comprendre le format des objets de l'écrivain stockant des données.
La première chose à noter est que chaque flux d'objet de filer contient des balises d'objet filer. Cette balise prend quatre octets et sa valeur est "TPF0". L'objet Filer accède à la balise des méthodes d'écriture et de lecture. Cette balise est principalement utilisée pour guider les opérations de lecture lorsque les objets de lecteur lisent des données (composants, etc.).
Deuxièmement, l'objet écrivain doit laisser un bit de drapeau d'octet avant de stocker les données pour indiquer quel type de données est stockée ultérieurement. Cet octet est une valeur de type tvalutype. Tvaluetype est un type d'énumération qui occupe un espace d'octet, et sa définition est la suivante:
Tvaluetype = (Vanull, Valist, Vaint8, Vaint16, Vaint32, Vaentend, Vastring, Vaident,
Vafalse, Vatrue, vabinaire, vaset, valstring, vanil, vacollection);
Par conséquent, dans la mise en œuvre de chaque méthode d'écriture de données de l'objet écrivain, vous devez d'abord écrire le bit de drapeau, puis écrire les données correspondantes; Données de lecture. Le logo VALIST a un objectif spécial. Par conséquent, lors de la rédaction de plusieurs éléments identiques consécutifs dans l'objet écrivain, utilisez d'abord WriteListBegin pour écrire le drapeau Valist, et après avoir écrit les éléments de données, écrivez le drapeau Vanull; Utilisez la fonction Endoflist au milieu, déterminez s'il y a un drapeau Vanull.
Jetons un coup d'œil à une méthode très importante pour Twriter Writedata:
Procédure twriter.writedata (instance: tcomponent);
...
Commencer
...
WritePrefix (drapeaux, fchildPos);
Si usequalifiedNames alors
WriteStr (getTypedata (ptypeinfo (instance.classtype.classInfo)). UnitName + '.' + Instance.classname)
autre
WriteStrAll (instance.classname);
WriteStrAll (instance.name);
PROPRIÉTÉSOSITION: = Position;
if (fancestorlist <> nil) et (fancestorpos <fancestorlist.count) puis
Commencer
si ancêtre <> nil alors inc (fancestorpos);
Inc (fchildPos);
fin;
WriteProperties (instance);
Écrivain;
...
fin;
D'après la méthode WriteData, nous pouvons voir l'image générale de la génération d'informations sur le fichier DFM. Écrivez d'abord l'indicateur (préfixe) devant le composant, puis écrivez le nom de classe et le nom de l'instance. Ensuite, il y a une phrase comme ceci:
WriteProperties (instance);
Ceci est utilisé pour écrire les propriétés du composant. Comme mentionné précédemment, dans les fichiers DFM, il existe à la fois des attributs publiés et des attributs non publiés. Jetons un coup d'œil à la mise en œuvre de WriteProperties:
procédure twriter.writeProperties (instance: tPesistent);
...
Commencer
Count: = getTypedata (instance.classInfo) ^. PROPCOUNT;
Si compter> 0 alors
Commencer
GetMem (proplist, count * sizeof (pointeur));
essayer
GetPropInfos (instance.classInfo, proplist);
pour i: = 0 pour compter - 1 do
Commencer
Propinfo: = proplist ^ [i];
Si ProPinfo = NIL alors
Casser;
Si isStoredProp (instance, propinfo) alors
WriteProperty (instance, propinfo);
fin;
Enfin
FreeMem (proplist, count * sizeof (pointeur));
fin;
fin;
Instance.defineProperties (self);
fin;
Veuillez consulter le code suivant:
Si isStoredProp (instance, propinfo) alors
WriteProperty (instance, propinfo);
La fonction IsstoredProp détermine si la propriété doit être enregistrée en stockant le qualificatif.
Après avoir enregistré la propriété publiée, la propriété non publiée doit être enregistrée.
Instance.defineProperties (self);
La mise en œuvre de DefineProperties a déjà été mentionnée.
Ok, jusqu'à présent, il y a encore une question: comment les composants enfants appartenant à la composante racine sont-ils enregistrés? Examinons la méthode WRITERDATA (cette méthode a été mentionnée plus tôt):
Procédure twriter.writedata (instance: tcomponent);
...
Commencer
...
Si ce n'est pas des ignorance, alors
essayer
if (fancestor <> nil) et (fancestor est tomponent) alors
Commencer
si (Fancestor est tComponent) et (csinline dans tComponent (fancestor) .ComponentState)
FrootanceStor: = TComponent (Fancestor);
Fancestorlist: = tlist.create;
TComponent (Fancestor) .getChildren (Addancestor, Frootancestor);
fin;
Si csinline dans instance.componentState alors
Froot: = instance;
Instance.getchildren (writeComponent, Froot);
Enfin
Fancestorlist.free;
fin;
fin;
La propriété IgnoreChildren permet à un objet d'écrivain de stocker un composant sans stocker des composants enfants appartenant au composant. Si la propriété IgnoreChildren est vraie, l'objet écrivain ne stocke pas les composants enfants qu'il a lors du stockage du composant. Sinon, les sous-composants seront stockés.
Instance.getchildren (writeComponent, Froot);
Il s'agit de la phrase la plus critique pour la rédaction de sous-composants. De cette façon, ce que nous voyons dans le fichier DFM est une structure de composants en forme d'arbre.