Delphi Open Tools Api实例研究(一)

先行知识: Delphi/ 接口 /VCL 组件包 /COM (了解)

难度:★★☆☆☆

在这篇文章正式开始以前,首先向大家道歉。因为这个月的专栏文章本该很早就发布,但由于一些事情所以一直推迟到现在,并且这个月也只发布了这么一篇。另外,关于这篇文章我觉得我应该感谢 csdn 上的几位朋友,他们是 chechy 、 FrameSniper 、 pankun ,特别感谢 chechy ,让我认识到 Delphi Open Tools Api (以下简称 OTA )的有趣,并决定在其上面投入一些精力。并向我介绍了相当不错的资料。另外还要说明的是虽然题目叫 xxxxx (一),但接下来的文章可能不是 xxxxx (二)因为这个系列文章的每一篇都会是一个独立的内容,之所以叫 xxx (一)是因为我会在以后的文章中不连续的些一写关于 OTA 的东西。

呵呵,说了那么多的废话,现在开始!但在开头还要再罗嗦一下,大概的介绍一下 OTA OTA 是 delphi 的各个版本中都有提供的一套有趣的接口,运用它你可以任意的扩展 delphi 的 IDE ,使之符合自己的需要。例如你可以扩展 IDE 的菜单、代码编辑器、窗口设计器、属性编辑器和控件编辑器(这个已经在前面的一系列关于 VCL 开发的文章中说明过)等等,几乎你想得到的所有地方,甚至是 code inside 功能你都能够扩展!这个激动人心的特性在 delphi5 以后得到了更好的发展,变的更易于使用。使开发者可以用很少的、很基础的代码完成这些有趣的扩展实现强大的功能。通过 OTA 也使你能够领略到 delphi IDE 完美的设计,建立在 COM 技术基础上使得 delphi IDE 能够轻易被客户扩展而无须重新编译 IDE 。

在进行这次的例子前应该指出想要学习 OTA 的最好资料是位于 delphi 安装目录下的 Source à ToolsAPI 里的 ToolsAPI.pas 文件,它列出了所有 OTA 的接口并有比较详细的注释说明。另外关于 OTA 的站点,你可以去 http://delphi.about.com/library/weekly/aa033099.htmhttp://www.tempest-sw.com/opentools/ 看看 ,还可以去 borland 的新闻组 borland.public.delphi.opentoolsapi 参与讨论。当然,《 delphi5 开发者指南》中的 26 章也介绍了一些 OTA 的知识,并演示了如何自己实现一个 delphi 中的向导(本文就不讲述这个了),你们也可以去看看。

由于 delphi OTA 的版本差异性比较大,这个文章中的内容都以使用 delphi7 为前提。当 delphi IDE 处于运行中的时候有一个我们应该清楚的一个重要的实例( Instance )是 BorlandIDEServices , 它实现了众多 OTA 接口,换句话说我们可以从 BorlandIDEServices 得到很多接口,并且这些接口在 delphi 运行时已经被实现,我们只用通过接口调用接口方法就可以轻松的得到 IDE 的很多东西,比如菜单、窗口等等,有了这些,扩展 delphi IDE 便成为了现实。为了能够扩展 delphi IDE 我们必须要在 delphi 处于运行时进入,这意味着我们可以有两种方法来实现我们的 delphi 扩展(也可以叫插件)并向外发布,一种办法是将插件做成设计时 VCL 组件包(本文采取这种形式,关于 VCL 组件包请参看我在之前发表的文章),让客户在 delphi 运行时安装。另一种办法是将插件基于一个 DLL 并在注册表中的 H K E Y _ C U R R E N T_ U S E R \ S o f t w a r e \ B o r l a n d \D e l p h i \ 7 . 0 \ E x p e r t s 注册,并在 DLL 中以一个特殊的导出函数作为入口点, delphi IDE 在重新启动以后便会加载你的插件(这个方法将在以后的文章中说明)。后者为建立简单化的插件安装程序提供了可能,前者需要用户在 delphi 运行中如同安装组件一样的进行安装。我们的例子将向 delphi 的主菜单中添加一个有两项的菜单(名字叫做 hk.barton ),点击第一项将向当前工程的第一个代码编辑器的代码中插入一句指定的代码,第 2 项简单的显示一个关于信息。当你不想使用这个菜单的时候只需要象一般组件的卸载一样将其卸载就可以了。最先还是来看看组件包的项目文件,如果大家看过我之前的关于 VCL 的文章就应该很熟悉了:

package Package1;

//… 省略编译器指示,注意将这个组件设计为设计时( Design Time )的

requires

rtl,

vcl,

designide;

contains

NTAMenu in 'NTAMenu.pas';

end.

