20.3.1.5 动态DFM文件应用揭秘
1. 动态DFM文件概述
动态DFM文件是相对于静态DFM文件而言。所谓静态 DFM文件是指在Delphi开发环境中设计
窗体文件。窗体
设计过程就是程序
编制过程。因此,动态DFM文件就是指在程序运行过程生成或存 取
DFM文件。
动态DFM文件
创建和使用分别如下两种情况: 字串4
● 在程序运行过程中,由Create方法动态生成窗体或部件,然后 动态生成其它部件插入其中生成DFM文件 字串8
● 在Delphi开发环境中,设计生成DFM文件,然后用DFM 文件存取函数,或者用Stream对象和 Filer对象
方法,将DFM文件读入内存,进行处理,最后又存入磁盘中 字串3
由Delphi
窗体设计
常规方法生成
DFM文件在程序运行一开 始就规定了部件
结构。因为在窗体设计过程中,窗体中
每个部件都在程序
对象声明中定义了部件变量。这种固定
结构虽然能方便应用 ,但以牺牲灵活性为代价。 字串8
在Delphi应用程序中有时需要在运行过程中创建控制,然后将该控制插入另一个部件中。例如:
字串4
procedure TForm1.Button1Click(Sender: Tobject); 字串2
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文件
内容自动创建部件及其拥有
所有部件。
字串1
在使用动态DFM文件时有两点需要注意。 字串1
● 每一个动态插入
控制或部 件必须在程序中调用RegisterClass进行注册 字串4
● 读入DFM文件自动创建部件后,如果调用了InsertControl方法, 则在关闭窗口时要调 用RemoveControl方法移去该控制,否则会产生异常事件
字串6
2. 动态DFM文件应用之一:超媒体系统
卡片设计
Delphi多种类型
可 视部件,如文本部件、编辑部件、图形图像部件、数据库部件、媒体媒放部件和OLE部件等,每一种部件在屏幕中占据一定
区域,具有相当丰 富
表现能力,可以作为卡片中
一种媒体,因此可以利用这些可视部件进行超媒体系统
卡片设计。 字串5
超媒体卡片设计要求卡片中
媒 体数目和媒体种类是不受限制
,而且必须能够修改和存取卡片,因此,采用动态DFM文件是比较合适
。而且如果利用Stream对象,将卡片存 储在数据库BLOB字段中,就为把超文本与关系数据库技术结合起来创造了契机。 字串8
下面是超媒体卡片设计子系统中
部分源程序,它演示 了如何创建对象、插入对象和存取动态DFM文件。
字串2
⑴ 在应用程序中注册对象 字串5
procedure TMainForm.FormCreate(Sender: TObject); 字串5
begin
RegisterClass(TLabel);
RegisterClass(TEdit);
RegisterClass(TMemo);
RegisterClass (TButton);
RegisterClass(TPanel);
RegisterClass(TPanelP);
RegisterClass(TBitBtn);
…
end;
⑵ 创建和插入对象 字串6
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; 字串4
TEdit(ctrl).Top := Point.Y;
TEdit(ctrl).Left := Point.X;
TEdit(Ctrl).Height := 20;
BackGround.InsertControl(Ctrl);
…
end;
3:
…
end;
end;
字串1
⑵ 存取 动态DFM文件
字串5
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;
字串7
DesignWin是在TMainForm中定义
TMDIChild类型
窗体部件,是卡片设计平台;FFileName是私有变量,用来保存当前 编辑
卡片文件名。DesignWin
Init方法实现如下:
procedure TMDIChild.Init; 字串1
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
媒体属性编辑器。 字串5
动态DFM文件
存储过程是这样
: 字串2
procedure TMainForm.FileSave(Sender: TObject); 字串1
begin
if DesignWin.CurControl <> nil then
DesignWin.CurControl.Enabled := True;
WriteComponentResFile(FFilename, DesignWin);
DesignWin.Caption := FileName;
end;
end;
因为在DesignWin
Init方法中调用了InsertControl方法,所以在关闭DesignWin窗口时要相 应地调用RemoveControl,否则在关闭DesignWin窗口时会产生内存错误。
字串4
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;
字串6
字串8
3. 动态DFM文 件应用之二:超媒体系统脚本语言设计 字串6
超媒体脚本语言设计是超媒体系统设计
重要内容。脚本语言必须能够表达卡片中
多种媒体对 象,必须是可编程,可理解
,必须是可执行
,应该可以由脚本语言生成超媒体系统中
卡片和链。
字串8
DFM文件可以看作是超媒体系统
卡片,DFM脚本能够表达DFM文件中
多种控制,也就是说能够表达卡片中
多种媒体对象,再加上DFM脚本
对象式表达,可编辑性,可转换 为DFM文件,因此用作超媒体系统脚本语言较
形式。
字串1
ObjectBinaryToText和ObjectTextToBinary过程提供了在部件和DFM脚本之间相 互转化
功能,ObjectResourceToText和ObjectTextToResoure过程提供了DFM文件和DFM脚本之间相互转化
功能。这样就可以在应用程序中自 如实现超媒体卡片和超媒体脚本语言相互转化。
下面是卡片和脚本语言相互转化
程序: 字串6
procedure TMDIChild.CardToScript;
var
In, Out: TStream;
begin
In := TMemoryStream.Create;
Out := TMemoryStream.Create;
try
In.WriteComponentRes(Self.ClassName, Self);
ObjectResourceToText(In, out);
ScriptForm.ScriptEdit.Lines.LoadFromStream(Out);
finally
In.Free;
Out.Free;
end;
end;
ScriptEdit是个文本编辑器,它
Lines属性是TStrings类型
对象。 字串9
procedure TScriptForm.ScriptToCard;
var
In, Out: TStream;
begin
In := TMemoryStream.Create;
Out := TMemoryStream.Create;
try
ScriptForm.ScriptEdit.Lines.SaveToFromStream(In);
ObjectTextToResource(In, out);
In.ReadComponentRes(DesignWin);
finally
In.Free;
Out.Free;
end;
end;
这两段程序是对整 个卡片,即窗体级,进行转换
。ObjectBinaryToText和ObjectTextToBinary过程可以细化到部件级
转换。因此超媒体脚本语言
编辑可以 细化到媒体对象级。
4. 超媒体编辑和表现系统与动态DFM文件
扩展
字串5
超媒体系统
媒体编辑与卡片管理有其特殊
需求,比如 链接需求。这时采用已有
窗体部件和媒体部件并按常规
DFM文件处理就显得力不从心了。解决这个矛盾有两套方案: 字串7
● 利用 Delphi部件开发技术,继承和开发新
部件增加新
超媒体特有
属性和处理方法
字串1
● 扩展DFM文件结构,使之能按自己
需要任意地存 取和转换部件和DFM文件
前者是充分利用Delphi
面向对象部件开发技术,在存取和转换等处理上仍旧与常规DFM文件相同。 字串4
而 后者需要DFM
存取和转换上作比较大
改动。下文介绍扩展DFM文件
思路。
字串7
扩展动态DFM文件
总体思路是降低处理操作
数据
颗 粒度,即从原先窗体级降低到部件级。
字串4
下面是存取操作
扩展示范:
字串7
var 字串9
FileStream: TStream;
I: Integer;
begin
FileStream := TFileStream.Create('OverView.Crd', fmOpenWrite);
With TWriter.Create(FileStream, 4096) do
try
for I := 0 to DesignWin.ControlCount - 1 do
begin
WriteInteger(MMID[i]);
WriteRootComponent(DesignWin.Controls[i]);
{ 写相应媒体扩展信息 }
……
end;
WriteListEnd;
finally.
Free;
end;
FileStream.Free;
end;
WriteInteger(MMID[i])语句是写入媒体标识 。
字串6
下面是相应
读扩展DFM
程序:
var
PropInfo: PPropInfo;
Method : TMethod;
FileStream: TStream;
I: Integer;
begin
FileStream := TFileStream.Create('OverView.Crd', fmOpenRead);
With TReader.Create(FileStream, 4096) do
try
while not EndOfList do
begin
case ReadInteger of
IDText: begin
Ctrl := TControl(ReadRootComponent(nil));
PropInfo := GetPropInfo(Ctrl.ClassInfo, 'OnClick');
Method.Code:= Self.MethodAddress(MethodName);
Method.Data := Self;
if Method.Code <> nil then
SetMethodProp(Ctrl, PropInfo, Method);
DesignWin.InsertControl(Ctrl);
end;
IDImage:
……
end;
……
WriteListEnd;
end;
finally.
Free;
end;
FileStream.Free;
end;
字串4
SetMethodProp过程是用于重新联接控制和它
事件处理过程。类似
功能还可以用TReader对象
OnFindMethod事件
处理过 程来实现。 字串6
实现脚本语言扩展
基本方法与存取扩展类似,但它还要加扩展媒体信息转换为文本,并插入到部件
脚本描述中。 字串2
20.3.2 数据库BLOB字段应用
字串6
Delphi VCL提供了TBlobStream对象支持对数据库BLOB字段
存取。Delphi
TBlobStream对象
作用在于一方面可以使Delphi应用程序充分利用多媒体数据库
数据管理能力。另一方面又能利用Delphi Object Pascal
程序设计能力给关 系型多媒体数据库提供底层控制能力和全方位
功能扩展余地。 字串7
20.3.2.1 TBlobStream
使用
字串2
TBlobStream对象用一个 TBlobField类型
对象作为参数来创建与BLOB字段相联
BLOB流,接着就可用流
存取方法在BLOB字段中存取数据。 字串4
var
BlobStream: TBlobStream;
I: Integer;
begin
BlobStream := TBlobStream.Create(TBlobField(CardTable.Fields [10], bmWrite);
With TWriter.Create(BlobStream, 4096) do
try
for I := 0 to DesignWin.ControlCount - 1 do
begin
WriteInteger(MMID[i]);
WriteRootComponent(DesignWin.Controls[i]);
{ 写相应媒体扩展信息 }
… …
end;
WriteListEnd;
finally.
Free;
end;
BlobStream.Free;
CardTable.Post;
end;
字串1
Fields变量是表示数据库记录
字段数组,Fields[10]正是数据库
BLOB 字段。CardTable
Post方法将数据库
修改 反馈到数据库
物理存储上。 字串3
上面这段程序是超媒体卡片存储
部分源程序,我们就是将卡片保存在数据库BLOB字段中,实现将超文本 和关系数据库两种数据管理方式结合起来。读卡片
程序如下: 字串2
var
PropInfo: PPropInfo;
Method: TMethod;
Blobtream: TStream;
I: Integer;
begin
BlobStream := TBlobStream.Create(TBlobField (CardTable.Fields[10]), bmRead);
With TReader.Create(BlobStream, 4096) do
try
while not EndOfList do
begin
case ReadInteger of
IDText: begin
Ctrl := TControl(ReadRootComponent(nil));
PropInfo := GetPropInfo(Ctrl.ClassInfo, 'OnClick');
Method.Code:= Self.MethodAddress(MethodName);
Method.Data := Self;
if Method.Code <> nil then
SetMethodProp(Ctrl, PropInfo, Method);
DesignWin.InsertControl(Ctrl);
end;
IDImage:
……
end;
……
WriteListEnd;
end;
finally.
Free;
end;
FileStream.Free;
end;
字串4
20.3.2.2 BLOB字段与图形图像
字串3
在多媒体数据库中处理得比较多
是图形图像,因此 早期
多媒体数据库在扩展关系数据库时往往是增加一个图像字段。BLOB字段是以二进制数据存储方式,因此它完全可以表达图形图像数据。
字串9
在TBlobField对象中提供了LoadFromBitMap和SaveToBitMap方法存取位图数据。它们在实现上都是使用BlobStream对象。
procedure TBlobField.LoadFromBitmap(Bitmap: TBitmap); 字串3
var
BlobStream: TBlobStream;
Header: TGraphicHeader;
begin
BlobStream := TBlobStream.Create(Self, bmWrite);
try
if (DataType = ftGraphic) or (DataType = ftTypedBinary) then
begin
Header.Count := 1;
Header.HType := $0100;
Header.Size := 0;
BlobStream.Write(Header, SizeOf(Header));
Bitmap.SaveToStream(BlobStream);
Header.Size := BlobStream.Position - SizeOf(Header);
BlobStream.Position := 0;
BlobStream.Write(Header, SizeOf(Header));
end else
Bitmap.SaveToStream(BlobStream);
finally
BlobStream.Free;
end;
end;
procedure TBlobField.SaveToBitmap(Bitmap: TBitmap);
var
BlobStream: TBlobStream;
Size: Longint;
Header: TGraphicHeader;
begin
BlobStream := TBlobStream.Create(Self, bmRead);
try
Size := BlobStream.Size;
if Size >= SizeOf(TGraphicHeader) then
begin
BlobStream.Read(Header, SizeOf(Header));
if (Header.Count <> 1) or (Header.HType <> $0100) or 字串6
(Header.Size <> Size - SizeOf(Header)) then
BlobStream.Position := 0;
end;
Bitmap.LoadFromStream(BlobStream);
finally
BlobStream.Free;
end;
end;
程序中按两种方式存取数据,对于位图数据,数据
起点是流
Potition为0处,对于图形或其它类型
Blob数据,则以 流
Position为SizeOf(Header) 1处开始, 即多了个头信息。 字串8
20.3.2.3 BLOB字段与文本 字串3
Delphi BLOB字段中增加了大型文本
处理能力。可以在TBlobField和Strings中自由地交换数据。 字串6
procedure TBlobField.LoadFromStrings(Strings: TStrings); 字串6
var
BlobStream: TBlobStream;
begin
BlobStream := TBlobStream.Create(Self, bmWrite);
try
Strings.SaveToStream(BlobStream);
finally
BlobStream.Free;
end;
end;
procedure TBlobField.SaveToStrings(Strings: TStrings);
var
BlobStream: TBlobStream;
begin
BlobStream := TBlobStream.Create(Self, bmRead);
try
Strings.LoadFromStream(BlobStream);
finally
BlobStream.Free;
end;
end;
字串9
20.3.2.4 BLOB字段与Stream对象
因为Delphi中,BLOB字段是通过BLOB流来访问
,所以可以很容易 地在BLOB字段和Stream对象之间传递数据。为此,TBlobField对象提供了LoadFromStream和SaveToStream方法。
procedure TBlobField.LoadFromStream(Stream: TStream); 字串1
var
BlobStream: TBlobStream;
begin
BlobStream := TBlobStream.Create(Self, bmWrite);
try
BlobStream.CopyFrom(Stream, 0);
finally
BlobStream.Free;
end;
end;
procedure TBlobField.SaveToStream(Stream: TStream);
var
BlobStream: TBlobStream;
begin
BlobStream := TBlobStream.Create(Self, bmRead);
try
Stream.CopyFrom(BlobStream, 0);
finally
BlobStream.Free;
end;
end;
20.3.3 存取嵌入在OleContainer对象中
OLE服务器
数据 字串6
对象链接和嵌入 (Object Linking and Embedding,简称OLE),是一组服务功能,它提供了一种用来源于不同应用程序
信息创建复合文档
强有力方法。 字串4
通过把图像、图形、表格、声音、注解、文件和其它表示手段描述成对象,用它能在不同软件厂家提供
应用程序中更为容易地交换合 成和处理数据它是应用程序
集成更为容易。OLE2.0支持直观编辑。用户不需切换到不同窗口就能在文档中直接对对象进行操作,改进了操作 环境。用户不用再关注应用程序和操作环境,只需关注于使用对象技术
数据和文件,便能完成全部工作。
字串6
OLE已成为操作系统功能上
一大标准,各大软商纷纷在开发工具中支持OLE 2.0规范。Delphi 2.0提供了OleContainer对象支持OLE窗户应用程序
开发。 字串4
尽管通 过OLE可以用来源于不同应用程序
信息创建复合文档,充分体现以任务、以文档为中心
思想,但是很难分解来自其它应用程序中
嵌入数据 ,以进行特殊
处理。
字串3
例如,一套多媒体电子文档管理系统,系统需要数据库管理功能文档编辑功能,全文检索功能等。在文档编辑功 能
实现上,如果能利用中文Word 或写字板之类
强大
编辑排版功能,就可以省却重新开发一个文档编辑
费用,使用具有直观编辑
OLE 复合文档嵌入Word
DOC数据或RTF数据当然是最佳
选择。 但问题在于全文检索系统要求能直接在文档中搜索关键字,因此要求将文档数据从 OLE嵌入数据或文档中
本地数据中分离出来。
字串2
Delphi 2.0
OleContainer部件支持存储OLE对象数据。OLE对象数据包括两部分:OLE类 描述信息和OLE服务器嵌入数据。一般说来,OLE服务器嵌入数据是以服务器支持
数据格式存储
; 比方说,中文Word 6.0
嵌入数据
格式 就是Word 6.0文档
格式。因此,要将文档数据从OLE 嵌入式文档中分离出来就是要访问第二部分数据。
字串5
字串9
我们分析了Delphi 2.0
OleContainer对象存取复合文档
程序,得到分离数据
方法。 字串5
让我们来看一段OleContainer对象存储数据
程序:
字串6
procedure TOleContainer.SaveToStream(Stream: TStream);
var
DataHandle: HGlobal;
Buffer: Pointer;
Header: TStreamHeader;
R: TRect;
……
begin
……
try
……
if FOldStreamFormat then
begin
R := BoundsRect;
Header.PartRect.Left := R.Left;
Header.PartRect.Top := R.Top;
Header.PartRect.Right := R.Right;
Header.PartRect.Bottom := R.Bottom;
end else
begin
Header.Signature := StreamSignature;
Header.DrawAspect := FDrawAspect;
end;
Header.DataSize := GlobalSize (DataHandle);
Stream.WriteBuffer(Header, SizeOf(Header));
Buffer := GlobalLock(DataHandle);
try
Stream.WriteBuffer(Buffer^, Header.DataSize);
finally
GlobalUnlock(DataHandle);
end;
finally
ReleaseObject(TempStorage);
ReleaseObject(TempLockBytes);
end;
end;
字串5
程序中,OleContainer对象执 行了两次往流中写数据
操作。 字串5
Stream.WriteBuffer(Header, Size(Header));
字串8
Stream.WriteBuffer(Buffer^, Header.DataSize);
字串1
前一语句是写入OLE类描述信息,后一句语句是写入OLE服务器
嵌入数据。Header是TStreamHeader记录类型
变量 。TStreamHeader记录
定义如下: 字串8
TStreamHeader = record 字串3
case Integer of
0: ( { 新版OLE对象 }
Signature: Integer;
DrawAspect: Integer;
DataSize: Integer);
1: ( { 旧版OLE对象 }
PartRect: TSmallRect);
end;
因此读OLE服务器嵌入数据时,要跳过文件头
TStreamHeader记录。下面就是如何分离OLE服务器嵌 入数据
程序:
字串6
var 字串7
Stream : TMemoryStream;
FileStream : TFileStream;
begin
Stream := TMemoryStream.Create;
FileStream := TFileStream.Create('TEST.DOC', fmCreate) ;
with OleContainer1 do
if (State <> osEmpty) then
SaveToStream(Stream);
Stream.Seek(Sizeof(TStreamHeader), 0);
FileStream.CopyFrom (Stream, Stream.Size - SizeOf(TStreamHeader));
Stream.Free;
FileStream.Free;
end;
字串7
OleContainer1包含
服务器对象是中文Word 6.0,程序中将分离出
数据存储在磁盘文件“TEST.DOC”上。如果希望存储在不 同
媒介上,可以使用相应
Stream对象,分离
方法类似。但是,这种方法并非对所有
OLE服务器数据都适用,如Windows 95 附件中
写 字板(WordPad)就不行。 字串4
![我要研发网[www.51dev.com]](/templets/images/toplogo.gif)
