20.3.1.5 動態DFM文件應用揭秘
1. 動態DFM文件概述
動態DFM文件是相對於靜態DFM文件而言。所謂靜態DFM文件是指在Delphi開發環境中設計的窗體文件。窗體的設計過程就是程序的編制過程。因此,動態DFM文件就是指在程序運行過程生成或存取的DFM文件。
動態DFM文件的創建和使用分別如下兩種情況:
● 在程序運行過程中,由Create方法動態生成窗體或部件,然後動態生成其它部件插入其中生成DFM文件
● 在Delphi開發環境中,設計生成DFM文件,然後用DFM 文件存取函數,或者用Stream對象和Filer對象的方法,將DFM文件讀入內存,進行處理,最後又存入磁盤中
由Delphi的窗體設計的常規方法生成的DFM文件在程序運行一開始就規定了部件的結構。因為在窗體設計過程中,窗體中的每個部件都在程序的對象聲明中定義了部件變量。這種固定的結構雖然能方便應用,但以犧牲靈活性為代價。
在Delphi應用程序中有時需要在運行過程中創建控制,然後將該控制插入另一個部件中。例如:
procedure TForm1.Button1Click(Sender: Tobject);
var
Ctrl: TControl
begin
Ctrl := TEdit.Create(Self);
Ctrl.Top := 100;
Ctrl.Left := 100;
Ctrl.Width := 150;
Ctrl.Height := 20;
InsertControl(Ctrl);
end;
動態插入控制的優點是可以在任何時刻、任意位置插入任意數量的任何類型的控制。因為應用程序需求在很多情況下是在程序運行中才知道的,所以動態插入控制就顯得很重要。而且在很多情況下,需要保存這些界面元素,留待程序再次調用。例如應用程序界面的定制、系統狀態的保存、對話框的保存等。這時生成動態DFM文件是最佳選擇。
動態插入控制的不足之處是在插入控制前,無法直觀地看到控制的大小、風格、位置等,也就是動態插入控制的過程是非可視化的。但可以借助於靜態DFM文件的可視化設計。這就是生成和使用動態DFM文件的第二種方法。也就是在應用程序運行前,在Delphi開發環境中,使用可視化開發工具設計所需窗口或部件的樣式,以DFM文件保存。然後在應用程序運行過程中,將DFM文件讀入內存。Delphi的Stream對象和Filer對象在讀取DFM文件時,會根據DFM文件的內容自動創建部件及其擁有的所有部件。
在使用動態DFM文件時有兩點需要注意。
● 每一個動態插入的控制或部件必須在程序中調用RegisterClass進行注冊
● 讀入DFM文件自動創建部件後,如果調用了InsertControl方法, 則在關閉窗口時要調用RemoveControl方法移去該控制,否則會產生異常事件
2. 動態DFM文件應用之一:超媒體系統的卡片設計
Delphi多種類型的可視部件,如文本部件、編輯部件、圖形圖像部件、數據庫部件、媒體媒放部件和OLE部件等,每一種部件在屏幕中占據一定的區域,具有相當豐富的表現能力,可以作為卡片中的一種媒體,因此可以利用這些可視部件進行超媒體系統的卡片設計。
超媒體卡片設計要求卡片中的媒體數目和媒體種類是不受限制的,而且必須能夠修改和存取卡片,因此,采用動態DFM文件是比較合適的。而且如果利用Stream對象,將卡片存儲在數據庫BLOB字段中,就為把超文本與關系數據庫技術結合起來創造了契機。
下面是超媒體卡片設計子系統中的部分源程序,它演示了如何創建對象、插入對象和存取動態DFM文件。
⑴ 在應用程序中注冊對象
procedure TMainForm.FormCreate(Sender: TObject);
begin
RegisterClass(TLabel);
RegisterClass(TEdit);
RegisterClass(TMemo);
RegisterClass(TButton);
RegisterClass(TPanel);
RegisterClass(TPanelP);
RegisterClass(TBitBtn);
…
end;
⑵ 創建和插入對象
procedure TMDIChild.FormClick(Sender: TObject);
var
Ctrl : TControl;
Point: TPoint;
begin
GetCursorPos(Point);
Point := BackGround.ScreenToClient(Point);
case CurToolIndex of
1 : begin
Ctrl := TLabel.Create(self);
TLabel(Ctrl).AutoSize := False;
TLabel(ctrl).Caption := 'Label'+S;
TLabel(ctrl).Name := 'Label 1';
TLabel(ctrl).Top := Point.Y;
TLabel(ctrl).Left := Point.X;
TLabel(Ctrl).Height := Round(100*Res/1000/Ratio);
TLabel(Ctrl).Width := Round(600*Res/1000/Ratio);
TLabel(Ctrl).Color := clWhite;
TLabel(Ctrl).Font.Color := clBlack;
TLabel(Ctrl).Font.Name := 'Roman';
TLabel(Ctrl).Font.Height := -TLabel(Ctrl).Height;
TLabel(Ctrl).Font.Pitch := fpFixed;
TLabel(Ctrl).Enabled := False;
TLabel(Ctrl).OnClick := LabelClick;
TLabel(Ctrl).OnMouseMove := ReportPos;
BackGround.InsertControl(Ctrl);
CurTool.Down := False;
CurTool := nil;
…
end;
2: begin
Ctrl := TEdit.Create(self);
TEdit(ctrl).AutoSize := True;
TEdit(ctrl).Top := Point.Y;
TEdit(ctrl).Left := Point.X;
TEdit(Ctrl).Height := 20;
BackGround.InsertControl(Ctrl);
…
end;
3:
…
end;
end;
⑵ 存取動態DFM文件
procedure TMainForm.FileOpen(Sender: TObject);
begin
if OpenDialog.Execute then
begin
DesignWin := TMDIChild.Create(Application);
ReadComponentResFile(OpenDialog.FileName, DesignWin);
DesignWin.Init;
FileName := OpenDialog.FileName;
DesignWin.Caption := FFileName;
end;
end;
DesignWin是在TMainForm中定義的TMDIChild類型的窗體部件,是卡片設計平台;FFileName是私有變量,用來保存當前編輯的卡片文件名。DesignWin的Init方法實現如下:
procedure TMDIChild.Init;
var
I: Integer;
Ctrl: TControl;
begin
BackGround.BringToFront;
with BackGround do
for I:= 0 to ControlCount - 1 do
if Controls[I].Name <> ''then
ObjectIns.ObjectList.Items.AddObject(Controls[I].Name, Controls[I]);
end;
BackGround是TPanel類型的部件,所有的動態創建對象都插入到BackGround中,所以,後面調用BackGround.InsertControl(Ctrl);ObjectIns是個仿Delphi 的媒體屬性編輯器。
動態DFM文件的存儲過程是這樣的:
procedure TMainForm.FileSave(Sender: TObject);
begin
if DesignWin.CurControl <> nil then
DesignWin.CurControl.Enabled := True;
WriteComponentResFile(FFilename, DesignWin);
DesignWin.Caption := FileName;
end;
end;
因為在DesignWin的Init方法中調用了InsertControl方法,所以在關閉DesignWin窗口時要相應地調用RemoveControl,否則在關閉DesignWin窗口時會產生內存錯誤。
procedure TMDIChild.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
I: Integer;
Ctrl: TControl;
Removed: Boolean;
begin
if Modified = True then
if MessageDlg('Close the form?', mtConfirmation,
[mbOk, mbCancel], 0) = mrCancel then
CanClose := False;
if CanClose = True then
begin
repeat
removed := False;
I := 0;
repeat
if BackGround.Controls[I].Name <> '' then
begin
BackGround.RemoveControl(BackGround.Controls[I]);
Removed := True;
end;
I := I + 1
until (I >= BackGround.ControlCount) or (Removed = True);
until (Removed = False);
SendMessage(ObjectIns.Handle, WM_MDICHILDCLOSED, 0, 0);
end;
end;