对象链接和嵌入(Object Linking and Embeding)是一组服务功能,它提供了一种用源于不同应用程序的信息创建复合文档的强有力方法。 对象可以是几乎所有的信息类型,如文字、位图、矢量图形,甚至于声音注解和录像剪辑等。
Windows附件组中的书写器是应用OLE的实例,使用单击“对象 | 插入”菜单项, 书写器弹出插入对话框,对话框中列出了多个OLE服务器程序,如公式编辑工具,绘图工具,报表生成工具。用户双击鼠标左键,可激活一个OLE服务器。在OLE服务器中可编辑OLE对象,当用户返回到书写器中时,在书写器文档中将出现OLE对象。
Delphi支持OLE技术,Delphi1.0可以创建OLE应用程序,Delphi2.0可创建OLE自动化服务器和控制器程序。本章通过例程介绍对象链接与嵌入的基本概念,Delphi创建OLE对象的方法,OLE自动化的概念以及如何开发OLE自动化服务器和控制器。
8.1 OLE简介
8.1.1 OLE1.0和OLE2.0
迄今为止,有两种版本的OLE:OLE1.0和OLE2.0。当用户在OLE1.0 服务器中激活OLE对象,服务器程序在前台打开自己的窗体,并获得焦点。OLE窗体失去焦点,存在于单独的窗体之中。
OLE2.0服务器采用“本地”(in place)激活方式。本地激活意味着服务器菜单与应用程序菜单要进行融合,服务器的状态条更换应用程序状态条,服务器的工具条更换应用程序工具条。OLE对象在应用程序窗体中进行编辑,但所有过程均由服务器处理。
创建OLE对象的服务器决定了OLE的激活方式。如果一个OLE1.0的对象在OLE2.0 编译的应用程序中打开,它将采用OLE1.0的方式。
8.1.2 链接与嵌入
链接对象的数据保存在OLE服务器创建的文件中,嵌入对象的数据保存在OLE应用程序中。
链接对象必须以文件形式保存,只有对OLE服务器已经创建好的OLE对象, 才能进行OLE链接,链接的OLE对象文件可被OLE应用程序或其它程序进行修改,OLE 服务器和其它OLE应用程序也可以访问和修改OLE对象。对象数据保存在某一处,但可以被多个应用程序访问。
Delphi应用程序可以得到OLE对象文件中的最新数据。当OLE 对象数据被应用程序修改时,这些变化将在所有包含该对象的其它应用程序中体现。
嵌入对象保存在OLE应用程序中,其它应用程序不能访问该对象。只有在OLE应用程序中激活OLE对象才能对其进行编辑。嵌入的OLE对象不需要保存在文件中,所有数据都在应用程序中,这就确保了OLE数据不会被偶然地删除或修改。不足之处是应用程序的规模因为保存了OLE数据而增大了 。
如果用户想保存对嵌入对象的修改,可以把OLE数据存入文件中,本章第3 节将详细讨论这个问题。
表8.1 使用链接或嵌入的原则。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
何时使用链接 何时使用嵌入
───────────────────────────────
想要对源对象进行修改及将 对源对象进行修改,并将这
这些修改反映到其他与源对 些修改反映在一个特定的应
象链接的应用程序或文本中 用程序或文本中
源对象可能被多个
OLE 应 源对象不可能被一个OLE 应用程序应用程序频繁修改 用程序频繁修改
源对象的文件不会被频繁移 源对象的文件可能被频繁移
动,且不会被删除
动,且不会被删除对象很大,一般通过网络或 对象很小,或对象很大却无法
电子邮件进行分配 通过网络或电子邮件进行分配
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8.1.3 设计状态OLE对象的创建
在Delphi中,可分别在设计状态或运行状态创建OLE对象,表8.2说明了两种状态创建对象的差别。
表8.2 设计、运行状态OLE对象的创建
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
设计状态OLE对象的创建 运行状态OLE对象的创建
──────────────────────────────────────
对象保存在运行文件中,增加了所需 对象保存在一个文件中或只在运行时
编译的程序的规模 才有,减小了编译程序的规模
开发者需在设计时访问OLE服务器 开发者不需要在设计时访问OLE服务器
运行时OLE对象已经创建,减小了 运行时OLE对象已经创建,增加了运行
运行时间 时间
OLE对象在设计运行时间可行性编辑 OLE对象只能在运行时编辑
应用程序的OLE对象数目在设计时已 应用程序可以在运行时创建新的OLE对
经确立 象
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
在设计状态,OLE服务器不能被本地激活,只能在自己的窗体内激活。但在运行状态,
只要OLE服务器支持本地激活,就可以使用这种方式。
8.1.4 OLE类、文件、项目
OLE类决定创建OLE对象的服务器。有些应用程序需要创建多种类型的OLE对象,例如应用程序同时链接或嵌入公式、图片等。OLE类也决定OLE对象所包含的数据类型,链接或嵌入对象均要定义OLE类。
OLE文件是包含OLE对象数据的源文件。链接对象必须使用对象文件,因为链接对象在文件中保存。如果应用程序从已存在的源文件中创建嵌入对象,也要使用OLE文件。例如,如果链接到QuattiPro笔记本的OLE对象TUTOR.WBI存储在D:\DFFICE\QPW目录下,则OLE文件就是D:\DFFICE\QPW\TUTOR.WBI。值得注意的是OLE文件只能为链接对象所定义,而对于嵌入对象,只需定义OLE类。
OLE项目是代表链接或嵌入数据的OLE文件中的一部分。当应用程序希望OLE对象包含比OLE文件小的数据块时,则必须使用OLE项目。
例如,在QuattiPro笔记本中,OLE对象链接了GasCosts的B4 到B5 范围的网格,OLE项目是$GasCosts;$B$4.$B$5。
8.2 设计状态OLE对象的创建
Dephi可以在设计状态和运行状态中创建OLE对象。本节介绍设计状态OLE对象的创建。
8.2.1 TOLEContainer部件
要创建OLE对象,需在窗体中加入OLE包容器部件。 应用程序部件包含链接或嵌入的对象。用该部件可显示在OLE服务器编辑的数据。部件的ObjClass,ObjDoc,ObjItem 属性分别定义OLE类、文件、项目。要定义OLE对象是否本地激活,使用InPlaceActive 属性。如果OLE对象可以本地激活,OLE服务器菜单将与OLE应用程序的菜单进行融合,GroupIndex属性的值将决定菜单融合情况。
8.2.2 OLE对象创建的步骤:
1.在窗体中增加OLE包容器部件;
2.在Object inspector中单击ObjClass或ObjDoc属性的省略按钮,将出现插入对象对话框;
3.如果要插入的OLE 对象已存储在文件中,选择“Creat From File”,而后定义该对象的文件名和路径名。如果是链接对象,则选择链接检查框。 如果是嵌入对象,选择“Creat new”,并在对象类型列表框中选择OLE对象;
4.选择OK按钮;
如果是创建新对象,OLE服务器将激活,则可对OLE对象进行编辑,完成编辑后关闭OLE服务器。典型的例子是单击服务器中的“File”或“File|Update”菜单。
5.此时ObjClass属性中包含了相应的值,如果OLE对象从已存在的文件中创建或插入一
个链接对象,ObjDoc属性包含了OLE文件。
在设计对象状态时也可以粘贴OLE对象,其步骤如下:
1.激活服务器应用程序,选择OLE包容器部件;
2.在服务器中,将数据或对象拷贝到剪切板;
3.进入Delphi集成开发环境,选择OLE包容器部件;
4.在 Object inspector窗体中选择ObjItem属性的省略(…)按钮;
5.在列表中选择OLE对象;
6.选择“Paste"创建一个嵌入对象或选择"Pastelink"创建链接对象;
7.选择OK。
OLE包容器部件在此时初始化。如果粘贴一个嵌入对象,ObjClass属性将包含适当的值。如果粘贴一链接对象,ObjClass,ObjDoc,ObjItem属性将全部定义。OLE 应用程序部件包含代表OLE对象的图片。
如果OLE服务器程序支持OLE对象的拖放功能,则在设计状态从服务器中拖动对象至应用程序,应用程序将创建链接对象,具体步骤:
1.激活服务器,并Delphi集成开放环境中选择要链接的对象;
2.按隹鼠标左键拖动OLE对象至设计状态的窗体;
3.松开鼠键释放OLE对象。
窗体将创建OLE应用程序并进行初始化。
8.3 OLE应用程序的开发
Delphi可以在设计状态和运行状态创建OLE对象,上一节介绍的是在设计状态如何创建OLE对象,这一节将通过例程介绍如何在运行状态创建OLE对象、粘贴对象、拖动对象,以及OLE 对象的文件操作。我们开发的 OLE.dpr是一个OLE应用程序的实例
8.3.1 OLE应用程序界面开发
OLE.dpr采用了多文档界面,父窗体有菜单,工具条,状态条,子窗体有一个OLE包容器部件,下面分别加以介绍。
8.3.1.1 OLE应用程序的菜单
OLE应用程序的菜单与其它应用程序的主菜单大体一致,如果应用程序中有支持本地激活的OLE 2.0对象,则要进行菜单融合。查阅OLE 服务器的资料可知道服务器是否支持本地激活。
OLE应用程序菜单的GroupIndex属性决定融合菜单的位置,即融合菜单是更换主菜单,还是插入至应用程序的主菜单中。
OLE服务器,将融合三组菜单:Edit,View,Help,每组菜单分配了唯一的组索引值。在OLE应用程序中任何索引值为1,3,5的菜单组在菜单融合时被OLE服务器中具有相应索引值的菜 单更换。在本例程中,编辑菜单项在菜单融合时被服务器的"Edit"替换。如图8.3。 要想保存应用程序中的菜单,分配有异于1,3,5的索引值。
表8.3 融合后的菜单
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
菜单 索引值 功能描述 来源(OLE激活时)
────────────────────────────────
文件 0 使用文件和退出程序 OLE应用程序
Edit 1 编辑OLE对象 OLE服务器
对象 2 操作未激活的OLE对象 OLE应用程序
View 3 修改OLE对象的观测方式 OLE服务器
窗体 4 操纵窗体 OLE应用程序
Help 5 访问服务器在线帮助 OLE服务器
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8.3.1.2 OLE工具条和状态条
当OLE对象被本地激活时,OLE服务器将试图用自己的工具条和状态条替换OLE应用程序的。如果应用程序想要本地激活, 就应该在应用程序中编写相应的代码让服务器使用工具条和状态条。要做到这点,必须:
● 设置工具条和状态条
● 在应用程序中加入状态条
通过修改面板部件的属性创建工具条和状态条。
当OLE对象被本地激活时,面板或其他对齐控制将与OLE服务器程序进行协调。 这意味
OLE服务器可以替换OLE应用程序窗体中任何对齐控制,但锁定的控制不能被替换。例如,
如果面板的
align 属性是alTop,alleft,alBottom,alIngh 时,控制未锁定,OLE 服务器可以替换。要使应用程序的工具条、状态条不被替换,可将locked 属性设置成真值。当
OLE 对象被激活,OLE 服务器在状态条中显示有关信息时,OLE 应用程序部件的OnStatusLineEvent 事件发生,一个文本字符会将从OLE 服务器传至该事件句柄。 OnStatusLineEvent 事件句柄的MSG 参数接受文本字符。以下代码用以状态条接收OLE服务器的信息:
procedure TOLEObjectForm.OleContainerStatusLineEvent(Sender: TObject;
Msg: String);
begin
OLEFrameForm.StatusBarPanel.Caption := Msg
end;
8.3.2 插入OLE对象
运行状态时进行对象链接与插入也要用到插入对话框,Delphi中没有插入对话框部件,但可调用InsertOLEObjectDlg 函数来显示对话框。
8.3.2.1 InsertOLEObjectDlg函数声明如下:
function InsertOleObjectDlg(Form: TForm; HelpContext: THelpContext;
var PInitInfo: Pointer): Boolean;
其中参数
Form 是拥有插入对话框的窗体,一般将拥有OLE 包容器部件的窗体名字传给Form.参数Helplontext为插入对象对话框定义在线帮助,如果应用程序没有在线帮助, HelpContext的值为零,对话框中将不出现帮助按钮。
参数PInitInfo是一个无类型指针,该指针指向一个包含初始化OLE 部件信息的内部数据结构。InsertOLEObjectDlg修改这个指针以指向一个有效的数据结构,该结构包含了对话框列表中被选择的OLE 对象初始化信息。当该指针被使用后,应调用ReleaseOLEInitInfo过程释放初始化信息所占用的内存。
当用户选择OK 按钮关闭插入对象对话框,InsertOLEObjectDlg 返回真值,并把 PInitInfo指向包含OLE对象的初始化信息的数据结构。
8.3.2.2 初始化OLE包容器部件
为了使OLE包容器部件包含OLE对象,必须对部件进行初始化。 初始化主要是定义部件的OLE类。如果定义了OLE文件和OLE项目,初始化完成后,OLE 应用程序部件将包含OLE对象。
调用InsertOLEObjetDlg函数可在其参数PInitInfo获得关于OLE对象初始化的信息时,把它传递给OLE包容器部件的PInitInfo属性,OLE包容部件的ObjClass,ObjDoc,ObjItem属性将被自动定义。
初始化完成后,OLE对象被击活。OLE服务器将获得控制,用户可通过OLE服务器对OLE对象进行编辑。当程序冻结OLE对象,OLE包容器部件将包含一幅图像或位图代表OLE对象。定义OLE包容器部件的AutoActive属性可重新激活OLE对象,缺省情况下,双击OLE包容器部件可击活OLE对象。
例程中初始OLE对象的代码如下:
procedure TOLEObjectForm.InitializeOLEObject(Info: Pointer);
begin
OLEContainer.PInitInfo := Info;
ReleaseOLEInitInfo(Info)
end;
该过程先将初始化指针传给
OLE 包容器部件的PInitInfo 属性,而后释放其内存空间。当用户单击例程中的“编辑
| 插入”菜单项,将弹出插入对象对话框,选择对象类型后, OLE 对象被激活,该过程的代码如下:procedure TOLEObjectForm.InsertObject1Click(Sender: TObject);
var
Info: Pointer;
begin
if InsertOLEObjectDlg(OLEFrameForm, 0, Info) then
InitializeOLEObject(Info);
end;
8.3.3 冻结OLE对象
如果OLE对象是OLE 1.0服务器创建,对象将在OLE服务器中被击活,焦点和控制移到OLE服务器中。要冻结一个由OLE 1.0创建的对象选择"File | Exit"菜单项。
如果OLE 2.0服务器支持本地激活,激活OLE对象后OLE服务器将进行菜单融合,并转换工具条和状态条。要冻结对象,只需在应用程序窗体中异于OLE包容器部件的任何地方单击鼠
标键即可。
另一种冻结对象的方法是把OLE包容器部件的Active属性设置成假值。在例程中,“对象|冻结”菜单项实现冻结功能。代码如下:
procedure TOLEObjectForm.Deactivate1Click(Sender: TObject);
begin
OLEContainer.Active := False
end;
8.3.4 粘贴OLE对象
一些OLE服务器允许用户把OLE对象复制到剪贴板,如果一个OLE对象复制到剪贴板上,OLE应用程序可通过初始化OLE包容器部件来粘贴OLE对象。
8.3.4.1 粘贴对话框
把OLE对象粘贴到OLE包容器部件,要使用粘贴对话框,Delphi 中没有粘贴对话框部件,但可用PasteSpecialDlg函数显示粘贴对话框。
PasteSpecialDlg 函数声明如下:
function PasteSpecialDlg(Form :TForm;Const First:arrang; HelpConcert: THelpCOntext;var Forrmat : Word; var Hardle : THanlle var PInitInfo :Point ) : Boolean;
PasteSpecialDlg参数定义如下:
参数Form是拥有粘贴对话框的窗体,应把包含OLE包容器部件的窗体名字传递给Form。
参数Format是注册对象格式的数组,每组格式是BOLEFormat类型的数组成员。例如应用程序可注册两种对象格式。为嵌入对象注册FEmbedClipFmt ,为链接对象注册FlinkClipFmt。
BOLEFormat 声明如下:
BOLEFormat: Record
fmtID : Word;
fmtName : array[0..31] of char;
fmtResultName : array[0..31] of char;
fmtMediun : BOleMedium;
fmIsLInkble : Bool;
end;
fmtID是对象的剪贴板格式ID号,fmtID 可以是标准的剪贴板格式:CF_TEXT,CF_BIFMAP。使用OLE 对象时, 需注册新的剪贴板格式来处理OLE 对象。Windows的API中 的RegisterClipbordFormat函数注册格式。
fmtName表示是对象的名字,用以定义出现在粘贴对话框中列表框 内的对象名称。在例程中,把“%S”匹配给fmtName,OLE服务器自动地把格式化的名字代替“%S”参数。例如,如果OLE服务器是画笔,在程序运行时“Paintbrush Picture Object”将代替“%S”。
fmtResultName,定义出现在粘贴对话框中结果检查框内的名字。在例程中, 把“%S”传给了fmtResultName。OLE服务器自动地把格式结果名称代替“%S”参数。例如,如果OLE服务器是画笔,程序运行时“Paintbrush Picture”将代替“%S”。
fmtMedium是BOLEMedium类型,是Windows决定对象格式的数据类型。例如,OLE 联
接对象的格式是BOLE_MED_STREAM。OLE嵌入对象的格式是BOLE_MED_STORAGE。BOLEMedium函数可计算出需要的BOLEMedium类型。
fmtIsLinkale决定对象格式是否可联连。联连对象的fmtIsLinkable为真值。嵌入对象的fmtIsLinkable为假值。
参数HelpContext 为粘贴对话框定义在线帮助。如果应用程序没有在线帮助,HelpContext的值为零,对话框中将不出现帮助按钮。
参数Form用以定义剪粘板上的格式,是由PasteSpecialDlg函数进行修改。因为使用粘贴对话框时,应用程序并不知道剪贴板的格式。因而用Format来处理剪贴板的数据。在本章例程中。 PasteSpecialDlg 函数把format 变量修改成FEmbedClipFmt 或FLinkClipFmt格式,这两种格式是在主窗体的OnCreate事件中定义的。如果剪贴板上的数据不是OLE对象,Format将被修改成其它类型的格式,如CF_TEXT等。
参数Landle定义剪贴板上的数据句柄。由PasteSpecialDlg函数进行修改。 当剪贴板的数据类型不是OLE对象时,需用Handle参数访问剪贴板数据。Handle是句柄类型。
参数PInitInfo是一个指向OLE对象初始化结构的指针。前面在讲述初始化OLE应用程序部件时也用到了这种指针。PasteSpecialDlg函数将修改PInitInfo指针以使其指向一个有效的数据结构。该结构包括了粘贴对话框中被选中的OLE对象的初始化信息。
下面介绍粘贴对话框中的部件。
● 将剪贴板上的数据插入OLE应用程序,以实现对象嵌入,须选择"Paste";
● 在OLE服务器资源文件与OLE应用程序之间建立联连,以实现对象联连,须选择: "Paste Line;
● 要将闻连与嵌入的对象显示成图标,选择"Display As Icon"。若这个检查框被选中,改变图标("Chang Icon")按钮将显示通过这个按钮可改变OLE对象的缺省图标或标签。
● 如果数据不是注册的格式,"Paste","Paste link"选择键将变灰。 用户无法从剪贴板上粘贴数据。在本章例程中,剪贴板上的数据只能是FEmbedClipFmt(嵌入对象) 和FlinkClipFmt(链接对象)。
● 用户在列表框中选择数据类型。有时数据被解释成多种类型。例如在包含OLE服务器功能的字处理器中把文本复制到剪贴板中。应用程序可以以文本和OLE对象两种方式粘贴对象。列表框中出现的选择项由OLE服务器决定。
用户在粘贴对话框中选择OK按钮,PasteSpecialDlg返回真值,关于OLE 应用程序的初始化信息贮存在PInitInfo所指向的结构中。
8.3.4.2 在剪贴板中使用OLE对象
要把OLE对象粘贴到OLE应用程序中,必须用Windows的 RegisterClipboardFormat函数为链连对象、嵌入对象注册两种新的剪贴板格式。这些格式将在BOLEFormat记录的fmtIdt域中被用到。
本章例程中, 程序在OnCreate事件中注册OLE对象的剪贴板格式,以下代码是主窗体的OnCreate事件:
procedure TOLEFrameForm.FormCreate(Sender: TObject);
begin
FEmbedClipFmt := RegisterClipboardFormat('Embedded Object');
FLinkClipFmt := RegisterClipboardFormat('Link Source');
Fmts[0].fmtId := FEmbedClipFmt;
Fmts[0].fmtMedium := BOLEMediumCalc(FEmbedClipFmt);
Fmts[0].fmtIsLinkable := False;
StrPCopy(Fmts[0].fmtName, '%s');
StrPCopy(Fmts[0].fmtResultName, '%s');
Fmts[1].fmtId := FLinkClipFmt;
Fmts[1].fmtMedium := BOLEMediumCalc(FLinkClipFmt);
Fmts[1].fmtIsLinkable := True;
StrPCopy(Fmts[1].fmtName, '%s');
StrPCopy(Fmts[1].fmtResultName, '%s');
RegisterFormAsOleDropTarget(Self, Fmts)
end;
程序传给RegistClipBroardFormat函数一个描述格式的参数,它返回一个Word类型的值。该值能唯一的辨识新注册的格式。FEmbdeClipFmt,FlinkClipFmt 是TOLEFormat类的私有数据成员。 声明如下:
TYPE
TOLEForaneForm = Class(TForm)
…
private
FEmbedClipFmt: Word;
FLinkClipFmt: Word;
function CreateChild: TOLEObjectForm;
public
Fmts: array[0..1] of BOleFormat;
end;
在注册剪贴板格式后, 还必须定义OLE 格式才能进行对象粘贴。 每种格式定义在BOLEFormat记录中。 程序中可能注册标准剪贴板格式并用这种格式进行粘贴。例如:注册文本作为粘贴格式,将BOLEFormat记录为fmtId域定义为CF_TEXT,fmt Medium 域定义为BOLE_MED_HGLOBOL。 BOLEMediumCalc 函数可以根据定义的剪贴板格式计算出fmtMedium值。在本章例程中,程序注册了两种格式,一种是链接OLE对象的格式,另一种是嵌入OLE对象的格式。
BOLEFormat类型定义在BOLEDefs单元中,BOLEMediumCalc函数定义在ToCtrl单元。因此主窗中的interface部分应加入这两个单元。
interface
use…,BOLEDefs,ToCtrl,
在粘贴
OLE 对象前,应用程序必须知道在剪贴板中是否有OLE 对象。PasteSpecialEnabled 函数可判断粘贴对话框是否有效。如果剪贴板上有Fmts 定义的任何一种格式,PasteSpecialEnable 将返回真值, 粘贴对话框才能成功地调用。反之调用粘贴对话框将不发生任何事件。
以下代码实现“编辑
| 粘贴”菜单项的功能:procedure TOLEObjectForm.PasteSpecial1Click(Sender: TObject);
var
ClipFmt: Word;
DataHand: THandle;
Info: Pointer;
begin
if PasteSpecialEnabled(Self, OLEFrameForm.Fmts) then
if PasteSpecialDlg(Self, OLEFrameForm.Fmts, 0,
ClipFmt, DataHand, Info) then
InitializeOLEObject(Info)
end;
只有在粘贴对话框有效时“编辑|粘贴”菜单才有效,以下代码实现此功能:
procedure TOLEObjectForm.Edit1Click(Sender: TObject);
begin
PasteSpecial1.Enabled := PasteSpecialEnabled(Self, OLEFrameForm.Fmts)
end;
8.3.5 释放OLE对象
从OLE服务器拖动OLE对象并将其放在OLE应用程序是一种方便的对象链接与嵌入的方法。通过拖放操作,用户不需要使用插入对话框或粘贴对话框来定义OLE对象。而只需用鼠标键从OLE服务器中“抓”住OLE对象,拖至OLE应用程序,松开鼠标键,从而实现OLE对象的插入。
8.3.5.1 注册OLE释放目标窗体
为了接收一个释放的OLE对象,必须有一个窗体在Windows中注册成OLE释放目标,用RegisterFormASOLEDropTarget函数可实现此功能。
RegisterFormASOLEDropTarger(Form : TFrom;Const Fmts: array of BOlefrom).
其中
Form 是OLE 对象的释放目标窗体,在本章例程中,将子窗体传递给Form 参数。Fmts 是对象格式的数组。它是BOLEFormat 类型的数组。 所有要释放的数据必须用Fmts 数组中相应BOLEFormat 元素注册。
在本章例程中,注册的
Fmts 数组与主窗体OnCreate 事件 声明的数组相同, 即:联接对象格式和嵌入对象格式。如果想接收更多类型的释放数据,就必须在Fmts 数组中加入其它元素。例如应用程序要接收释放的文本,Fmts 需加第三个元素, 其fmtId 域为CF_TEXT,BOLEMedium 域为BOLE_MED_HGLOBL.拖放过程中不需要用BOLEFormat的fmtName,fmtResultName域,如果程序只进行拖放操作而不进行对象粘贴,可以不初始化两个域。
在主窗体的
OnCreate 事件中可调用RegisterFormAsOLEDropTorget 。procedure TOLEFrameForm,FormCreate(Sender : TObject);
begin…
Register FormASOleDropTarget(Self,Fmts)
end;
8.3.5.2 在应用程序中释放OLE对象
当一个对象释放到一个窗体,该窗体发生OnDragDrop 事件。该对象定义为TDragDropEvent方法中的Source参数,而TDragDropEvent 方法是用来处理OnDragDrop事件”。 如果Source 是一个OLE 对象, 那么它是TOLEDropNotify 对象的派生类型。 TOLEDropNotify对象有一个与OLE包容器部件PInitInfo属性相对应的PIniInfo属性。 如果一个OLE对象被释放。PInitInfo指向OLE对象的初始化信息结构。要实现释放功能。只需将TOLEDropNotify的PInitInfo属性赋给OLE包容器部件的PInitInfo属性。
以下为处理OnDragDrop事件的代码:
procedure TOLEFrameForm.FormDragDrop(Sender, Source: TObject; X,
Y: Integer);
var
NewChild: TOLEObjectForm;
begin
if Source is TOLEDropNotify then
begin
NewChild := CreateChild;
with Source as TOLEDropNotify do
NewChild.OLEContainer.PInitInfo := PInitInfo
end
end;
注意不要用ReleaseOLEInitInfo释放分配给PInitInfo属性的内存。Delphi自动释放这块内存。
8.3.6 文件中的OLE对象
在OLE应用程序中,要保存对OLE对象的修改,需将对象数据保存在文件中。 如果对象是链接的数据,Delphi将自动的保存在源文件中。当对象被修改时,文件中的数据自动修改。 如果对象是嵌入的,数据贮存在应用程序程序的窗体。要保存对嵌入对象的修改, 应用程序应把数据保存在特殊的OLE文件中。如果要对已存文件的对象进行编辑,应用程序必须从文件中装入OLE对象。
OLE包容器部件的SaveToFile方法可保存对象:
OleCntainer1.SaveToFile('C: \SALEs.OLE');
OLE包容器部件的loadFromFile方法可把文件中的对象装入OLE包容器部件。
OleContainer1.loadFromFile('C:\SALEs.OLE')
本章例程使用了保存对话框和打开对话框来实现运行状态的对象保存和对象装入。
在
OLEObjectForm 窗体加入保存对话框部件和打开对话框部件。其主要属性如表8.4 :表
8.4 保存对话框的属性及取值:━━━━━━━━━━━━━━━━━━━━━━━━
属性 值
────────────────────────
Name SaveAsDialog
DefaultExit ole
FileName .OLE
Filter OLE files (*.OLE)|*.OLE
━━━━━━━━━━━━━━━━━━━━━━━━
表8.5 打开对话框的属性及取值
━━━━━━━━━━━━━━━━━━━━━━━━━
属性 取值
────────────────────────
Name OpenDialog
DefaultExit ole
FileName .OLE
Filter OLE files (*.OLE)|*.OLE
━━━━━━━━━━━━━━━━━━━━━━━━━
用户单击“文件|保存”菜单项实现OLE对象的保存。代码如下:
procedure TOLEObjectForm.SaveAs1Click(Sender: TObject);
begin
if SaveAsDialog.Execute then
OLEContainer.SaveToFile(SaveAsDialog.Filename)
end;
用户单击“文件|打开”菜单项实现对象文件装入:
procedure TOLEFrameForm.Open1Click(Sender: TObject);
var
NewChild: TOLEObjectForm;
begin
f OpenDialog.Execute then
begin
NewChild := CreateChild;
NewChild.OLEContainer.LoadFromFile(OpenDialog.FileName)
end
end;
8.4 OLE 自动化
OLE 自动化是Windows 应用程序操纵另一个程序的一种机制。OLE 2.0 提供了一种方法来集成应用程序,这就是应用程序之间的命令操作。
利用
OLE 2.0 ,程序员可以定义一组命令,使它们进入到其它程序中。这些命令可带参数。看起来很象应用程序在调用函数或过程一样。采用上述办法, 可以在人不参与的情况下,就能使得两个应用程序的相互作用。被自动化的程序称作自动化对象或自动化服务器,
操作或自动化其他程序的应用程序称为自动化控制器或自动化客户器。Delphi2.0 完全支持OLE2.0 的应用程序自动化,可以用Delphi 2.0 编写自动化控制器和服务
器。在应用程序之间可编程的潜能是巨大的。用户可以创建宏或者其它命令,
使得某个应用程序能透过其它应用程序进行工作。已经存在的应用程序的宏语言很容易被扩展,它可以包括一组别的应用程序能够执行的命令和函数调用。现在介绍两个应用程序,其中MemoEdit.dpr 是多文档界面的文本编辑器,作为OLE 自动化服务器,AutoFrom.dpr 是自动化控制器。运行AutoForm 前,在Delphi 集成开发环境中单击菜单(run | parameters),Delphi 弹出运行参数对话框,如图8.5 ,输入参数后运行状态如图8.6 。AutoForm 窗体的多个按钮。可对MemoEdit 进行操作;如按Creat 按钮,MemoEdit 产生三个子窗体,如图8.7 ,按"AddText" ,子窗体将出现"This text was added through OLE Automation" 的字符串“
MemoEdit包括三个单元:
Mainfrom MDI主窗体
EditFrom MDE子窗体和自动化类
MemoAuto 应用程序自动化对象
下面结合例程讲述OLE自动化的基本概念及开发。
8.4.1 TAutoObject对象
TAutoObject 是Delphi自动化服务器中所有对象的基类,任何自动化对象都是从TAutoObject类派生出来的。
OLE对象的定义与其它类的定义类似。它的automated部分象普通类的public部分,OLE控制器可引用在这部分声明的属性和方法。编译器把automated部分创建成OLE自动化对象的入口。但automated部分的代码有很多限制:
● 属性方法可以定义,但不能定义域;
● 所有属性、参数、函数类型必须是以下类型之一:
SmallInt,Integer,Single,Double,Currency,TDateTime,String,WordBool, Varint
● 属性声明只能包括访问定义符(read and Write),其它定义符如index,stored,
default,odefault均不能使用;
● 访问定义符必须列出相应的方法标识符,不能使用域标识符;
● 支持数组类型;
● 不允许属性重载;
● 方法是可以是虚拟的,但不能是动态的,允许方法重载。
在EditFrom单元中定义了TMemoDoc类:
type
TMemoDoc = Class(TAutoObject)
private
FEditForm : TEditForm;
funtion CretFileName : String;
funtion CretModiFied : WordBool;
procedure SetFileName(Const Value : String);
automated
procedure Clear;
procedure Ineart(Const Text : String);
procedure Save;
procedure Close;
procedure FileName : String read GretFileName write
SetFileName;
procedure Modified : WordBool read GretModified
end;
TMemeDoc类是MemoEdit程序的内部自动化类,因此不需要注册。外部OLE自动化控制器对它不能直接引用。如果要使外部控制器对自动化对象进行操作,则要在声明自动化对象的单元中调用Automation. RegisterClass 进行注册。例程MemoAuto 单元定义了TMemoApp对象并进行注册。
unit MemoAuto
…
type
TMemoApp = Class(TAutoObject)
implementation
…
procedure RegisterMemoApp
Const
AutoClassInfo : TAutoClassInfo = (
AutoClass : TMemoApp;
ProgID : MemoEdit,Application
ClassIn : '{FIFF4880 - 200D - 11CF - BDCF - D020AFOE5B81}';
Description : 'Memo Editor Application';
Instancing : acSingle Instance );
begin
Automation,RegisterClass(AutoClassInfo)
end;
inibialization
RegisterMemoApp;
end;
自动化对象要在initialization部分中对自动化对象进行注册。 注册的信息用以唯一辨识服务器对象。把一个自动化对象加入到服务器中要用到这些信息。程序一旦注册了自动化对象,全局自动化对象将用OLE自动化API进行自动管理。
注册后的OLE自动化对象是引用记数的,因为对象可能被多个控制器控制。当使用完一个OLE对象,调用Release方法,Release可减少引用数目,当引用数目为零时,调用Free方法释放对象。
通常把OLE对象作为变体类型(variants)进行输出,任何OLE 对象的方法和属性必须返回一个包含OLE对象的变体类型,TAutoObject提供了一个变体类型的OLEObject属性。控制器不能直接得到服务器中的类或指针,而是引用OLE对象的OLEObject属性。
例程MemoAuto单元的NewMemo函数就是通过引用OLEObject 属性而提供引用TMemoDoc对象的接口。
function TMemoApp,NewMemo : Variant;
begin
Result := MainForm,CreateMemo(' '),OleObject;
end;
8.4.2 创建OLE自动化服务器
OLE自动化服务器是应用程序或动态链接库(DLL),它可向OLE 自动化控制器输出OLE对象。 MemoEditdpr 就是OLE 自动化服务器, 在MemoAuto 单元中注册了MemoEdit.Appdication自动化类,所有OLE控制器均可对MemoEdit.Application进行引用。
在
Windows 环境下有两种OLE 自动化服务器,进程内服务器和进程外服务器, Delphi 可创建这两种服务器。进程内服务器是输出
OLE 自动化对象的动态链接库。因为OLE 自动化对象来自于DLL ,对象是控制器程序的同一窗体进程,进程内服务器适合于创建共享的程序模块,
而这个模块可以被用不同语言编写的多个程序所共享。 进程内服务器被调用时在同一地址中运行,这样就不需要控制器进行调度,以避免处理大量的消息句柄。 进程外服务器是能输出OLE 自动化对象的应用程序。有些
OLE 自动化服务器只能创建和输出一个OLE 对象,有些服务器则可以处理多个OLE 对象,另外一些服务器不能输出OLE 对象,只能在程序内部使用OLE 对象。 服务器与其能输出的对象数目的关系称为实例(instancing) 。在创建
OLE 自动化对象时必须定义实例, 这样, 在创建一个OLE 自动化对象时,Windows 就能决定是否创建一个新的服务器实例。表8.5 列出三种实例类型。表8.6 实例的取值及含义
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
instancing类型 含义
─────────────────────────────── ───────
internal OLE对象是应用程序的内部对象,对象不需要注册,外部进程不能创
建此对象
Single 每个服务器实例只能输出一个OLE对象实例, 若控制器需要多个OLE
对象实例,WIndows 为第一个OLE 对象创建一个服务器实例
Multiple 一个服务器能创建和输出多个OLE 对象实例, 进程内服务器大多是
Multiple类型
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
每个使用OLEAuto单元的工程文件自动地拥有一个叫Automation的对象,它是非可视对象。就象Application部件拥有Delphi应用程序的一些信息一样,Automation对象也拥有服务器的一些信息,其中最重要的是StartMode属性和OnLastRelease事件。
StartMode指示OLE自动化服务器打开方式打开的目的。表8.7列出StartMode四种取值。
表8.7 StartMode 的取值及含义
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
取值 含义
───────────────────────────────
SmStandAlone 用户启动应用程序
SmAutomation Windows为创建OLE对象而启动程序
SmRegSever 应用程序仅为注册一个或多个OLE对象而启动
SmUnregSever 应用程序仅为注销一个或多个OLE对象而启动
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
当StartMode模式是SmAutomation,而用户不再需要服务器时发生OnLastRelease 事件。此时所有OLE控制器释放了由服务器创建的对象。缺省情况下,服务器关闭实例,但OnLastRelease 事件可根据实际情况是否关闭。OnLastRelease 事件可得到一个叫ShutDown的布尔型变量。把ShutDown设置成True,则在最后一个OLE对象释放时服务器不关闭。
无论创建何种自动化服务器,必须定义对控制器的界面,包括定义和注册OLE对象,OLE自动化对象的属性和方法。定义界面主要是为了控制器能够引用它们。
对已存在的自动化服务器界进行修改时,要确保向上兼容 ,不要删去已有的属性、方法,这样会导致已存在的自动化控制器发生错误,修改服务器只能增加属性和方法。
创建OLE自动化服务器第一步是创建服务器自身。即创建能输出OLE 对象的应用程序或动态链接库。这主要取决于是创建进程内服务器还是进程外服务器。
创建进程内服务器,即动态链接库:
1.创建动态链接库;
2.在工程文件的uses条款中加入OLEAuto单元;
3.在DLL中输出四个标准入口,即加入以下代码。
exports
DLLGetClassObject,DLLCanUnloadNow;
DLLRegisterServer,DLLUnregisterServer;
以上代码必须准确拼写,包括大小写。与Object Pascal的其它项目不同,这些代码
对大小写敏感。
创建进程外服务器:
1.创建一个Delphi应用程序;
2.在工程文件的begin之后加入以下代码;
if Automation,Server Registration then Exit;
创建服务器之后,应该向服务器加入OLE自动化对象,这个过程大部分是自动完成的,但必须向Delphi的自动化对象专家提供必要的信息。
把OLE自动化对象加入服务器:
1.在Delphi集成开发环境中选择File| New 菜单项, 并在对象集中选择Automation
Object,Delphi打开自动化对象专家。
2.给自动化对象命名
这是服务器内部标识OLE对象的名字,必须是个有效的面象对象Pascal标识符,习惯上以T字母开头;
3.给OLE类命名
该名用以外部控制器创建对象。当服务器在Windows中注册OLE对象, 就以这个名字在系统注册。控制器使用这个名字调用CreateOLEObject来创建对象。
4.描述要输出的对象。
5.定义对象的实例(instancing),进程内服务器常定义为Multiple,进程外服务器常定义为Single;
6.选择OK键完成该过程
自动化对象专家将产生以下代码:
● 从TAutoObject派生下来的自动化对象定义,但没有定义任何属性方法;
● 调用DelphiOLE自动化管理器的注册代码,管理器负责Windows中注册服务器和对象。
在注册代码中包括一个自动产生的ID号,这个ID号是全局唯一的,通常不要修改。每个ID号与一个OLE类名相对应,如果其中之一被改变,应用程序在使用时会发生错误。
在创建了服务器并把OLE自动化对象加入服务器之后,控制器程序就可以对服务器进行操纵。
8.4.3 自动化另一程序
每个服务器在系统注册中有一个叫ProgID的关键定,主要用以控制器辨识服务器。任何控制器可以用ProgID号来创建OLE对象实例。例程AutoForm是控制器程序,它在其主窗体创建了OLE对象实例。
procedure TMainForm.FormCreate(Sender : TObject);
begin
try
MemoEdit := CreateOleObject('MemoEdit.Application');
except
MessageDlg(
'An instance of the "MemoEdit Application"OLE Automation Class could
not be created,Make sure that the MemoEdit application has been registered
using a "MemoEdit|regserver"command line',
mtError,[mbok],0)
Halt;
end;
end;
控制器创建了OLE自动化对象实例后,可对其进行操纵。OLE自动对象包括属性和方法,虽然OLE自动化对象与面向对象Pascal中的对象不是同一概念,但Delphi允许使用与类似的语法对OLE对象的方法进行调用。
AutoForm的很多过程引用了OLE自动化对象的方法:
procedure TMainForm,TileButtonClick(Sender : Tobject);
begin
MemoEdit,TileWindow;
end;
其中TileWindows是OLE对象TMemoApp中定义的方法。
AutoForm还通过TMemoApp的NewMemo方法获得了对服务器内部OLE对象TMemoDoc 的引用。
procedure TMainForm,CreateButtonClick(Sender : TObject);
var
I : Integer;
begin
CloseMemo
for I := 1 to 3 do Memos[2] := MemoEdit.NewMemo;
end;
其中NewMemo在MemoAuto单元中定义如下:
function IMemoApp.NewMemo : Variant;
begin
Result := MainForm,CreateMemo(' '),OleObject;
end;
控制器在获得服务器的内部OLE对象后,可以引用其方法:
procedure TMainForm.AddTextButtonClick(Sender,TObject);
var
I : Integer;
begin
for I := 1 to 3 do
if not var IsEmpty(Memo[I]) then
Memo[I],Insert{'This text was added through OLE Automation'#13#10);
end;
Insert是TMemoDoc中定义的方法,用以在子窗体中插入字符串。