7.2.1 Windows的DDE原理
Windows的DDE机制基于Windows的消息机制。两个Windows应用程序通过相互之间传递DDE消息进行DDE会话(Conversation),从而完成数据的请求、应答、传输。这两个应用程序分别称为服务器(Server)和客户(Client)。服务器是数据的提供者,客户是数据的请求和接受者。
DDE会话由客户程序启动。客户程序把一条消息(WM_DDE_INITIATE)传播给当前运行的所有Windows程序。这条消息指明了客户程序所需要的一般数据(应用程序、主题)。拥有这些数据的DDE服务器可以响应这条被传播的消息。此时,DDE会话就开始了。
由于在每个主题中,DDE服务器可以支持一个或多个数据项,所以在客户请求数据时应同时指明应用程序名、主题名和项目名。应用程序、主题、项目是DDE中三个最基本的概念。
利用Windows本身提供的DDE消息和API进行DDE编程是一件相当棘手的问题。 虽然使用DDE管理库(ddeml.dll)可以一定程度上减轻开发者的工作负担,但开发DDE程序仍不是一件轻松的事情。
此时Delphi出现了!Delphi通过其自身巧妙的设计使开发一个DDE应用程序同开发一个普通程序一样地快捷、方便。
7.2.2 Delphi的DDE实现机制简介
Delphi把所有的DDE功能做到四个部件中,它们是:
● TDDEClientConv : 用于客户程序建立和维护一个DDE会话
● TDDEClientItem : 用于客户程序建立和维护数据交换通道
● TDDEServerConv : 用于服务器程序响应DDE会话
● TDDEServerItem : 用于服务器程序维护数据交换通道
前两个部件用于生成一个DDE客户程序,后两个部件用于生成一个DDE服务器程序。如果一个应用程序同时拥有这些部件,则这一程序既可以充当DDE客户,也可以充当DDE服务器。
会话部件TDDEClientConv、TDDEServerConv用于建立和维护一个DDE会话。DDE会话包括DDE服务和DDE主题两部分。
DDE服务是DDE服务器的名称,即在一般的Windows DDE机制中所讲的应用程序名。一般说来这一名称是DDE服务器应用程序执行文件名去掉 .EXE后缀。比如你的应用程序要和Word 6.0建立会话,则DDE服务为WINWORD。但也不尽然。比如你的应用程序要和Borland ReportSmith ( RPTSMITH.EXE ) 建立会话,则DDE 服务为 Report Smith。DDE服务到底如何,读者可参看相关的DDE服务器应用程序文档。
DDE主题是一个包含了联接信息的数据单元。一般说来DDE 主题是一个包括扩展名的完整文件名。例如和Excel中的一个文件建立DDE会话,则主题可能是
Topic = 'c:\excel\Example\sale.xls'
如果服务器是一个Delphi应用程序,缺省情况下主题是包含欲联接数据窗体的标题。如果服务器使用了DDEServerConv部件,则要求使用部件DDEServerConv的名称作为DDE主题。
项目部件TDDEclientItem、TDDEServerItem用于建立和维护DDE数据的传输通道。 DDE项目中包含着实际欲传输的数据。DDE项目的格式取决于DDE服务器应用程序。一个可能的DDE项目例子是电子表格中的单元和数据库表中的域。如果服务器是Delphi应用程序,则项目是连接的 DDEServerItem部件的名称。
Delphi的DDE实现机制方便、实用,但也有一个令人遗憾的缺陷:只能传输文本数据以及命令、宏,而不能传输图像数据。在这一点上微软公司推出的Visual Basic 要略胜一筹。不过在目前文本数据的使用仍是最广泛的,而且图像传输可以利用剪贴板和OLE来实现,则这一缺陷也并无很大的影响。
7.3 DDE客户程序的实现
DDE客户程序启动DDE会话,向服务器请求并从服务器接收数据。同时还可以向服务器发送数据、命令、宏,改变服务器的状态并控制服务器的运行。
7.3.1 联接模式(ConnectMode)
Delphi的DDE提供了两种联接模式:自动和人工。这可以通过DDEClinetConv 部件的ConnectMode属性进行设置。如下表所示。
表 7.5 DDE的联接模式
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
值 意 义
───────────────────────────────
ddeAutomatic 在运行中当包含TDDEClientConv部件的窗口创建时联接自动建立
ddeManual 只有当调用OpenLink方法时联接才建立
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
不同联接模式,DDE客户程序的实现方式不同。
对于自动模式:
1. 向窗体中加入DDEClientConv和DDEClientItem部件并命名;
2. 把DDEClientItem部件的DdeConv属性设置为DDEClientConv部件的名称;
如果在设计时建立,则通过对象观察器进行选择;如果在运行时建立联系, 则通过如下的一条语句设置属性的值:
DDEClientItem1.DdeConv := 'DDEClientConv1' ;
3. 和服务器建立联系,实现数据共享。
对于人工模式:
1.向窗体中加入DDEClientConv部件;
2.和服务器建立联系;
3. 数据更新时调用RequestData方法申请并获得数据。
7.3.2 和DDE服务器建立联系
和DDE服务器建立联系,既可以在设计时进行,也可以在运行时进行。
在设计时,DDE联接可以通过剪贴板进行粘贴。具体步骤如下:
1. 激活服务器程序,并选中你的客户程序欲联接的数据;
2. 把数据和DDE联接信息拷贝到剪贴板上。一般说来这只需要选择服务器应用程序的 Edit|Copy 菜单;
3. 在Delphi IDE的设计窗体中选中DDEClientConv部件;
4. 在Object Inspector(对象观察器)中单击DDEService属性或DDETopic属性,然后再单击Ellipsis按钮,打开DDE Info对话框;
5.选择Paste Link按钮。此时App编辑框和Topic编辑框被自动填充。如果Paste Link按钮变灰,说明你准备用作服务器的应用程序不支持DDE或者DDE信息没有被成功地拷贝到剪贴板上;
6.选择OK 按钮。此时Object Inspector中的DDEService、DDETopic 属性包含了建立一个DDE联接的正确值。
对于人工模式以下步骤是不需要的。
7.选中DDEClientItem部件,并在Object Inspector中设置DdeConv属性为已完成联接的DDEClientConv部件名称;
8.假如剪贴板上的DDE 联接信息仍保留的话,从Object Inspector的下拉列表框中选择 DDEItem 属性的值。否则输入正确的值。
在运行时,调用 SetLink 方法来建立DDE联接。
SetLink有两个String类型的参数,分别用来接受DDEService和DDETopic的值。过程执行后DDEClientConv部件的DDEService 和DDeTopic属性被设置。要注意的是:在运行时直接设置DDEService和DDETopic的值并不能建立一个DDE联接,而必须调用SetLink 方法进行初始化。
比如,下面的语句和Excel的System主题建立联接:
DDEClietnConv. SetLink('Excel','System');
调用SetLink方法后,还需要设置DDEClientItem部件的DDEItem属性。
比如,下面的语句联接Excel的Topics项目,用以获取当前活跃文件的文件名:
DDEClietnItem.DDEItem := 'Topics';
当DDE联接建立后,联接的数据保存在DDEClientItem部件的Text和Lines 属性中,Text用于保存一个字符串(String),Lines用于保存一个字符串链表(TStrings)对象。
为了显示联接数据,可以在DDEClientItem的OnChange事件中把数值赋给一个可视部件。
下面的事件过程把联接数据实时地显示在一个编辑框中。
procedure Form1.DDEClientItemChange(Sender: Tobject);
begin
Edit1.Text := DDEClientItem1.Text;
end;
运行状态下也可以从剪贴板上粘贴DDE联接信息,并调用SetLink建立DDE会话。下面的例子显示了当用户按下应用程序中的Paste Link按钮时,动态建立DDE会话的过程。
procedure Form1.OnPasteLink(Sender: Tobject);
var
Service, Topic, Item: String;
begin
if GetPasteLinkInfo (Service, Topic, Item) then
begin
AppName.Text := Service;
TopicName.Text := Topic;
ItemName.Text := Item;
DDEClient.SetLink (Service, Topic);
DDEClientItem.DdeConv := DDEClient;
DDEClientItem.DDEItem := ItemName.Text;
end;
end;
GetPasteLinkInfo是DDEMan 库单元中定义的一个过程。如果返回True,则DDE联接信息保存在三个参数中;如果返回False,说明剪贴板上没有正确格式的DDE联接信息。
7.3.3 数据申请
虽然自动模式快捷、方便,但仍有一些理由使用DDE的人工模式:
1.服务器程序可能不支持自动数据传输,客户必须显式申请服务器更新一个特定的项目;
2.节省通信费用。假如没有实时传输的要求,则人工模式可以大幅度降低通信的开销;
3.若客户程序只用于控制服务器的运行,则往往没有必要使用自动模式。
人工模式下客户程序的数据更新需要采用数据申请的方式。数据申请需要调用DDEClientConv部件的RequestData方法。RequestData有一个参数,指向要申请的DDE项目。RequestData返回一个Pchar类型的无结束符字符串,包含了申请到的文本。返回字符串占用的内存必须在程序终止前显式释放。
在人工模式下,即使存在一个DDEClientItem部件且与DDEClientConv相联接,数据更新后DDEClientItem部件的Text、Lines属性的值也不会改变。
7.3.4 数据发送
数据发送与一般的DDE数据流向正好相反,是把数据从DDE客户应用程序发送到DDE服务器应用程序。
数据发送使用DDEClientConv部件的两个方法PokeData 和 PokeDataLines, 它们的语法是:
function PokeData (Item: String ; Data: PChar): Boolean;
function PokeDataLines (Item: String ; Data: TStrings): Boolean;
参数Item是DDE服务器中被联接的项目,Data是要发送的数据。如果数据是一个字符串,则把它转化为PChar类型并调用PokeData方法;如果数据是一个字符串链表对象,可调用PokeDataLines方法。
方法的返回值标志数据传送是否成功。因为有一些DDE服务器应用程序并不接收发送的数据。
下面的语句把编辑框中的内容发送给服务器:
StrPCopy(TheText , Edit1.text);
DDEClientConv1.PokeData(DDEClientItem1.DDEItem , TheText);
过程StrPCopy把一个Pascal类型的字符串拷贝到一个无结束符的PChar类型字符串中。
7.3.5 控制服务器应用程序的执行
客户程序控制服务器应用程序的一个方面是:必要的时候客户程序可以启动服务器程序,并装载会话主题。
而客户程序控制服务器应用程序更重要的一点是向服务器发送服务器承认的宏命令,来完成对服务器应用程序的各种操作。服务器到底支持哪些宏命令,可参阅服务器应用程序文档。
发送宏命令要使用DDEClientConv的两个方法 ExecuteMacro和ExecuteMacroLines ,它们的语法如下:
function ExecuteMacro(Cmd: PChar; WaitFlag: Boolean): Boolean;
function ExecuteMacroLines(Cmd: TStrings;WaitFlag: Boolean): Boolean;
Cmd是欲发送的宏命令字符串或宏命令字符串链表。WaitFlag决定了在DDE 服务器程序执行宏命令时客户程序的行为。如果WaitFlag设置为True,则在服务器宏命令执行完毕前,不允许对ExecuteMacro、ExecuteMacroLines、PokeData、PokeDataLines这些方法的成功调用,它们都不向服务器发送数据并返回False。如果WaitFlag设置为False,则调用的方法在第一个宏执行完毕前即试图向服务器发送数据。
WaitFalg的设置也取决于服务器应用程序。一些应用程序当在第一个宏执行完之前就试图向它发送数据或命令时,可能导致第一个宏执行失败或导致不可预料的后果。具体情况可查阅服务器应用程序文档。
函数返回值表示命令串是否被成功传输。而宏命令执行是否成功客户是无法检测到的。
7.3.6 格式化文本
DDEClientConv有一个布尔属性FormartChars,用于决定是否格式化文本。所谓格式化文本是指从传输来的文本数据中过滤掉BackSpace(8)、 Tab(7) 、Linefeed(10) 、Return(13)等字符。括号内是字符的ASCII码。许多时候这些字符将导致DDE客户数据显示的混乱。
FormatChars的缺省值是False。
7.3.7 响应DDE事件
部件DDEClientConv有两个事件OnOpen和OnClose,分别在DDE 会话建立和中止时触发。部件DDEClientItem有一个OnChange事件。这一事件常用于DDE项目数据的转储和显示,如(7.3.1)节所示。
在自动模式下,OnOpen事件在包含DDEClientConv部件的窗口创建时触发,或在调用SetLink方法时触发,OnClose事件在客户程序或服务器程序关闭时触发。
在人工模式下,OnOpen事件在调用OpenLink 方法时触发,OnClose事件在调用ColseLink方法时触发。
7.3.8 利用客户程序和Excel交换数据
下面我们建立一个DDE客户程序,并利用这一程序与Excel中的一个工作表交换数据。程序设计界面如图。
图7.2 客户程序示例设计界面
界面中包含一个DDE会话部件DDEClientConv1和DDE项目部件DDEClientItem1,用于建立和维护DDE联接;一个RadioGroup控件和其中的两个无线电按钮AutoRadio、ManualRadio,用于设置联接模式;一个GroupBox控件和其中的两个按钮RequestBtn和PokeBtn,用于控制数据的申请和发送,其中RequestBtn在自动模式下变灰;一个文本框Memo1用于保存DDE数据;一个按钮PasteBtn用于粘贴联接信息并建立DDE联接;另外一个按钮CloseBtn用于关闭系统。
设计时把DDEClientConv1的FormatChars属性置为True,这样可以保留服务器传来数据的显示格式;ConnectMode保留ddeAutomatic的缺省设置。
程序在类TForm1中定义了一个私有数据成员Automatic,用于标志联接模式;三个字符串数据成员DDEService、DDETopic、DDEItem用于记录联接信息。
窗口生成时进行变量和部件状态的初始化。
procedure TForm1.FormCreate(Sender: TObject);
begin
RequestBtn.Enabled := False;
AutoRadio.Checked := True;
Automatic := True;
end;
当联接模式改变时,程序进行相应的处理。
自动模式转换为人工模式:
procedure TForm1.ManualRadioClick(Sender: TObject);
begin
if Automatic then
begin
RequestBtn.Enabled := ManualRadio.Checked;
DDEClientConv1.ConnectMode := ddeManual;
Automatic := False;
end;
end;
人工模式转换为自动模式:
procedure TForm1.AutoRadioClick(Sender: TObject);
begin
if not Automatic then
begin
RequestBtn.Enabled := ManualRadio.Checked;
If (DDEService = '') or (DDETopic = '') then
begin
MessageDlg(' Can not Set Link.',mtWarning,[mbOK],0);
Exit;
end;
DDEClientConv1.SetLink (DDEService, DDETopic);
DDEClientItem1.DdeConv := DDEClientConv1;
DDEClientItem1.DDEItem := DDEItem;
DDEClientConv1.ConnectMode := ddeAutomatic;
Automatic := True;
end;
end;
当从自动模式转换到人工模式,只需要简单修改相应属性即可;而从人工模式转换到自动模式,则需要调用SetLink重新建立联接,否则往往会引发一个DDE异常。
联接的建立采用从剪贴板粘贴联接信息的方式,这是最具有灵活性的一种方法。
procedure TForm1.PasteBtnClick(Sender: TObject);
begin
if GetPasteLinkInfo (DDEService, DDETopic, DDEItem) then
begin
DDEClientConv1.SetLink (DDEService, DDETopic);
if Automatic then
begin
DDEClientItem1.DdeConv := DDEClientConv1;
DDEClientItem1.DDEItem := DDEItem;
end;
end;
end;
GetPasteInfo是 DDEMan库单元中定义的一个函数,用于检测剪贴板上是否有联接信息并返回相应的DDE服务、主题和项目。
对于人工模式,必须由客户显式向服务器申请数据。在这种模式下DDE项目部件是多余的,接收到的DDE联接信息用一个字符串来记录。下面是实现代码。
procedure TForm1.RequestBtnClick(Sender: TObject);
var
TheData: PChar;
begin
If DDEItem = '' then
begin
MessageDlg('Can not Request Data',mtWarning,[mbOK],0);
Exit;
end;
TheData := StrAlloc(79);
DDEClientConv1.OpenLink;
TheData := DDEClientConv1.RequestData(DDEItem);
DDEClientConv1.CloseLink;
if TheData <> nil then
Memo1.Text := StrPas(TheData);
StrDisPose(TheData);
end;
OpenLink、CloseLink方法用于打开和关闭联接。RequestData方法向服务器申请数据并返回到一个PChar字符串中。字符串必须显式分配内存并在退出时释放。
数据发送在不同联接模式下是不同的。对于人工模式,增加了联接的打开和关闭操作。程序清单如下。
procedure TForm1.PokeBtnClick(Sender: TObject);
begin
If DDEItem = '' then
begin
MessageDlg('Can not Poke Data.',mtWarning,[mbOK],0);
Exit;
end;
if Automatic then
DDEClientConv1.PokeDataLines(DDEItem,Memo1.Lines)
else
begin
DDEClientConv1.OpenLink;
DDEClientConv1.PokeDataLines(DDEItem,Memo1.Lines);
DDEClientConv1.CloseLink;
end;
end;
打开Microsoft Office中的Excel,装入一个文件,把相关的单元选中,拷贝到剪贴板上。而后运行程序,按下Paste Link按钮,DDE联接就建立起来,相关单元中的数据显示在Memo1中。之后可以进行模式转换、数据申请、申请发送等一系列工作。运行后的屏幕显示如下图所示。
图7.3 DDE客户和Excel交换数据
7.3.9 用客户程序控制程序管理器
下面的例子用客户程序向程序管理器发送命令,用于创建程序组、程序项以及删除程序组。
程序管理器提供了应用程序的DDE接口命令字符串,应用程序利用这些命令字符串可以实现以下的功能:
1.创建程序组
命令格式为:
CreateGroup(程序组名[,程序组所在的路径])
程序组不存在时进行创建;如程序组存在则按照指定的路径激活。
2.删除程序组
命令格式为:
DeleteGroup(程序组名)
3.显示程序组
命令格式为;
ShowGroup(程序组名,显示标志)
显示标志用于控制程序组在程序管理器中以极大、极小或正常方式显示。
4.重新装入程序组
命令格式为:
ReLoadGroup(程序组名)
该命令使程序管理器先删除而后再重新装入一个已有的程序组。
5.向程序组中添加程序项
命令格式为:
AddItem(命令行[,描述[,图标路径[,图标序号[,图标横坐标,图标纵坐标[,工作区目录[,热键[,是否最小化显示标志]]]]]]])
命令行控制程序项的执行,可包括路径、参数等。其它参数分别对应在程序管理器中添加一个程序项时需要设置的参数和选项。它们都有缺省设置,因而是可选的。
6.替换程序组中的程序项
命令格式为:
ReplaceItem(程序项名)
该命令删除一个程序项,并将所删除程序项的位置记录下来,以后通过AddItem在这个所记录的位置增加新项目。
7.从程序组中删除程序项
命令格式为:
DeleteItem(程序项名)
从当前活动程序组中删除一个程序项。
8.关闭程序管理器
命令格式为:
ExitProgram(是否保存程序组信息标志)
从应用程序向程序管理器发送命令字符串的方法是基本一致的。为简便起见,在例程中只实现了其中仅包含一个字符串参数的情形,读者可以很容易作进一步的扩展。
程序设计界面如图所示,包含一个DDE客户会话(DDEClientConv)部件和四个完成不同功能的按钮。
图7.4 控制程序管理器应用程序的设计界面
DDEClientConv在设计时和程序管理器建立一个DDE会话,其中DDE服务器和DDE主题 都为PROGMAN。联接模式ConnectMode设置为ddeManual。
我们把只有一个字符串参数的命令发送情况抽象出来,形成下面的SendMacro函数。
function TForm1.SendMacro(Name: String;Command: String): Boolean;
var
Macro: String;
Cmd: array[0..255] of Char;
begin
Result := True;
if Name <> '' then
begin
Macro := Format('['+Command+'(%s)]', [Name]) + #13#10;
StrPCopy (Cmd, Macro);
DDEClient.OpenLink;
if not DDEClient.ExecuteMacro(Cmd, False) then
Result := False;
DDEClient.CloseLink;
end;
end;
过程首先利用Format函数形成宏字符串:
Macro := Format('['+Command+'(%s)]', [Name]) + #13#10;
而后把Pascal类型的字符串拷贝到一个程序管理器可接受的PChar类型字符串中。
DDE联接采用人工模式。首先调用OpenLink方法。而后调用ExecuteMacro方法发送命令,如失败则返回False。最后用CloseLink关闭联接。
三个按钮CreateButton、AddButton、DeleteButton分别用于创建程序组、添加程序项、删除程序组。它们的程序实现大同小异,如下所示。
创建程序组:
procedure TForm1.CreateButtonClick(Sender: TObject);
var
Name: String;
begin
Name := InputBox('Input Box','Input Group Name','');
if Name = '' then
MessageDlg('Group name can not be blank.', mtError, [mbOK], 0)
else
if SendMacro(Name,'CreateGroup') = False then
MessageDlg('Unable to create group.', mtInformation, [mbOK], 0);
end;
添加程序项:
procedure TForm1.AddButtonClick(Sender: TObject);
var
Name: String;
begin
Name := InputBox('Input Box','Input Application full_Path name','');
if Name = '' then
MessageDlg('Application name can not be blank.', mtError, [mbOK], 0)
else
if SendMacro(Name,'AddItem') = False then
MessageDlg('Unable to Add Item.', mtInformation, [mbOK], 0);
end;
删除程序组:
procedure TForm1.DeleteButtonClick(Sender: TObject);
var
Name: String;
begin
Name := InputBox('Input Box','Input Group Name to be Deleted','');
if Name = '' then
MessageDlg('Group name can not be blank.', mtError, [mbOK], 0)
else
if SendMacro(Name,'DeleteGroup') = False then
MessageDlg('Unable to create group.', mtInformation, [mbOK], 0);
end;
7.4 DDE服务器程序的实现
DDE服务器程序响应DDE客户的请求,一般地它包含了客户程序希望获取的数据。
创建一个DDE服务器程序,必须要把一个DDEServerItem部件添加到窗体中。DDEServerItem的text或Lines属性包含了要联接的数据。一般地 DDEServerItem部件又和另一个文本控件相联系。当文本控件中的内容变化时则更新DDEServerItem 的text或Lines属性的值。下面的一段程序把DDEServerItem和一个列表框相联系。这一联系是在列表框的OnChange事件中实现。
procedure Form1.OnListBoxChange(Sender: TObject);
begin
DDEServerItem1.Lines := ListBox1.Items;
end;
创建DDE服务器程序时也可以再加入一个DDEServerConv部件,并把两个部件利用DDEServerItem的ServerConv属性联系起来。此时DDE主题成为部件DDEServerConv的名称,而不是拥有DDEServerItem部件窗体的标题。
在下列情况下使用DDEServerConv部件成为必要:
1.拥有DDEServerItem 部件窗体的标题可能在运行时改变或可能有其它窗体拥有同样的标题。在这种情况下DDE联接可能无法建立;
2.DDE客户程序可能会向你的服务器程序发送一条宏命令。在这种情况下只有拥有一个DDEServerConv部件才能响应OnMacroExecute事件并执行相应的动作。
7.4.1 和DDE客户程序建立联接
一般说来,建立DDE联接是客户程序的任务。但服务器程序可以把一个联接拷贝到剪贴板上供客户程序粘贴并建立DDE会话。步骤如下:
1.调用DDEServerItem部件的CopyToClipboard方法, 把Text(或Lines)属性的值和DDE联接信息拷贝到剪贴板上;
2.DDE客户程序插入联接的数据。一般地这是通过选择适当的命令(如Edit|Paste Special或Edit|Paste Link)来实现的。
7.4.2 响应DDE事件
部件DDEServerConv有三个事件:OnOpen、OnClose、OnExecuteMacro。前两个事件在DDE会话建立和终止时触发。同(7.3.7)中的介绍。
OnExecuteMacro事件用于响应客户程序发送过来的宏指令。OnExecuteMacro事件处理过程有一个Msg参数,保存发送过来的指令串。用户可以在该过程中决定如何响应这些宏指令。
DDEServerItem部件只有一个事件OnPokeData。这一事件用于响应客户程序发送来的数据。如果客户程序是Delphi程序,则客户程序调用了PokeData或PokeDataLines方法。在这一事件的处理过程中用户可以把发送来的数据保存到一个合适的地方。一般说来这应该就是DDEServerItem所联系的文本控件。
下面的程序把发送来的数据保存到ListBox中。
procedure Form1.OnDDEServerItemPokeData(Serder: TObject);
begin
ListBox1.Items := DDEServerItem1.Lines;
end;
7.4.3 DDE服务器应用例程
下面我们创建一个DDE服务器例程和一个相应的DDE客户例程。
DDE服务器例程可以完成的工作有:
1.把DDE联接信息拷贝到剪贴板上供其它程序使用;
2.利用一个TMemo部件为其它程序提供数据源;
3.接收客户程序发送来的数据;
4.根据客户程序发送来的宏指令改变自身的运行状态。
服务器例程的设计界面如图。
图7.5 服务器例程设计界面
其中各部件的关键属性如下:
DDESrvrForm.ActiveControl = Memo1
DDESrvrForm.Menu = MainMenu1
Bevel1.Align = alTop
Memo1.Align = alClient
DDETestItem.ServerConv = DDETestTopic
通过设置Bevel1、Memo1的Align属性,可以保证窗口大小变化时仍能有较为美观的屏幕显示。
Memo1是服务器的数据源,DDE项目部件DDETestItem通过Memo1的OnChange事件与Memo1 建立联系。
procedure TDdeSrvrForm.doOnChange(Sender: TObject);
begin
if not FInPoke then
DDETestItem.Lines := Memo1.Lines;
end;
其中FInPoke是一个布尔类型的私有数据成员,用于标志程序是否在处理客户程序的数据发送。当数据是由客户发送过来转存到数据源时,则没有必要再把数据传给DDE项目部件。
把联接信息拷贝到剪贴板,只需简单调用DDETestItem的CopyToClipboard方法。
procedure TDDESrvrForm.CopyClick(Sender: TObject);
begin
DDETestItem.CopyToClipboard;
end;
这是通过菜单项Edit|Copy来调用的。
接收客户程序发送来的数据,是在DDETestItem的OnPokeData事件处理过程中。在接收过程中改变FInPoke的值,以阻止数据的无效反送。
procedure TDDESrvrForm.doOnPoke(Sender: TObject);
begin
FInPoke := True;
Memo1.Lines := DDETestItem.Lines;
FInPoke := False;
end;
在DDE会话部件DDETestTopic的OnExecuteMacro事件处理过程中处理客户发送来的宏指令。我们共定义了五种可以响应的宏指令:CopyDDE、Clear、WS_Normal、WS_MINIMIZED、WS_MAXIMIZED,分别用于拷贝联接信息、清除Memo1中的内容以及改变窗口显示状态。
procedure TDdeSrvrForm.doMacro(Sender: TObject;Msg: TStrings);
var
Cmd: String;
i: Integer;
begin
Cmd := '';
if Msg.Count = 0 then Exit;
for I := 0 to Msg.Count-1 do
begin
Cmd := Msg.Strings[i];
if UpperCase(Cmd) = 'COPYDDE' then
DDETestItem.CopyToClipboard
else if UpperCase(Cmd) = 'CLEAR' then
Memo1.text: = ''
else if UpperCase(Cmd) = 'WS_NORMAL' then
WindowState := wsNormal
else if UpperCase(Cmd) = 'WS_MINIMIZED' then
WindowState := wsMinimized
else if UpperCase(Cmd) = 'WS_MAXIMIZED' then
WindowState := wsMaximized
else
MessageDlg('Invalid Command',mtWarning,[mbOK],0);
end;
end;
下面的DDE客户程序,主要是为了验证上面的DDE服务器程序而设计的,但同时也提供了一个DDE客户程序设计的完整实例。程序设计界面如图。
图7.6 DDE客户程序的设计界面
程序把接收到的DDE数据保存在一个TMemo类部件DDEDat中,而欲发送给服务器的数据和宏指令在另一个TMemo类部件PokeDat中输入。两个按钮PokeBtn、ExecuteBtn用于发送数据和宏指令。两个菜单项File|New Link和Edit|Paste Link用于根据用户的输入建立新联接和从剪贴板上粘贴DDE联接。
DDE联接的建立通过调用SetLink方法实现。
建立新联接的实现代码如下。
procedure TFormD.doNewLink(Sender: TObject);
begin
DDEClient.SetLink (AppName.Text, TopicName.Text);
DDEClientItem.DdeConv := DDEClient;
DDEClientItem.DDEItem := ItemName.Text;
end;
通过从剪贴板粘贴联接信息来建立联接的实现代码如下。
procedure TFormD.Edit1Click(Sender: TObject);
var
Service, Topic, Item : String;
begin
PasteLink1.Enabled := GetPasteLinkInfo (Service, Topic, Item);
end;
procedure TFormD.doPasteLink(Sender: TObject);
var
Service, Topic, Item : String;
begin
if GetPasteLinkInfo (Service, Topic, Item) then
begin
AppName.Text := Service;
TopicName.Text := Topic;
ItemName.Text := Item;
DDEClient.SetLink (Service, Topic);
DDEClientItem.DdeConv := DDEClient;
DDEClientItem.DdeItem := ItemName.Text;
end;
end;
在DDEClientItem的OnChange事件处理过程中把接收到的事件保存在DDEDat中。
procedure TFormD.DDEClientItemChange(Sender: TObject);
begin
DDEDat.Lines := DDEClientItem.Lines;
end;
数据发送的实现代码如下。
procedure TFormD.doPoke (Sender: TObject);
var
DDECli : TDDEClientConv;
begin
DDECli := DDEClientItem.DdeConv;
if DdeCli <> nil then
DDECli.PokeDataLines (DDEClientItem.DDEItem, PokeDat.Lines);
end;
宏指令发送的实现代码如下。
procedure TFormD.doMacro (Sender: TObject);
var
DDECli: TDDEClientConv;
Cmd: String;
begin
DDECli := DDEClientItem.DdeConv;
if DDECli <> nil then
begin
Cmd := PokeDat.Text + #13#10;
DDECli.ExecuteMacroLines (PokeDat.Lines, True);
end;
end;
运行以上两个程序,建立DDE联接。经测试,数据传输、数据发送和宏指令的发送与执行都达到预期要求。下面是两个程序同时运行后的屏幕显示。
图7.7 服务器和客户间的动态数据交换示例
7.4.4 小结
剪贴板和DDE是Windows下数据通信的两种方法。Delphi以简便、友好的方式实现了相应的功能,为用户编程提供了方便。一般说来,剪贴板多用于静态数据传输,而DDE用于动态数据交换、控制其它程序运行等场合。这些内容对于多用户环境下的程序开发是很重要的。
=====================
备注:
(1)DDE一个基础的WINDOWS应用程序通讯协议,动态数据交换(DDE)为WINDOWS 应用程序相互通信提供了一个杠架,使 客户和服务器之间开通会话,一次DDE会话(conversation)是指两个合作应用程序从连通到结束的会话期,这两 个应用程序必须都在运行,尽管运行前必有一定的准备。一个DDE对话必须有主题,一个最普通主题 的例子就是文件名,MICROSOFT 认为OLE大有前途,不鼓励使用DDE,但有些事必须使用DDE来作。
(2)现在人都在用ole,
(3)如果数据通讯的两方都是你自己做的,那么就不要用DDE了,好烦的,效率也不高,用WM_COPYDATA
消息要快得多,当然,如果面向工业控制,有些设备提供的就是DDE接口,那就不得不用了