先行知识: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.htm和http://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;
EditView:IOTAEditView;
EditWriterPos:IOTAEditPosition;
i:integer;
begin
Module:=BorlandIDEServices as IOTAModuleServices;
CurentMoudle:=Module.CurrentModule;
//使用IOTAModuleServices的CurrentModule方法得到当前打开的工程模块
if CurentMoudle=nil then
begin
messagebox(IDEHandle,'当前没有打开项目文件','hkTest',MB_ICONINFORMATION);
exit;
end;
//遍历已打开工程中所有的文件
for i:=0 to CurentMoudle.ModuleFileCount-1 do
begin
IntfEditor:=CurentMoudle.ModuleFileEditors[i];
//IOTAModule的ModuleFileEditors[]属性得到一个IOTAEditor
if IntfEditor.QueryInterface(IOTASourceEditor,Editor)=S_OK then
//查看遍历到的文件是否是代码文件并已开始在代码编辑器中编辑。
//如果是便通过一个out参数Editor得到一个实现IOTASourceEditor的实例
break;
end;
if Editor=nil then
begin
messagebox(IDEHandle,'当前没有代码编辑窗口','hkTest',MB_ICONINFORMATION);
exit;
end;
EditView:=Editor.EditViews[i];
//使用IOTASourceEditor的 EditViews[]属性得到一个IOTAEditView
EditWriterPos:=EditView.Position;
//使用IOTAEditView的Position属性最终得到一个IOTAEditPosition
EditWriterPos.InsertText('{/// This is add by the OTATest of hk.barton,enjoy days! ///}');
//IOTAEditPosition的InsertText方法向当前光标位置插入一行代码,这里是一行注释。
end;
procedure TNTATest.AboutForm(sender: TObject);
//一个简单的关于对话框,注意参数中的IDEHandle
begin
messagebox(IDEHandle,'This is a test of OTA write by hk.barton','hkTest',MB_ICONINFORMATION);
end;
procedure TNTATest.ReMoveMenu;
//卸载菜单
begin
if assigned(NewMenu) then
NewMenu.Free;
end;
destructor TNTATest.Destroy;
begin
MyNTATest.ReMoveMenu;
if ImageIndex1<>-1 then
//如果在前面加载图标的工作出现异常就不释放图标,否则会释放到delphi本身使用的图标
MyNTATest.FImageList.Delete(MyNTATest.ImageIndex1);
inherited;
end;
initialization
//在组件第一次被安装时创建了TNTATest
MyNTATest:=TNTATest.Create;
finalization
//在组件被卸载时释放了MyNTATest
MyNTATest.Free;
end.
请注意上面代码中的注释。单就这个例子可能并没有多少用处,然而只要你稍微扩展就可以让这个例子有一点实际用处,你可以加入很多菜单项,每一个项对应一些用户常用的但烦琐的代码,这样就可以免去在开发中输入同样代码的烦琐了,甚至你还可以设置快捷键,也可以做一个设置窗口允许用户自己设置需要的代码和动态的添加菜单项目。当然要完成更复杂的插件你还需要其他的OTA知识,我会在以后的专栏文章中不连续的介绍这些。这次就写到这里,我已经在文章开始的地方推荐了一些学习OTA的资料。