• Delphi 和 DFM


    Delphi et les DFM


    Depuis la toute première version de Delphi, celui-ci intègre des fichiers à l'extension DFM pour gérer les fiches créées dans le designer.
    Si vous créez un projet vierge, il possède par défaut une unité Unit1.pas et son fichier Unit1.dfm associé. Dans le code c'est la ligne {$R *.dfm} qui fait le lien entre les deux.

    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs;
    
    type
      TForm1 = class(TForm)
      private
        { Déclarations privées }
      public
        { Déclarations publiques }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    end.
    
    


    le fichier DFM est un fichier texte qui contient toutes les propriétés des objets de la fiche

    object Form1: TForm1
      Left = 259
      Top = 124
      Width = 1088
      Height = 563
      Caption = 'Form1'
      Color = clBtnFace
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -11
      Font.Name = 'MS Sans Serif'
      Font.Style = []
      OldCreateOrder = False
      PixelsPerInch = 96
      TextHeight = 13
    end


    Si j'ajoute un bouton sur la fiche, il apparaît dans la déclaration de la classe TForm1 et dans le DFM

    type
      TForm1 = class(TForm)
        Button1: TButton;
      private
        { Déclarations privées }
      public
        { Déclarations publiques }
      end;
    
    object Form1: TForm1
      Left = 259
      Top = 124
      Width = 1088
      Height = 563
      Caption = 'Form1'
      Color = clBtnFace
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -11
      Font.Name = 'MS Sans Serif'
      Font.Style = []
      OldCreateOrder = False
      PixelsPerInch = 96
      TextHeight = 13
      object Button1: TButton
        Left = 160
        Top = 72
        Width = 75
        Height = 25
        Caption = 'Button1'
        TabOrder = 0
      end
    end

    La ressource DFM


    Le fichier DFM est inclus dans l'exécutable sous forme binaire dans une ressource Windows portant le nom de classe de la fiche, ici TFORM1. On peut accéder à cette ressource par le code suivant :TResourceStream.Create(hInstance, 'TFORM1', RT_RCDATA);.

    L'unité Classes déclare la classe TReader qui est capable de lire un flux DFM. En temps normal, quand on crée une fiche - ou quand celle-ci est créée automatiquement par Delphi, c'est à dire dans le .DPR - elle utilise unTReader pour charger son DFM et instancier automatiquement tous ses composants avec la méthode ReadComponent. Ainsi la propriété Form1.Button1 pointera sur une instance de TButton sans qu'il soit nécessaire de le coder dans Unit1.pas.

    Mais on peut aussi exploiter TReader pour simplement consulter le contenu du DFM, voici une classe utilitaire pour remplir un TTreeView avec le contenu d'une ressource DFM.

    type
      TTreeReader = class(TReader)
      private
        FStream  : TResourceStream;
        FTreeView: TTreeView;
        function StringValue: string;
        procedure ReadNode(Parent: TTreeNode);
      public
        constructor Create(AResourceName: string; ATreeView: TTreeView);
        destructor Destroy; override;
      end;
    
    { TTreeReader }
    
    constructor TTreeReader.Create(AResourceName: string; ATreeView: TTreeView);
    begin
      FTreeView := ATreeView;
      FStream := TResourceStream.Create(hInstance, AResourceName, RT_RCDATA);
      inherited Create(FStream, 4096);
      ReadSignature; // Signature du DFM
      FTreeView.Items.Clear;
      ReadNode(nil); // lire les noeuds
    end;
    
    destructor TTreeReader.Destroy;
    begin
      inherited;
      FStream.Free;
    end;
    
    // Retourne une valeur quelconque sous forme de chaîne
    function TTreeReader.StringValue: string;
    var
      Str: string;
    begin
      case NextValue of
        vaSet:
        begin
          ReadValue;
          Result := '';
          repeat
            Str := ReadStr;
            Result := Result + ',' + Str;
          until Str = '';
          if Result = ',' then
            Result := '[]'
          else begin
            Result[1] := '[';
            Result[Length(Result)] := ']';
          end;
        end;
        vaIdent : Result := ReadIdent;
      else
        Result := ReadVariant;
      end;
    end;
    
    procedure TTreeReader.ReadNode(Parent: TTreeNode);
    var
      Flags   : TFilerFlags;
      Index   : Integer;
      strClass: string;
      strName : string;
      root    : TTreeNode;
    begin
      ReadPrefix(Flags, Index); // utile pour TFrame par exemple
    
      strClass := ReadStr; // la classe du composant
      strName  := ReadStr; // son nom
    
      root := FTreeView.Items.AddChild(Parent, strName + ':' + strClass);
    
      // Liste de propriétés du composant
      while not EndOfList do
      begin
        strName := ReadStr; // nom de la propriété, peut être sous la forme 'Font.Name'
        FTreeView.Items.AddChild(root, strName + ':' + StringValue);
      end;
      ReadListEnd;
    
      // Liste des enfants de ce composant
      while not EndOfList do
      begin
        ReadNode(root);
      end;
      ReadListEnd;
    end;
    


    En plaçant un TTreeView sur la fiche, je peux facilement afficher la ressource "TFORM1" :

    procedure TForm1.Button1Click(Sender: TObject);
    begin
      with TTreeReader.Create('TFORM1', TreeView1) do
        Free;
    end;
    




    Comme le code ci-dessus ne fait pas explicitement référence aux composants de la fiche, on pourrait très bien afficher un DFM contenant des composants inconnus de notre application. Mieux encore, on peut afficher un DFM qui n'a strictement rien à voir avec la bibliothèque de composants de Delphi !

    Les DFM exotiques !


    Et oui, car ce qui ne saute pas aux yeux de prime abord, c'est que le fichier DFM étant un simple fichier texte associé à l'unité par un {$R *.dfm}, on peut très bien lier à l'application un fichier DFM totalement différent comme celui-ci :

    object data: rootNode
      text = 'MainNode'
      object firstChild: childNode
        Image   =  1
        text    = 'Subnode'
        options = [One, Two, Three]
      end
    end


    On le lie au projet et le tour est joué !

    {$R ExtraFM.dfm}
    
    procedure TForm1.Button2Click(Sender: TObject);
    begin
      with TTreeReader.Create('ROOTNODE', TreeView1) do
        Free;
    end;
    




    Les seules contraintes sont dans le format du fichier, il doit déclarer des objets par un couple object/end, l'objet possède un couple d'identifiants nom : classe qui n'ont pas besoin d'être des classes objets de Delphi, il doivent simplement être des identifiants valides : pas d'espace, d'accents, commencer par une lettre... Ensuite on a une liste de propriétés nom = valeur qui supporte tous les types publiés de Delphi (y compris des images, de SET OF, etc), et enfin la liste des objets enfants. Ce n'est pas un format aussi souple que JSON mais c'est amusant malgré tout.

    Attention ! Pour toute erreur de syntaxe dans le fichier DFM vous aurez une erreur de compilation surprenante indiquant que RLINK32 ne parvient pas à compiler une ressource 16bits.

    Les DFM utiles


    Alors c'est bien beau, on peut donc utiliser les DFM pour stocker des informations non standard ... reste à savoir ce qu'on peut en faire. Et bien une idée m'est venue de suite à l'esprit. Et si j'utilisais un DFM pour modifier les propriétés de ma fiche ?

    object Form1: TForm1_extra
      Caption = 'Titre de la fenêtre'
      object Button3: TButton
        Caption = 'Bouton !'
      end
    end


    Je reprends ici la structure de ma fiche en ne conservant que quelques attributs...les Caption. On peut toujours associer cela et visualiser le résultat. Notez au passage que le nom de la ressource est lié à celui de la classe du premier objet et non celui du fichier; il faut donc choisir un autre nom que TFORM1 qui est déjà utilisé par la fiche elle-même.

    {$R ExtraData.dfm}
    
    procedure TForm1.Button3Click(Sender: TObject);
    begin
      with TTreeReader.Create('TFORM1_EXTRA', TreeView1) do
        Free;
    end;
    




    Et maintenant, un petit bout de code pour charger ces propriétés dans la fiche; on n'utilisera pas TReader.ReadComponent qui instancie les objets, mais TReader.ReadProperty pourra tranquillement faire son office.

    procedure TForm1.Button4Click(Sender: TObject);
    var
      Resource: TResourceStream;
      Reader  : TOpenReader;
    
      procedure ReadExtra;
      var
        Flags   : TFilerFlags;
        Index   : Integer;
        strClass: string;
        strName : string;
        Comp    : TComponent;
      begin
        Reader.ReadPrefix(Flags, Index); // don't care
        strClass := Reader.ReadStr;
        strName  := Reader.ReadStr;
    
        if strName = Self.Name then
          Comp := Self
        else begin
          Comp := FindComponent(strName);
          if Comp = nil then
            raise Exception.Create(strName + ':' + strClass + ' not found');
        end;
    
        while not Reader.EndOfList do
          Reader.ReadProperty(Comp);
        Reader.ReadListEnd;
    
        while not Reader.EndOfList do
          ReadExtra;
        Reader.ReadListEnd;
      end;
    
    begin
      Resource := TResourceStream.Create(hInstance, 'TFORM1_EXTRA', RT_RCDATA);
      try
        Reader := TOpenReader.Create(Resource, 4096);
        try
          Reader.ReadSignature; // DFM signature
    
          ReadExtra;
    
        finally
          Reader.Free;
        end;
      finally
        Resource.Free;
      end;
    end;
    


    Et voilà ! en cliquant sur Button4 le titre de la fiche et de Button3 ont changé !



    Et voilà comment vous pouvez sans trop d'effort créer une application multilingue avec des DFM additionnels épurés ne comprenant que les propriétés à modifier ! Mais pourquoi donc Borland/Embarcadero n'y ont pas pensé plus tôt ?

    Dernière astuce


    Avant de clore ce billet, une petite astuce complémentaire, contrairement aux DFM créés par l'IDE de Delphi, les DFM que vous créez à la main peuvent tout à fait faire référence à des propriétés non enregistrées dans l'IDE et même plusieurs fois à la même propriété.

    Ajoutons une propriété publiée à la fiche :

    type
      TForm1 = class(TForm)
        TreeView1: TTreeView;
        Button1: TButton;
        Button2: TButton;
        Button3: TButton;
        Button4: TButton;
        procedure Button1Click(Sender: TObject);
        procedure Button2Click(Sender: TObject);
        procedure Button3Click(Sender: TObject);
        procedure Button4Click(Sender: TObject);
      private
        { Déclarations privées }
        procedure SetMsg(Value: string);
      published
        property Msg: string write SetMsg;
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm1.SetMsg(Value: string);
    begin
      ShowMessage(Value);
    end;
    


    et exploitons la dans le DFM sans avoir à recenser la fiche dans l'IDE

    object Form1: TForm1_extra
      Msg = 'Chargement de TForm1_extra'
      Caption = 'Titre de la fenêtre'
      Msg = 'Le titre a changé'
      object Button3: TButton
        Caption = 'Bouton !'
      end
    end


    A chaque modification de la propriété Msg l'application nous affiche un message !

    Cette approche pourra aussi être utilisée pour désactiver (Active = False) un composant avant de modifier ses autres propriétés puis le réactiver (Active = True). On pourra aussi jouer séquentiellement sur les propriétésAlign, Anchors, Top, Left, Width, Height pour positionner des composants les uns par rapport aux autres.

    Conlusion


    Delphi a fêté il y peu ses 18 ans, mais au cours de toutes ces années, les idées que je viens d'exposer ne me sont jamais venus à l'esprit...et je ne les ai jamais vues exposées sur le net. Mais si je n'ai pas la primeur de l'idée, ou si le sujet vous inspire, n'hésitez pas à me contacter.

    Les source Delphi 6 de cet article sont compatibles avec Delphi XE2 sans aucune modification.

    http://lookinside.free.fr/delphi.php?Delphi+et+les+DFM

  • 相关阅读:
    Java基础——组合(Composition)和聚合(aggregation) (转)
    Java基础——安全地停止 SpringBoot 应用 (转)
    Java 基础——定时任务实现方式——SOLID 原则
    正态分布
    python django 使用numpy包 import numpy in django
    Python Django 安装 install
    节选《数据库高效优化》架构、规范与SQL技巧一书 有关SQL优化、索引、规范等
    ES
    疫情隔离在上海,跟邻居借了点花籽草籽~
    爱上洗脸盆 Love washbasin
  • 原文地址:https://www.cnblogs.com/findumars/p/4768409.html
Copyright © 2020-2023  润新知