Delphi開發(fā)ActiveX控件之一步到位
原文:Ray Konopka 翻譯:陳省
Delphi ActiveX 框架
Delphi ActiveX 框架(DAX)使我們能夠很容易的通過轉(zhuǎn)換向?qū)?/span>Delphi本身的控件(VCL)出發(fā)創(chuàng)建ActiveX,而不需要你具備很多的COM知識(shí),生成的ActiveX可以應(yīng)用在VC、 VB等不同的開發(fā)環(huán)境中,避免重復(fù)開發(fā),實(shí)現(xiàn)真正的代碼重用。
創(chuàng)建ActiveX涉及到生成一個(gè)VCL的COM封裝,封裝類繼承于TActiveXControl 實(shí)現(xiàn)了一個(gè)對(duì)Delphi控件的引用參考。Delphi的VCL控件要想能夠轉(zhuǎn)換為ActiveX控件,必須是繼承于TWinControl。這是因?yàn)?/span>COM封裝和VCL控件之間的通訊需要一個(gè)窗口句柄,只有TWincontrol的子類才有句柄。
封裝類通過屬性和方法向其他的對(duì)象公開了Delphi控件的功能。同時(shí)它也負(fù)責(zé)創(chuàng)建管理和銷毀嵌入的VCL控件。COM封裝類通常還負(fù)責(zé)響應(yīng)控件產(chǎn)生的事件,并提交給ActiveX 控件容器。
聽起來好像有點(diǎn)復(fù)雜,幸好Delphi提供了ActiveX控件向?qū)Э梢詷O大的簡(jiǎn)化過程讓我們把一個(gè)最常見的TListBox控件轉(zhuǎn)化成ActiveX。首先選File|New|ActiveX頁,如下圖。
然后選擇ActiveX Control項(xiàng),顯示一個(gè)對(duì)話框,第一步是選則VCL的類名,VCL類名下拉框中列出了全部TWinControl的子類(不包括使用RegisterNonActiveX函數(shù)注冊(cè)的控件)從列表我們可以看到TListBox,但你找不到TLable,因?yàn)?/span>TLabel繼承于TGraphicControl,當(dāng)然象那種TTimer的非可視控件更是沒有了。如下圖所示:
當(dāng)我們選好了類名,向?qū)?huì)自動(dòng)給其他編輯框添上缺省值。這里我們使用它的缺省值,同時(shí)選中ActiveX Control Options的三個(gè)選項(xiàng),然后點(diǎn)OK 。
ActiveX 控制選項(xiàng)
上圖顯示了全部可以選的ActiveX控制選項(xiàng),Include Design-Time License選項(xiàng)表明設(shè)計(jì)時(shí)許可證將被創(chuàng)建,除非用戶有許可證,否則它可以防止控件在設(shè)計(jì)環(huán)境中被非法使用,。如果選擇了這個(gè)選項(xiàng),向?qū)?huì)產(chǎn)生一個(gè)LIC文件,里面包含了許可證信息。用戶必須有這個(gè).LIC文件才能在開發(fā)環(huán)境中使用相應(yīng)的ActiveX控件。Include Version Information選項(xiàng)使你可以在項(xiàng)目中添加象版權(quán)說明、版本號(hào)等版本信息。具體版本信息的設(shè)定可以在創(chuàng)建好項(xiàng)目后,選Project|Options然后切換到Version-Info頁來進(jìn)行。(注意: 盡管版本信息是一個(gè)可選的選項(xiàng),但如果你打算在Visual Basic 4.0及以上中使用Delphi生成的ActiveX控件,你必須選定它)。Include About Box選項(xiàng)表明你是否為ActiveX控件創(chuàng)建一個(gè)關(guān)于對(duì)話框。關(guān)于對(duì)話框是一個(gè)獨(dú)立的窗體單元,你可以編輯它來顯示你需要的版本信息或其他信息。關(guān)于信息是通過在開發(fā)環(huán)境中點(diǎn)擊About 屬性來調(diào)用的。
生成單元文件
一切設(shè)定好后,點(diǎn)OK按鈕。向?qū)紫葎?chuàng)建一個(gè)ActiveX Library 項(xiàng)目。接著創(chuàng)建一個(gè)實(shí)現(xiàn)單元,來實(shí)現(xiàn)原生的Delphi控件的COM封裝。封裝類是TActiveXControl一個(gè)簡(jiǎn)單的子類,然后是類型庫和類型庫接口單元(類型庫實(shí)際上是一個(gè)二進(jìn)制文件里面定義數(shù)據(jù)類型、接口、方法以及ActiveX Library要公開的對(duì)象。類型庫接口單元包含了對(duì)應(yīng)于類型庫中的信息Pascal的聲明)。最后是許可證文件,關(guān)于對(duì)話框和源碼文件。
編譯和注冊(cè)ActiveX控制
現(xiàn)在我們可以編譯ActiveX Library 項(xiàng)目來生成包含ActiveX控件的OCX文件。我們可以直接調(diào)用Run|Register Sever來注冊(cè)ActiveX Sever。然而在做之前,我建議保存全部的項(xiàng)目文件。如果你沒有先保存項(xiàng)目文件,那么儲(chǔ)存在注冊(cè)表中的路徑就是你當(dāng)前的路徑而不一定是你的項(xiàng)目的真正路徑。
注冊(cè)后我們就可以在Visual Basic等程序中使用Delphi生成的ActiveX控件了,如下圖示意:
ActiveX 轉(zhuǎn)換過程中的問題
雖然ListBoxX控件使我們能在Visual Basic中使用TListBox很多功能。但ListBoxX控件并沒有實(shí)現(xiàn)TListBox的全部功能。比如,TListBox的OnDrawItem的事件定義在ListBoxX中是找不到的?;叵胍幌略?/span>Delphi里我們經(jīng)常使用OnDrawItem事件來支持用戶自繪畫功能。沒有了這個(gè)事件,ActiveX版本的ListBox就無法支持這一特性即使Style屬性設(shè)定為lsOwnerDrawFixed。
要理解為什么這個(gè)事件沒有出現(xiàn)在ActiveX中,我們需要研究一下事件類型,特別是要研究一下傳遞給事件的參數(shù)類型。OnDrawItem聲明為TDrawItemEvent類型,定義如下:
TDrawItemEvent = procedure( Control: TWinControl;
Index: Integer; Rect: TRect;
State: TOwnerDrawState) of object;
我們看到OnDrawItem 事件處理函數(shù)接受4個(gè)參數(shù)。其中第三個(gè)參數(shù)阻止了事件出現(xiàn)在ActiveX Control中。因?yàn)?/span>OLE自動(dòng)化不知道如何整理(marshall)TRect類型的數(shù)據(jù)。
原因在于ActiveX 控制實(shí)際上是小型的OLE自動(dòng)化服務(wù)器。包含 ActiveX控件的ActiveX 容器使用自動(dòng)化來和控件通訊。因此要想使一個(gè)屬性、方法或事件出現(xiàn)在ActiveX版本的控制中,全部參數(shù)和返回類型必須是自動(dòng)化兼容的。表1列出了全部的兼容類型。
雖然在這個(gè)表中我們沒有列出但ActiveX轉(zhuǎn)換向?qū)н€可以正確處理TColor, TFont, TPicture, and TStrings屬性。Color 屬性可以很容易轉(zhuǎn)換因?yàn)樗鼘?shí)際上就是一個(gè)整數(shù)類型。然而對(duì)于其他屬性類型,Delphi提供了一個(gè)定制的接口來處理對(duì)應(yīng)的Delphi屬性。比如,IStrings 接口提供了處理TStrings屬性的途徑。
同時(shí),一個(gè)屬性是自動(dòng)化兼容的并不意味著它就一定會(huì)出現(xiàn)在ActiveX 控件中,向?qū)Р粫?huì)轉(zhuǎn)化對(duì)ActiveX無意義的屬性、方法或事件。下列屬性就沒有被轉(zhuǎn)化,比如:Height, HelpContext, Hint, Left, Name, ParentFont, ParentShowHint, PopupMenu, ShowHint, TabOrder, Tag, Top, and Width等。
此外,有兩種特殊類型的屬性向?qū)б膊粫?huì)轉(zhuǎn)化。對(duì)象引用和數(shù)據(jù)明了屬性。對(duì)象引用(就是指Delphi的某個(gè)屬性對(duì)應(yīng)于一個(gè)對(duì)象,比如TTable的Datasource實(shí)際上是引用了另外一個(gè)對(duì)象TDataSource)在Delphi里是通過指針來實(shí)現(xiàn)的因此是自動(dòng)化不兼容的。此外, ActiveX本身也沒有提供一個(gè)標(biāo)準(zhǔn)的方式來使一個(gè)控件同容器中的其他控件相關(guān)聯(lián)。而數(shù)據(jù)明了屬性DataSource和DataField沒有被映射到ActiveX控件中去是因?yàn)?/span>Delphi實(shí)現(xiàn)的數(shù)據(jù)明了采用了完全不同于ActiveX 的機(jī)制。當(dāng)然如果非常想利用Delphi中好用的數(shù)據(jù)庫控件,這也是可以的,但需要一些額外的工作,這已經(jīng)超出了本文的范圍,將不在這里介紹。必須指出的是ActiveX的數(shù)據(jù)控件和Delphi的數(shù)據(jù)控件是不一樣的。這也就是為什么沒有一個(gè)Delphi的數(shù)據(jù)明了控件出現(xiàn)在ActiveX轉(zhuǎn)換向?qū)У念惷斜碇械木壒省?/span>
正確的轉(zhuǎn)換的關(guān)鍵
到目前為止ListBoxX其他特性都沒問題,我們也可以把Style變成lsOwnerDrawFixed, 但是沒有OnDrawItem事件我們無法改變它的外觀顯示。讓我們?cè)囈环N不同的方法,這就是定義一個(gè)自動(dòng)化兼容的接口來實(shí)現(xiàn)自繪畫的外觀(Owner-Draw).例如我們可以創(chuàng)建一個(gè)新的事件OnColorItem使我們能改變每一個(gè)列表項(xiàng)的顏色。
添加新事件
添加新的事件到ActiveX中需要修改類型庫中的信息,可以有兩種方法:通過選擇 Edit|Add To Interface 菜單或用內(nèi)置的類型庫編輯器都可以,Edit|Add To Interface 菜單只有當(dāng)當(dāng)前文件為實(shí)現(xiàn)單元時(shí)才能使用。如下圖所示:
上圖顯示了OnColorItem 是如何加入到IListBoxXEvents 接口中的。定義一個(gè)ActiveX接口的新事件同Delphi中有點(diǎn)不同。不象Delphi中那樣給事件聲明一個(gè)屬性類比如TNotifyEvent, 你必須寫一個(gè)將會(huì)傳遞給事件處理函數(shù)的帶參數(shù)的過程。類似于寫一個(gè)控件中的事件分配方法。OnColorItem事件定義如下:
procedure OnColorItem(Index: Integer; var Color: Integer );
點(diǎn)OK按鈕,Delphi 就把這個(gè)聲明加入了類型庫。這使得我們可以給OnColorItem事件寫處理函數(shù)了。但是在這之前, 我們還必須實(shí)現(xiàn)產(chǎn)生事件的代碼,這有點(diǎn)類似于在Delphi控件中定義新的事件。生成事件屬性和事件分配方法僅僅是一部分工作。我們還必須在合適的時(shí)間調(diào)用事件分配方法來產(chǎn)生事件。最好的產(chǎn)生OnColorItem事件的位置就是在Delphi控件的OnDrawItem事件中,實(shí)現(xiàn)的方法見源碼清單1。
TListBoxX 封裝類定義了一個(gè)事件處理函數(shù)DrawItemEvent 來對(duì)嵌入的ListBox的OnDrawIem事件來響應(yīng),DrawItemEvent函數(shù)在InitializeControl方法中進(jìn)行初始化。同時(shí)DrawItemEvent函數(shù)負(fù)責(zé)產(chǎn)生OnColorItem事件。為了產(chǎn)生事件,要使用FEvents接口來激發(fā)OnColorItem事件過程。Index參數(shù)來自于DrawItemEvent參數(shù)列表,而Color參數(shù)是在DrawItemEvent里定義的。當(dāng)OnColorItem 事件返回時(shí), ItemColor 變量將包括缺省的顏色或用戶定義的顏色。然后ItemColor變量就被用來畫列表項(xiàng)。如下圖所示:
上圖顯示了ListBoxX控件在VB中的運(yùn)行狀況。雖然ActiveX不可能象在Delphi可以隨意引用Canvas來畫圖,但是這個(gè)例子證明了利用ActiveX控件的靈活性,我們還是可以作一些有趣的效果。
正如我們?cè)谏厦孢@個(gè)例子中看到的,實(shí)際上把Delphi控件轉(zhuǎn)化為ActiveX控件并不是很容易,這是由于我們轉(zhuǎn)換的是一個(gè)現(xiàn)成的Delphi控件,特別是這個(gè)控件使用了某些VCL的高級(jí)特性。從這個(gè)方面來說,Active控制框架不如VCL靈活,然而ActiveX是一個(gè)公認(rèn)的標(biāo)準(zhǔn),可以在除了Delphi外的其他環(huán)境中使用,犧牲靈活性意味著獲得了廣泛性。
結(jié)論
如果你想從頭開始創(chuàng)建一個(gè)ActiveX控件,首先你的控件必須從TWincontrol開始繼承,如果想要提供自繪畫的功能,必須從TCustomControl開始。另外,應(yīng)該使用自動(dòng)化兼容的數(shù)據(jù)類型來定義屬性、方法、參數(shù)。如果你想轉(zhuǎn)換一個(gè)已有的VCL控件變成ActiveX,注意一定要改變參數(shù)類型,否則方法,屬性等等是不會(huì)被轉(zhuǎn)換的。
源碼清單1 - ListBoxImpl.pas
unit ListBoxImpl;
interface
uses
Windows, ActiveX, Classes, Controls, Graphics, Menus, Forms,
StdCtrls, ComServ, StdVCL, AXCtrls, DelphiByDesignXLib_TLB;
type
TListBoxX = class(TActiveXControl, IListBoxX)
private
{ Private declarations }
FDelphiControl: TListBox;
FEvents: IListBoxXEvents;
procedure ClickEvent(Sender: TObject);
procedure DblClickEvent(Sender: TObject);
procedure KeyPressEvent(Sender: TObject; var Key: Char);
// Add a custom event handler for the OnDrawItem event
procedure DrawItemEvent( Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState );
protected
{ Protected declarations }
procedure InitializeControl; override;
procedure EventSinkChanged(const EventSink: IUnknown); override;
procedure DefinePropertyPages(
DefinePropertyPage: TDefinePropertyPage); override;
function Get_BorderStyle: TxBorderStyle; safecall;
function Get_Color: TColor; safecall;
function Get_Columns: Integer; safecall;
function Get_Ctl3D: WordBool; safecall;
function Get_Cursor: Smallint; safecall;
function Get_DragCursor: Smallint; safecall;
function Get_DragMode: TxDragMode; safecall;
function Get_Enabled: WordBool; safecall;
function Get_ExtendedSelect: WordBool; safecall;
function Get_Font: Font; safecall;
function Get_ImeMode: TxImeMode; safecall;
function Get_ImeName: WideString; safecall;
function Get_IntegralHeight: WordBool; safecall;
function Get_ItemHeight: Integer; safecall;
function Get_ItemIndex: Integer; safecall;
function Get_Items: IStrings; safecall;
function Get_MultiSelect: WordBool; safecall;
function Get_ParentColor: WordBool; safecall;
function Get_ParentCtl3D: WordBool; safecall;
function Get_SelCount: Integer; safecall;
function Get_Sorted: WordBool; safecall;
function Get_Style: TxListBoxStyle; safecall;
function Get_TabWidth: Integer; safecall;
function Get_TopIndex: Integer; safecall;
function Get_Visible: WordBool; safecall;
procedure AboutBox; safecall;
procedure Clear; safecall;
procedure Set_BorderStyle(Value: TxBorderStyle); safecall;
procedure Set_Color(Value: TColor); safecall;
procedure Set_Columns(Value: Integer); safecall;
procedure Set_Ctl3D(Value: WordBool); safecall;
procedure Set_Cursor(Value: Smallint); safecall;
procedure Set_DragCursor(Value: Smallint); safecall;
procedure Set_DragMode(Value: TxDragMode); safecall;
procedure Set_Enabled(Value: WordBool); safecall;
procedure Set_ExtendedSelect(Value: WordBool); safecall;
procedure Set_Font(const Value: Font); safecall;
procedure Set_ImeMode(Value: TxImeMode); safecall;
procedure Set_ImeName(const Value: WideString); safecall;
procedure Set_IntegralHeight(Value: WordBool); safecall;
procedure Set_ItemHeight(Value: Integer); safecall;
procedure Set_ItemIndex(Value: Integer); safecall;
procedure Set_Items(const Value: IStrings); safecall;
procedure Set_MultiSelect(Value: WordBool); safecall;
procedure Set_ParentColor(Value: WordBool); safecall;
procedure Set_ParentCtl3D(Value: WordBool); safecall;
procedure Set_Sorted(Value: WordBool); safecall;
procedure Set_Style(Value: TxListBoxStyle); safecall;
procedure Set_TabWidth(Value: Integer); safecall;
procedure Set_TopIndex(Value: Integer); safecall;
procedure Set_Visible(Value: WordBool); safecall;
end;
implementation
uses AboutListBox;
{ TListBoxX }
procedure TListBoxX.InitializeControl;
begin
FDelphiControl := Control as TListBox;
FDelphiControl.OnClick := ClickEvent;
FDelphiControl.OnDblClick := DblClickEvent;
FDelphiControl.OnKeyPress := KeyPressEvent;
//添加一個(gè)定制的事件來處理OnDrawItem事件
FDelphiControl.OnDrawItem := DrawItemEvent;
end;
procedure TListBoxX.EventSinkChanged(const EventSink: IUnknown);
begin
FEvents := EventSink as IListBoxXEvents;
end;
function TListBoxX.Get_Enabled: WordBool;
begin
Result := FDelphiControl.Enabled;
end;
procedure TListBoxX.Set_Enabled(Value: WordBool);
begin
FDelphiControl.Enabled := Value;
end;
procedure TListBoxX.DrawItemEvent( Control: TWinControl;
Index: Integer; Rect: TRect; State: TOwnerDrawState );
var
ItemColor: Integer;
begin
ItemColor := Integer(FDelphiControl.Font.Color);
//產(chǎn)生OnColorItem ActiveX事件
if FEvents <> nil then
FEvents.OnColorItem( Index, ItemColor );
//用ItemColor畫列表項(xiàng)
with FDelphiControl do
begin
if not ( odSelected in State ) then
Canvas.Font.Color := ItemColor;
Canvas.TextRect(Rect, Rect.Left + 2, Rect.Top, Items[Index]);
Canvas.Font.Color := FDelphiControl.Font.Color;
end;
end;
initialization
TActiveXControlFactory.Create(
ComServer,
TListBoxX,
TListBox,
Class_ListBoxX,
1,
'{B19A64E4-644D-11D1-AE4B-444553540000}',
0);
end.
聯(lián)系客服