線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:7057
推到 Plurk!
推到 Facebook!

Delphi Open Tools Api實例研究

 
conundrum
尊榮會員


發表:893
回覆:1272
積分:643
註冊:2004-01-06

發送簡訊給我
#1 引用回覆 回覆 發表時間:2004-05-23 20:43:15 IP:61.64.xxx.xxx 未訂閱
 Delphi Open Tools Api實例研究(一)
http://www.csdn.net/develop/read_article.asp?id=21725
先行知識: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的資料,另外如果需要這個例子的全部代碼,請給我來信索取:
hk.barton@sohu.com。        Delphi Open Tools Api實例研究(二)
http://www.kehui.org/aread.php?aid=21163
在開始之前先說一些題外話,這段時間一直很忙(馬上就要期末考試,而且最慘的是現在正在忙著準備
即將到來的英語4級考試),所以自己也不知道這篇文章夠不夠份量。這篇文章的內容可能不是太多,
但我還是抽時間把它寫了出來作為我們的Delphi Open Tools Api實例研究(二)。另外我又發現了一
些很不錯的關於這方面的資料和網站,一併在文後推薦給大家。
還記得上次的實例研究一嗎?我們展示了一個通過設計時元件包擴展delphi的例子。這次我們仍將做一
個實際意義的delphi插件,功能表仍向上次一樣不變,不過這次的不是上次的向原始檔案插入一行代
碼,而是向當前工程中添加一份開發文檔,並顯示在delphi的代碼編輯器中提供給開發者修改。(同時
也保存在工程檔所在的目錄下)。然而這次與上次有一個很大的不同,也是這次最重點要說的地方是:
我們將把這次的插件編譯成dll,而不是上次的元件包,這給創建自己更人性話的插件安裝程式(而不是
叫用戶打開一個元件包來自己安裝)提供了機會。
先來看看這次的重點,我們建立了一個動態連接庫專案,並在加入的第一個單元檔內實現了我們的插件
類。與上次不同的是,我們並不採用傳統的元件註冊過程Register,而是在單元類定義了一個類型為
TwizardInitProc的全局函數,並在專案檔中以WizardEntryPoint名稱導出(注意:必須使用這個名稱。)    exports
 InitNewMenu Name WizardEntryPoint;
下面是單元中的這個函數的原形和實現:
function InitNewMenu(const BorlandIDEServices:IBorlandIDEServices;
    RegisterProc:TWizardRegisterProc;var Terminate:TWizardTerminateProc):boolean;stdcall;
//RegisterProc:TWizardRegisterProc;參數用來創建嚮導,實際上這個初始化函數是用來創建一個dll
//形式的傳統Wizard這種時候就可以向RegisterProc參數傳遞一個實現了IOTAWizard的類實例,用以
//註冊嚮導,如同這樣RegisterProc(xxx.Create)。這裏我們只是使用這個函數作為初始化dll的入口點
//所以並沒有使用這個函數,而是直接MyNTATest:=TNTATest.Create; 另外var Terminate參數用來釋放
//你在嚮導中使用的資源,你可以賦給它一個普通的過程類型如Terminate:=xxx; xxx為一個procedure
//這樣ide在退出時,便會調用這個過程來釋放資源。注意這個函數必須以stdcall指示。
var
 svcs:IOTAServices;
begin
 result:=BorlandIDEServices <> nil;
 if result then
 begin
  svcs:=BorlandIDEServices as IOTAServices;
  //保存BorlandIDEServices指針
  ToolsAPI.BorlandIDEServices:=BorlandIDEServices;
  //設置dll的host application控制碼
  Application.Handle:=svcs.GetParentHandle;
  MyNTATest:=TNTATest.Create;
 end;
end;
另外這次也使用了一個新的OTA介面,主要體現在功能表項的第一個事件內(我們完成了向當前工程
中添加一份開發文檔的工作):IOTAActionServices,這是個相當有用的介面,在ide運行時由
BorlandIDEServices實現,可以用來完成對IDE各種功能的調用。如:CloseFile、OpenFile、
OpenProject、ReloadFile、SaveFile。這些功能的作用根據它們的名稱就能猜測出來。本來我還想使用
IOTAProjectOptions介面來得到當前工程的相關資訊和配置選項一併寫在文檔檔裏,但並沒有得到理想
的結果,最多只得到了關於工程有哪些Options的字串列表。我們有可能將在下次的文章中研究和
IOTAProject相關的一系列介面。下面是產生文檔部分的代碼(對應于功能表項的第一個功能表事件):
procedure TNTATest.AddDocumentToPro(sender:TObject);
var
 templen,i,temppos:integer;
 DocumentFile:TextFile;
 ModuleCount: Integer;
 TempString,MoudleFilePath:String;
