DELPHI基础教程
第七章 剪贴板和动态数据交换(二) 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 中的一个工作表交换数据。程序设计界面 界面中包含一个 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.9 用客户程序控制程序管理器 下面的例子用客户程序向程序管理器发送命令,用于创建程序组、程序项以及删除程序组。 程序管理器提供了应用程序的 DDE 接口命令字符串,应用程序利用这些命令字符串可以实现以下的功能: 1. 创建程序组 命令格式为: CreateGroup( 程序组名 [ ,程序组所在的路径 ]) 程序组不存在时进行创建;如程序组存在则按照指定的路径激活。 2. 删除程序组 命令格式为: DeleteGroup( 程序组名 ) 3. 显示程序组 命令格式为; ShowGroup( 程序组名,显示标志 ) 显示标志用于控制程序组在程序管理器中以极大、极小或正常方式显示。 4. 重新装入程序组 命令格式为: ReLoadGroup( 程序组名 ) 该命令使程序管理器先删除而后再重新装入一个已有的程序组。 5. 向程序组中添加程序项 命令格式为: AddItem( 命令行 [ ,描述 [ ,图标路径 [ ,图标序号 [ ,图标横坐标,图标纵坐标 [ ,工作区目录 [ ,热键 [ ,是否最小化显示标志 ]]]]]]]) 命令行控制程序项的执行,可包括路径、参数等。其它参数分别对应在程序管理器中添加一个程序项时需要设置的参数和选项。它们都有缺省设置,因而是可选的。 6. 替换程序组中的程序项 命令格式为: ReplaceItem( 程序项名 ) 该命令删除一个程序项,并将所删除程序项的位置记录下来,以后通过 AddItem 在这个所记录的位置增加新项目。 7. 从程序组中删除程序项 命令格式为: DeleteItem( 程序项名 ) 从当前活动程序组中删除一个程序项。 8. 关闭程序管理器 命令格式为: ExitProgram( 是否保存程序组信息标志 ) 从应用程序向程序管理器发送命令字符串的方法是基本一致的。为简便起见,在例程中只实现了其中仅包含一个字符串参数的情形,读者可以很容易作进一步的扩展。 程序设计界面如图所示,包含一个 DDE 客户会话 (DDEClientConv) 部件和四个完成不同功能的按钮。 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. 根据客户程序发送来的宏指令改变自身的运行状态。 其中各部件的关键属性如下: 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 客户程序设计的完整实例。
程序把接收到的 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.4.4 小结 剪贴板和 DDE 是 Windows 下数据通信的两种方法。 Delphi 以简便、友好的方式实现了相应的功能,为用户编程提供了方便。一般说来,剪贴板多用于静态数据传输,而 DDE 用于动态数据交换、控制其它程序运行等场合。这些内容对于多用户环境下的程序开发是很重要的 |