DelphiIDE扩展开发
unit as1
{
一、 概 述
1、 前言
Delphi的IDE扩展是一般程序员很少涉足的领域,不管是网上还是书店里,这方面的资料都是鲜有所见。Delphi7自带的帮助文件是我们最容易找到的资料,为了方便CnPack开发组成员以及对IDE扩展感兴趣的朋友对这一领域有更多的认识,我花了点时间把Delphi7中IDE扩展部分的帮助翻译成中文发布,希望对大家有所帮助。
2、 术语列表
以下是本文档翻译时使用的术语对照表:
插件(Add-in),以设计期包或DLL形式被设计期的IDE调用的扩展工具。
专家(Wizard),实现了IOTAWizard接口的IDE插件工具。
仓库专家(Repository Wizard),用来创建新的单元、窗体或工程的专家。
包(Package),Delphi中使用的特殊的动态链接库。
设计期包(Design-time Package),被编译为允许IDE在设计期装载的包。
运行期包(Runtime Package),被编译为允许DLL在运行期调用的包。
接口(Interface),Delphi中使用的COM风格的接口。
通知器(Notifier),由用户实现特定接口并由IDE在特定事件中调用的用户对象。
创建器(Creator),由用户实现特定接口的用于创建新的单元、窗体或工程的用户对象。
工程(Project),Delphi中的Project。
单元(Unit),Delphi中的Unit。
模块(Module),对应着一组在IDE中打开的逻辑上关联的文件集,可以是一个单元、包含窗体的单元、工程文件等对象。
编辑器(Editor),IDE中用来设计和编辑模块的对象。
其它如IDE、DLL、Action、Tools API这样的术语则沿用英文,不作翻译。
二、 扩展Delphi的IDE
1、 IDE扩展
通过使用Open Tools API(通常缩写为Tools API),你可以用你自己的菜单项、工具栏按钮、动态的窗体创建专家以及更多的东西来扩展和定制IDE。Tools API是一套超过100个用于关联以及控制IDE的接口,包括主菜单、工具栏、主Action列表以及图像列表、源代码编辑器内部缓冲区、键盘宏及键盘绑定、窗体设计器中的窗体及其上面的组件、调试器和正在被调试的进程、代码完成、消息视图,以及任务列表。
使用Tools API是一件容易的事情,只要写几个实现了特定接口的类,并调用由另一些接口提供的服务即可。你的Tools API代码必须编译并作为一个设计期包或DLL装载到设计期的IDE中。这样,编写一个Tools API扩展有些类似编写一个属性或组件编辑器。在阅读这份材料之前,请确信你已经对基本的 用包来工作 和 注册组件 比较熟悉了。
下面这些主题描述了怎样使用Tools API:
Tools API概述
编写一个专家类
获得Tools API服务
对文件和编辑器的操作
创建窗体和工程
IDE的专家事件通知
2、 Tools API概述
所有的Tools API声明都在这一个单元里:ToolsAPI。要使用Tools API,你通常要引用designide这个包,这意味着你需要将你的Tools API插件作为设计期包或使用了运行期包的DLL来构建。关于包和库的问题,参阅 安装专家包。
编写一个Tools API扩展的主要接口是IOTAWizard,故大部分IDE插件都称为专家(Wizards)。在绝大多数情况下,C++Builder和Delphi的专家可以通用。你可以在Delphi中编写和编译一个专家,然后在C++Builder中使用,反之亦然。这种共用的工作最好在同一个IDE版本号之间,但同样也可能编写一个专家并且他们能在两种产品的将来版本里都可使用。要使用Tools API,你可以编写一个专家类并实现在ToolsAPI单元中定义的一个或多个接口。专家可以利用在Tools API中提供的服务,每个服务都是一个提供一组相关函数的接口,接口的实现部分被隐藏在IDE里面。Tools API只公布了接口,你可以利用它们来编写你的专家,而不必关心那些接口的实现细节。这些不同的接口提供了对源代码编辑器、窗体设计器、调试器等的访问。怎样在你的专家中使用这些接口包含的服务,参阅 获得Tools API服务。
这些服务和其它的接口被划分为两个基本的分类,你可以按照用为类型名称的前缀来区分它们:
NTA(native tools API)本地的Tools API允许直接访问实际的IDE对象,如IDE的TMainMenu对象。当使用这些接口时,专家必须引用Borland的包,这意味着专家将限制于特定的IDE版本中。这类专家可以放在一个设计期包或使用了运行期包的DLL中。
OTA(open tools API)开放的Tools API不需要引用包,只能通过接口访问IDE。在理论上,如果你能支持Delphi的函数调用约定以及类似于AnsiString这样的Delphi类型,则你能够使用任何支持COM风格接口的语言来编写专家,但是几乎所有的Tools API功能都只能通过OTA接口获得。如果一个专家只使用OTA接口,则它有可能写成一个不依赖于特定IDE版本的DLL。
Tools API有两种类型的接口:一种是作为程序员的你必须实现的接口,另一种是IDE已经实现了的接口。大部分的接口属于后者的分类:接口定义了IDE的功能而隐藏了真正的实现。你必须实现的接口可分为以下三类:专家(Wizards)、通知器(Notifiers)以及创建器(Creators):
在前面的主题中提到,一个专家类要实现IOTAWizard接口以及可能的派生接口。
通知器是Tools API中另一种类型的接口。IDE使用通知器在某些你关注的事情发生时回调你的专家。你可以编写一个类来实现通知器接口,并使用Tools API注册该通知器,当用户打开一个文件、编译源代码、修改窗体、开始调试会话及其它情况时,IDE会回调你的通知器对象。通知器的介绍见 IDE的专家事件通知。
创建器是你必须实现的另一种类型的接口。Tools API使用创建器来创建新的单元、工程或文件,或用来打开一个已存在的文件。关于创建器的内容请查看 创建窗体和工程 部分。
其它的重要接口是模块(Module)和编辑器(Editor)。一个模块接口代表了一个打开的单元,包含一个或多个文件。一个编辑器接口代表一个打开的文件。不同类型的编辑器接口提供给你对IDE中不同方面的访问:源代码编辑器(Source Editor)对应着源代码文件,窗体设计器(Form Designer)对应着窗体文件,另外还有工程资源(Project Resource)对应资源文件。关于模块和编辑器的内容请查看 对文件和编辑器的操作 部分。
3、 编写一个专家类
一共有四种类型的专家,专家的类型依赖于专家类所实现的接口。下面的表格描述了这四种类型的专家:
四种类型的专家
接口 描述
IOTAFormWizard 用来创建新的单元、窗体或其它文件
IOTAMenuWizard 自动增加到Help菜单中
IOTAProjectWizard 用来创建一个新的应用程序工程。
IOTAWizard 不适合放在其它分类中的各种专家
这四种类型的专家区别仅在于用户怎样调用专家:
菜单型专家(Menu Wizard)将增加到IDE的Help菜单中。当用户选择该菜单项时,IDE将调用该专家的Execute方法。普通的专家表现得更为灵活,故菜单型专家通常只在原型和调试时使用。
窗体和工程专家又叫仓库专家,因为他们被放在对象仓库(Object Repository)中。用户在新建项目对话框中调用这些专家,用户也能在对象仓库(通过选择Tools|Repository菜单项)中看到这些专家。用户可以为一个窗体专家选中“New Form”检查框,这将通知IDE当用户从主菜单中选择“File|New Form”时,将自动调用这个窗体专家。用户同样可以选择“Main Form”检查框,这将通知IDE使用这个窗体专家来生成新应用程序默认的主窗体。用户还可以为一个工程专家选择“New Project”检查框,当用户选择“File|New Application”时,IDE将调用选择的工程专家。
第四种类型的专家用于不能放到其它分类时的情况。一个普通的专家自身不能做任何事,取而代之的是,你必须自己定义专家怎样被调用。
Tools API并不对专家作任何强制性的约束,比如并不是必须要有个工程专家才能创建工程。你可以很容易地写一个工程专家来创建一个窗体以及写一个窗体专家来创建工程(如果你确实想要这样做的话)。
下面的主题详细说明了怎样实现和安装专家:
实现专家接口
安装专家包
4、 实现专家接口
每一个专家类至少必须实现IOTAWizard接口,同样,也要求实现它的父接口:IOTANotifier和IInterface。 窗体和工程专家必须实现他们的所有父接口,即:IOTARepositoryWizard、IOTAWizard、IOTANotifier和IInterface。
你对IInterfac的实现必须遵循Delphi接口的一般规则,这同样也是COM接口的规则。即,QueryInterface执行类型匹配,_AddRef和_Release管理引用计数。你可能会想使用一个公共的基类来简化专家和通知器类的编写。出于这个考虑,ToolsAPI单元定义了一个类,TNotifierObject,它实现了IOTANotifier接口并使用了空方法体。
尽管专家继承自IOTANotifier,而且因此必须实现它定义的所有函数,但IDE通常并不使用这些函数,所以你的实现可以为空(它们在TNotifierObject中实现)。因此,当你编写你的专家类时,你只需要声明并实现那些在专家接口中引入的方法就行了,默认使用TNotifierObject对IOTANotifier的实现。
5、 安装专家包
类似于其它的设计期包,一个专家包(Wizard Package)也必须实现一个Register函数。(关于Register函数的详细说明见 注册组件。)在Register函数中,通过调用RegisterPackageWizard,你可以注册任意多的专家,并传递一个专家对象作为唯一的参数,如下所示:
procedure Register;
begin
RegisterPackageWizard(MyWizard.Create);
RegisterPackageWizard(MyOtherWizard.Create);
end;
同样,你也可以注册属性编辑器、组件等等,作为同一个包的一部分。
请记住,设计期包是Delphi主程序的一部分,这意味着所有窗体的名称在整个应用程序和所有其它的设计期包中都应该是唯一的。这是使用包方式主要的一个缺点:你并不知道其它人会怎样命名他们的窗体。
在开发中,安装包专家类似于其它的设计期包:在包管理器中点击Install按钮。IDE将编译和连接该包并尝试装载它。如果装载包成功,IDE会显示一个对话框通知你。
(译注:如果是DLL类型的专家,需要在注册表中注册,例如在Delphi7中注册一个名称MyWizard的专家,可以在注册表HKEY_CURRENT_USER\Software\Borland\Delphi\7.0\Experts中增加一个字符串项:MyWizard,值为DLL的完整路径文件名)
6、 获得Tools API服务
为了做一些有用的工作,专家需要访问IDE:它的编辑器、窗体、菜单等等,这些是服务接口的任务。Tools API包括很多的服务,例如用Action服务执行文件Action操作,用编辑器服务访问源代码编辑器,用调试器服务访问调试器,等等。下面的表格总结了所有的服务接口:
Tools API服务接口
接口 描述
INTAServices 提供对本地IDE对象的访问:主菜单、Action列表、图像列表和工具栏。
IOTAActionServices 实现基本的文件操作:打开、关闭、保存和重装载文件。
IOTACodeCompletionServices 提供对代码完成的访问,允许专家安装自定义的代码完成管理器。
IOTADebuggerServices 提供对调试器的访问。
IOTAEditorServices 提供对源代码编辑器及其内部缓冲区的访问。
IOTAKeyBindingServices 允许专家注册自定义的键盘绑定。
IOTAKeyboardServices 提供对键盘宏和绑定的访问。
IOTAKeyboardDiagnostics 切换按键调试。
IOTAMessageServices 提供对消息视图(Message View)的访问。
IOTAModuleServices 提供对打开的文件的访问。
IOTAPackageServices 查询已安装的包及他们的组件的名称。
IOTAServices 其它的服务。
IOTAToDoServices 提供对To-Do列表的访问,允许专家安装自己的To-Do列表管理器。
IOTAToolsFilter 注册工具过滤通知器(Tools Filter Notifiers)。
IOTAWizardServices 注册及删除专家。
要使用服务接口,使用在SysUtils单元中定义的全局的Supports函数将BorlandIDEServices变量转换为目标服务接口。例如:
}
procedure set_keystroke_debugging(debugging: Boolean);
var
diag:
IOTAKeyboardDiagnostics
begin
if Supports(BorlandIDEServices, IOTAKeyboardDiagnostics, diag) then
diag.KeyTracing := debugging;
end;
{
如果你的专家频繁地使用一个特定的服务,你可以将这个服务的指针作为一个数据成员保存在你的专家类中。
下面的主题讨论了使用Tools API服务接口来工作时一些特定的事项:
使用本地IDE对象
调试专家
接口版本号
7、 使用本地IDE对象
专家可以完全地访问IDE的主菜单、工具栏、Action列表和图像列表。(注:IDE的很多弹出菜单不能直接通过Tools API来访问。)
对IDE本地对象的操作以INTAServices接口为起点,你可以使用这个接口来增加图像到图像列表,增加Action到Action列表,添加菜单项到主菜单,以及在工具栏上添加按钮。你也可以关联Action到菜单项和工具栏按钮。当专家释放的时候,它必须清除那些由它自己创建的对象,但是不能删除它增加到图像列表中的图像,因为删除图像可能会打乱所有在该专家之后增加的其它图像的索引号。
下面的主题阐述了如何执行这些操作:
增加图像到图像列表
增加Action到Action列表
删除工具栏按钮
专家操作的是IDE中真实的TMainMenu、TActionList、TImageList和TToolBar对象,故你可以象在编写其它应用程序那样写代码。这同样意味着你有很大的机会让IDE崩溃或者禁用掉一些重要的功能,例如删除文件菜单。调试专家 讨论了当你发现类似这样的问题时,怎样调试你的专家的方法。
8、 增加图像到图像列表
假如你打算增加一个菜单项来调用你的专家,你同样也会允许用户增加一个工具栏按钮来调用这个专家。第一个步骤是增加一个图像到IDE的图像列表,然后你增加的图像的索引号就可以在Action中使用,随后也就可在菜单项和工具栏中使用。使用图像编辑器(Image Editor)创建一个包含16X16位图资源的资源文件,然后在你的专家构造器中加上下面的代码:
}
constructor MyWizard.Create;
var
Services: INTAServices;
Bmp: TBitmap;
ImageIndex: Integer;
begin
inherited;
Supports(BorlandIDEServices, INTAServices, Services);
{ Add an image to the image list. }
Bmp := TBitmap.Create;
Bmp.LoadFromResourceName(HInstance, 'Bitmap1');
ImageIndex := Services.AddMasked(Bmp, Bmp.TransparentColor,
'Tempest Software.intro wizard image');
Bmp.Free;
end;
{
请确认使用你在资源文件中指定的名称或ID来装载位图。你必须选择一个颜色来作为图像的背景颜色。如果你不想要背景颜色,可以选择一个在位图中不存在的颜色。
9、 增加Action到Action列表
在 增加图像到图像列表 中获得的图像索引号可以用来创建Action,如下所示。专家使用OnExecute和OnUpdate事件。在专家中常用的方法是在OnUpdate事件中启用或禁用Action,请确认OnUpdate事件能很快的返回,否则用户将会发现在装载你的专家后,IDE变得慢如蜗牛。Action的OnExecute事件类似于专家的Execute方法。如果你使用菜单项来调用一个窗体或工程专家,你甚至可能希望让OnExecute直接调用Execute方法。
}
NewAction := TAction.Create(nil);
NewAction.ActionList := Services.ActionList;
NewAction.Caption := GetMenuText();
NewAction.Hint := 'Display a silly dialog box';
NewAction.ImageIndex := ImageIndex;
NewAction.OnUpdate := action_update;
NewAction.OnExecute := action_execute;
// 菜单项可以设置它的Action属性为新创建的Action。创建一个新的菜单项时比较复杂的部分是要知道它应该被插入到哪儿。下面的例子查找View菜单,并且创建一个新的菜单项作为第一项插入到View菜单下。(通常,依赖于绝对位置不是个好主意:你无法知道还有哪些其它的专家会将自己插入到菜单中。另外,将来版本的Delphi也可能会调整菜单项的顺序。一个更好的方法是使用特定的名称查找指定的菜单项。下面的简单例子仅用来说明概念。)
for I := 0 to Services.MainMenu.Items.Count - 1 do
begin
with Services.MainMenu.Items[I] do
begin
if CompareText(Name, 'ViewsMenu') = 0 then
begin
NewItem := TMenuItem.Create(nil);
NewItem.Action := NewAction;
Insert(0, NewItem);
end;
end;
end;
{
增加Actoin到IDE的Action列表中后,用户就能在定制工具栏时看到这个Action了。用户可以选择Action并把它作为按钮增加到工具栏上。这在你的专家被卸载时会带来一些问题:所有这些指向已经不存在的Action的工具栏按钮和它们的OnClick事件句柄都将被悬空。为了防止访问违规错误,你的专家必须查找所有引用了它的Action的工具栏按钮,并且删除它们。
10、 删除工具栏按钮
此处没有直接的函数来从工具栏中删除按钮,你必须自己发送CM_CONTROLCHANGE消息。消息的第一个参数是要修改的控件,第二个参数为零表示从工具栏中删除(非零是添加)。删除工具栏按钮后,专家析构器删除Action和菜单项,删除这些对象将自动把它们从IDE的ActionList和MainMenu中移除。
}
procedure remove_action(Action: TAction; ToolBar: TToolBar);
var
I: Integer;
Btn: TToolButton;
begin
for I := ToolBar.ButtonCount - 1 downto 0 do
begin
Btn := ToolBar.Buttons[I];
if Btn.Action = Action then
begin
{ Remove "Btn" from "ToolBar" }
ToolBar.Perform(CM_CONTROLCHANGE, WPARAM(Btn), 0);
Btn.Free;
end;
end;
end;
destructor MyWizard.Destroy;
var
Services: INTAServices;
Btn: TToolButton;
begin
Supports(BorlandIDEServices, INTAServices, Services);
{ Check all the toolbars, and remove any buttons that use this action. }
remove_action(NewAction, Services.ToolBar[sCustomToolBar]);
remove_action(NewAction, Services.ToolBar[sDesktopToolBar]);
remove_action(NewAction, Services.ToolBar[sStandardToolBar]);
remove_action(NewAction, Services.ToolBar[sDebugToolBar]);
remove_action(NewAction, Services.ToolBar[sViewToolBar]);
remove_action(NewAction, Services.ToolBar[sInternetToolBar]);
NewItem.Free;
NewAction.Free;
end;
{
11、 调试专家
Tools API为你的专家与IDE之间的交互提供了极大的灵活性。然而,这种灵活性也带来了风险,很容易因为空指针和访问违规导致错误。
当使用本地