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

Delphi 和 DFM的更多相关文章

  1. delphi save .dfm to .txt

    procedure TForm2.saveDfm; var inStream,outStream:TMemoryStream; begin inStream:=TMemoryStream.Create ...

  2. delphi的流操作的语法

    Delphi在这两方面都做的相当出色.在Delphi的早期版本Turbo Pascal 中就曾有流(Stream).群(Collection)和资源(Resource)等专门用于对象式数据管理的类.在 ...

  3. Delphi中的文件扩展名

    Filename Extensions in Delphi http://delphi.about.com/od/beginners/a/aa032800a.htm Try building a sm ...

  4. 汉字与区位码互转(天天使用Delphi的String存储的是内码,Windows记事本存储的文件也是内码),几个常见汉字的各种编码,utf8与unicode的编码在线查询,附有读书笔记 good

    汉=BABA(内码)=-A0A0=2626(区位码)字=D7D6(内码)=-A0A0=5554(区位码) 各种编码查询表:http://bm.kdd.cc/ 汉(记住它,以后碰到内存里的数值,就会有敏 ...

  5. Delphi实现类的持久化保存(DFM格式)

    var inStream,outStream:TMemoryStream; begin inStream:=TMemoryStream.Create; outStream:=TMemoryStream ...

  6. Delphi使用ADO进行数据库编程

    Delphi是一个可视化的编程工具,ADO编程也是这样,所以话不多言,直接通过代码.截图和语言来说明. 我的数据库是Oracle,为了测试,先建一个表:create table practice(un ...

  7. Delphi在创建和使用DLL的时候如果使用到string,请引入ShareMem单元

    当使用了长字符串类型的参数.变量时,如string,要引用ShareMem. 虽然Delphi中的string功能很强大,但若是您编写的Dll文件要供其它编程语言调用时,最好使用PChar类型.如果您 ...

  8. Delphi控件之---通过编码学习TStringGrid(也会涉及到Panel控件,还有对Object Inspector的控件Events的介绍

    我是参考了万一的博客里面的关于TStringGrid学习的教程,但是我也结合自己的实际操作和理解,加入了一些个人的补充,至少对我有用! 学用TStringGrid之——ColCount.RowCoun ...

  9. Delphi控件之---UpDown以及其与TEdit的配合使用(比如限制TEdit只能输入数字,还有Object Inspector之组件属性的介绍)

    最近在开发中使用到了UpDown这个控件,但是因为之前没有使用过,所以很不熟悉,于是就编写了一个简单的demo来学习UpDown以及其结合TEdit的用法. 初步的常用功能的简介 目前(2015.08 ...

随机推荐

  1. js控件位置

    function ShowSettingDiv() { var div = document.getElementById('ShowDiv'); //将要弹出的层 div.style.display ...

  2. max_connections 与 max_used_connections --ERROR 1040: Too many connections

    mysql> show variables like 'max_connections'; +-----------------+-------+ | Variable_name | Value ...

  3. json-lib-2.4-jdk15.jar maven

    最近自己将一个web项目装换到使用mevan自动管理. 遇到了一个json包导入的问题.最终解决如下: <!-- https://mvnrepository.com/artifact/net.s ...

  4. Java中ArrayList问题:删除一个ArrayList中的重复元素,注意留意一个问题

    该问题有两种方法: 一 利用两个数组,此法简单,不讨论 二 利用一个数组,从第0个开始依次取元素,并在其后元素中查找是否有该元素,有则删掉后面的重复元素,依次遍历.---但是这种情况要特别注意,当后续 ...

  5. 一款js、css压缩工具yuicompressor

    //压缩JS java -jar yuicompressor-.jar --type js --charset utf- -v src.js > packed.js //压缩CSS java - ...

  6. 什么是php命名空间

    php命名空间是在5.3版本后加入的,命名空间反过来就是空间命名,在这里的空间命名就像window下的文件夹命名,命名空间用关键字namespace来定义.在这里用文件夹举三个例子,比如相对于test ...

  7. C++ hello world

    日文版本的vs 2008 , 在 < 新建 里面先创建一个项目 然后点击项目去创建一个C++的主启动文件 选择创建的文件类型 然后在文件里面写入代码 #include<iostream&g ...

  8. 关于“SQL Server 阻止了对组件 'Ad Hoc Distributed Queries' 的 STATEMENT'OpenRowset/OpenDatasource' 的访问 ”

    原因:在从远程服务器复制数据到本地时出现“SQL Server 阻止了对组件 'Ad Hoc Distributed Queries' 的 STATEMENT'OpenRowset/OpenDatas ...

  9. oracle创建实例SID

    用oracle用户登录 输入startx开发可视化界面,打开命令行模式 (如果只有壁纸,没有桌面图标和任务栏,按下 Ctrl + Alt + T 打开命令行) 输入dbca打开配置窗口 最后就各种下一 ...

  10. CentOS下的svn强制用户提交时写日志

    问题:在项目提交时候不写日志,在后期查看修改历史时需要对比版本才知道提交原因.解决方案:在svn服务端通过hooks在提交时强制要求写日志.#!/bin/shREPOS="$1"T ...