可以看到我们将在 NTAMenu.pas 中实现我们的插件,在这个文件中我们主要用到了以下的接口: IOTAServices ,被 BorlandIDEServices 直接实现的接口,是 OTA 的一个基础接口,我们用它的 GetParentHandle 方法来取得 delphi IDE 的句柄; INTAServices ,在 delphi 运行时被实现的接口,可以用它的方法直接得到 delphi IDE 的 MainMenu 、 ImageLis 、 ActionList 、 ToolBar 这样我们就可以直接做很多操作了; IOTAModuleServices 、 IOTAModule 、 IOTAEditor 、 IOTASourceEditor 、 IOTAEditView ,在代码中可以看到我们用这些接口来一步一步得到代码编辑器并最后得到一个可以在代码中当前的光标位置处理数据的 IOTAEditPosition 接口,我们就用它来向当前光标处插入一句代码,插入大量的代码段还可以使用 IOTAEditWriter 接口。关于在下面的代码中使用到的接口方法我们会在注释中做说明,没有使用到的接口和其它方法别忘了查看 ToolsApi.pas 文件。另外可以注意到下面的代码在很多地方进行检测以保证代码在运行后尽量不要出问题以及在出现异常时能够合理释放资源。别忘了,我们的目的是扩展 delphi ,而并不是要把 delphi IDE 弄的面目全非。

unit NTAMenu;

interface

uses

SysUtils, Classes,Menus,ToolsApi,Controls,ImgList,Graphics,Forms,ComCtrls,windows;

type

TNTATest = class

private

FMainMenu:TMainMenu; // 用来存贮 delphi IDE 的主菜单

NewMenu:TMenuItem; // 我们将要插入的菜单

FImageList:TCustomImageList; // 用来存贮 delphi IDE 主菜单和工具栏的 ImageList

ImageIndex1:integer; // 检测量,请参看后面的代码

IDEHandle:HWND; // 存贮 IDE 的 handle

protected

procedure AddMenu; // 加入我们的菜单

procedure ReMoveMenu; // 卸载我们的菜单

procedure ReCodeEditer(sender:TObject); // 菜单项一的事件

procedure AboutForm(sender:TObject); // 菜单项二的事件

public

constructor Create;

destructor Destroy;override;

end;

procedure Register;

var

MyNTATest:TNTATest;

implementation

procedure Register;

begin

MyNTATest.AddMenu;

// 和传统组件的同名方法不同,这里没有在组件面板上安装图标

// 而是直接调用 AddMenu 方法添加我们的菜单

end;

{ TNTATest }

constructor TNTATest.Create;

begin

IDEHandle:=(BorlandIDEServices as IOTAServices).GetParentHandle;

// 我们用 IOTAServices 接口的 GetParentHandle 方法取得了 ide 的 handle

end;

procedure TNTATest.AddMenu;

var

MenuItem:array [0..2] of TMenuItem;

i:integer;

Icon1:TIcon;// 菜单项一的图标

begin

FMainMenu:=(BorlandIDEServices as INTAServices).MainMenu;

// 我们用 INTAServices 的 MainMenu 属性直接得到了 IDE 的主菜单

FImageList:=(BorlandIDEServices as INTAServices).ImageList;

// 我们用 INTAServices 的 ImageList 属性直接得到了 IDE 的图象列表

NewMenu:=TMenuItem.Create(FMainMenu);

// 创建我们的菜单

NewMenu.Caption:='hk.barton';

ImageIndex1:=-1;// 没有载入图标

// 下面的代码使用 for 和 case 来添加两个菜单项有点小题大作,但

// 我们展示了一种更通用的方法使你能够添加更多的菜单项,而不必简单的复制代码。

for i:=0 to 2 do

begin

MenuItem[i]:=TMenuItem.Create(NewMenu); // 创建子菜单项

case i of

0:

begin

MenuItem[i].Caption:='InsertText';

Icon1:=TIcon.Create;

try

Icon1.LoadFromFile('D:\MyWorks\MyComponent\OTATest\NewForm.ico');

// 我从硬盘的文件上载入了一个图标作为菜单项一的图标

except

On E:Exception do

begin

raise Exception.Create(E.Message);

exit;

end;

end;

ImageIndex1:=FImageList.AddIcon(Icon1);

// 加入那个载入的图标并返回一个 ImageIndex

MenuItem[i].ImageIndex:=ImageIndex1;

MenuItem[i].OnClick:=ReCodeEditer; // 添加事件处理程序

end;

1:MenuItem[i].Caption:='-'; // 当然还有一个分割符号,其实是 3 个菜单项

2:

begin

MenuItem[i].Caption:='About';

MenuItem[i].OnClick:=AboutForm;

end;

end;

NewMenu.Add(MenuItem[i]); // 添加菜单项

end;

FMainMenu.Items.Add(NewMenu); // 最后添加我们的菜单到 IDE 主菜单

end;

procedure TNTATest.ReCodeEditer(sender:TObject);

var

Module:IOTAModuleServices;

CurentMoudle: IOTAModule;

IntfEditor:IOTAEditor;

Editor:IOTASourceEditor;<o:p

Published At
Categories with Web编程
Tagged with
comments powered by Disqus