begin
 //請求IOTAModuleServices t介面
 Supports(BorlandIDEServices,IOTAModuleServices,MoudleService);
 ModuleCount:=MoudleService.ModuleCount;
 if ModuleCount<>0 then //沒有任何檔打開
 begin
  CurentMoudle:=MoudleService.CurrentModule;
  //獲得當前的檔案名,我們可以用它得到專案路徑
  TempString:=CurentMoudle.FileName;
  //下面的部分用來分析字串並取出專案的路徑//
  i:=pos('',TempString);templen:=length(TempString);temppos:=i;
  while i<>0 do
  begin
TempString:=RightStr(TempString,templen-i);
i:=pos('',TempString);templen:=length(TempString);temppos:=temppos i;
  end;
  MoudleFilePath:=leftstr(CurentMoudle.FileName,temppos);
  ////////////////////////////////////////////
  if Supports(BorlandIDEServices,IOTAActionServices,ActionServices) then
  begin
AssignFile(DocumentFile,MoudleFilePath 'DocumentFile.txt');
Rewrite(DocumentFile);
try
    writeln(DocumentFile,'項目名稱:');
    writeln(DocumentFile,'專案主程序名:');
    writeln(DocumentFile,'項目版本號:');
    writeln(DocumentFile,'項目描述:');
    writeln(DocumentFile,'專案組成員:');
    writeln(DocumentFile,'文檔建立時間:' DateTimeToStr(now));
finally
    CloseFile(DocumentFile);
end;
//注意,我們用到了IOTAActionServices的OpenFile方法來打開剛才保存的文檔
ActionServices.OpenFile(MoudleFilePath 'DocumentFile.txt');
  end;
 end
 else
  messagebox(IDEHandle,'There isn''''t Active Project.','DocCreator',MB_ICONWARNING);
end;
其餘的代碼(如向IDE添加功能表等和上次的類似),在這裏我們就省略不寫出來了,你可以參看上一
篇文章(連接: http://www.csdn.net/develop/read_article.asp?id=21725 ),也可以給我來信得到代碼(位址和上
一次的一樣)。最後當我們完成代碼編寫時,我們就可以將它編譯成dll檔。現在來看看我們怎麼來安
裝我們的插件:首先退出delphi,打開註冊表,
在HKEY_CURRENT_USER/Software/Borland/Delphi/7.0/Experts鍵下新建立一個字串類型的值,名稱為我們
的插件的名稱,值為我們編譯得到的dll的路徑。現在重新啟動delphi,怎麼樣,插件開始工作了吧。
有了這個辦法後相信我們可以很容易的為我們的插件創建安裝程式和反安裝程式(無非就是簡單的操作
註冊表而已,當你再次推出delphi,並在註冊表中刪除剛才建立的值,重啟delphi會發現插件已經卸載
了),使其變的對用戶更友好,也可以更加方便我們的發佈。最後再做一點說明:關於調試dll插件的
說明,我建議不要在編碼過程中返太多的錯誤(指運行時的),你會發現你會為調式插件而不得不一次
次的啟動、關閉delphi,這是一件相當煩人的事情(把我害慘了),我建議你可以先把插件作為上一次
的元件包形式進行測試和調式,這樣不用重啟delphi,當調式成功後再改為dll形式的。另外這次的插件
依然以delphi7為基礎,在低版本的delphi中可能會有錯誤。最後說明的是,這個插件可能會使delphi在
退出的時候產生一個AV錯誤,我暫時還沒有解決這個問題(希望高人指教),雖然我使用了string類
型,但我也的確引用了sharemem單元。我也嘗試在Terminate參數中向其賦一個退出釋放資源的函數,
但問題依舊。
在文章的最後我向大家推薦一些我最近發現的一些關於OTA的相當不錯的網站和資料:    http://www.gexperts.org/opentools/  在這裏你將會得到一份OTA的FAQ,相當不錯。
http://www.tempest-sw.com/opentools/  一些老版本的OTA資料
http://home.quicknet.nl/qn/prive/rapp/delphi/delphi.html  OTA部分介面的相關資料和例子
http://www.frasersoft.net/program/  delphi的OTA幫助補丁(非官方),但相當不全
發表人 - conundrum 於 2004/05/23 20:50:52
系統時間:2024-11-21 16:39:39
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!