深入分析基於VCL派生的ActiveX控件的實現原理及應用

深入分析基於VCL派生的ActiveX控件的實現原理及應用


Aweay<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

你可轉載,拷貝,但必須加入作者署名Aweay,如果用於商業目的,必須經過作者同意。

前言

這篇文章雖然是以VCL爲題,但卻是基於BCB的,也就是說是在VCL基礎上使用ATL實現的ActiveX的原理分析,如果你是Delphi程序員,這篇文章可能不適合你,不過作者如果有時間會再寫一篇“Delphi版的深入分析”,本篇文章比較深入的分析了VCL實現ActiveX控件的原理、事件機制、屬性頁和ActiveX控件編寫的相關知識。希望大家已經掌握了VCL編寫元件(Component)的知識,COM原理及ATL/模板的相關知識,因爲作者不會對文章中探討的相關知識做詳細介紹,所以你可能因爲缺乏相應知識而遇到一些困難,不過作者會盡量用簡單的語言來闡述一切。另,作者本來水平有限,在寫這篇文章的時候僅是參考了幫助文檔、分析了VCL源代碼再加上一些猜測,如果有任何理解錯誤敬請大家指教,好我們開始吧!

從嚮導開始


爲了有分析的對象,我們就從最基本的開始,由嚮導從TButton派生一個TButtonX的ActiveX控件(以下出現TButtonX都是指ActiveX控件),注意在嚮導中選中“產生關於對話框”的選項,這樣生成的TButtonX會有一個關於對話框。現在我們得到了ButtonImpl,ButtonXContrl_TLB這兩個主要的單元,所有的奧祕也在這裏,我們進入看看。

ButtonImpl.h文件中,有這樣的申明:

class ATL_NO_VTABLE TButtonXImpl:
VCLCONTROL_IMPL(TButtonXImpl, ButtonX, TButton, IButtonX, DIID_IButtonXEvents)
{

}



這個TButtonXImpl實現了TButtonX,一般情況下針對ActiveX的工作都可以在這個單元內完成,不過這個類看起來還是很奇怪:首先ATL_NO_VTABLE是個什麼東西?這是一個宏,經過豫編譯處理後,這個宏最終替換爲__declspec(novtable),而這又是一個編譯器指示字,用於指示編譯器不要產生vtable(準確的說,是不初始化vtable指針,這樣連接器可以排除那些需要vtable才能調用的函數,比如虛函數),這又爲什麼?
我們知道COM是語言無關的,但是vtable的機制不是所有的語言都有,比如VB等,而我們編寫ActiveX控件肯定是要拿到這些開發工具上使用的,所以爲了使得C++開發的COM元件可以在VB下使用,我們不能使用vtable機制,而且ActiveX還要是自動化對象,這在後面再討論。

還有一個宏VCLCONTROL_IMPL,這個宏是關鍵,它隱藏了VCL實現ActiveX控件的全部奧祕,看來必須分析它才能撥開全部迷霧:

VCLCONTROL_IMPL 宏,封裝了ActiveX控件繼承的基類,展開後:

#define VCLCONTROL_IMPL(cppClass, CoClass, VclClass, intf, EventID) /
public TVclControlImpl<cppClass, VclClass, &CLSID_##CoClass, /
&IID_##intf, &EventID, LIBID_OF_##CoClass>,/
public IDispatchImpl<intf, &IID_##intf, LIBID_OF_##CoClass>, /
public TEvents_##CoClass<cppClass>



cppClass是C++實現的類的名字,
CoClass是ActiveX的類名,
VclClass是ActiveX控件繼承的VCL基類的名字,
intf是ActiveX控件實現的IDispatch接口,
EventID是ActiveX控件事件接口的標示符。

TVclControlImpl它封裝了VclClass指定的VCL類,通過它的窗口管理和消息處理機制,使得它可以工作在ActiveX的宿主環境下。TVclControlImpl實現了標準的ActiveX控件需要的接口,因爲它間接繼承自CComObjectRootEx和CComCoClass,使得它可以工作在C++ Builder的基與ATL的COM應用程序中。

IDispatchImpl,它實現了IDispatch接口,所以從這個類派生的子類的屬性和方法可以被自動化(Automation)操作。上面說了ActiveX必須是自動化對象,而自動化對象必須繼承自IDispatch接口,這裏正好說明這一點。

TEvents_##CoClass(或者TEvents_CoClassName),提供了VCL控件的事件觸發機制,IDE會根據創建的VCL控件自動產生這個類,通過這個類,ActiveX控件可以在事件觸發的時候調用相應的方法來處理這些事件。

注意上面的##的編譯器指示字,它是用來連接2個宏參數的,比如TEvents_##CoClass會被替換爲TEvents_ButtonX,這也是一個類,不過是用IDE自動產生的,用於支持事件機制。

而上面展開代碼的關鍵在於TVclControlImpl這個類,我們再看看它:

template <class T, // User class implementing Control
class TVCL, // Underlying VCL type used in One-Step Conversion
const CLSID* pclsid, // Class ID of Control
const IID* piid, // Primary interface of Control
const IID* peventsid, // Event (outgoing) interface of Control
const GUID* plibid> // GUID of TypeLibrary
class ATL_NO_VTABLE TVclControlImpl:
public CComObjectRootEx<CComObjectThreadModel>,
public CComCoClass<T, pclsid>,
public TVclComControl<T, TVCL>,
public IProvideClassInfo2Impl<pclsid, peventsid, plibid>,
public IPersistStorageImpl<T>,
public IPersistStreamInitImpl<T>,
public IQuickActivateImpl<T>,
public IOleControlImpl<T>,
public IOleObjectImpl<T>,
public IOleInPlaceActiveObjectImpl<T>,
public IViewObjectExImpl<T>,
public IOleInPlaceObjectWindowlessImpl<T>,
public IDataObjectImpl<T>,
public ISpecifyPropertyPagesImpl<T>,
public IConnectionPointContainerImpl<T>,
public IPropertyNotifySinkCP<T, CComDynamicUnkArray>,
public ISupportErrorInfo,
public ISimpleFrameSiteImpl<T>
{

}


可以看到,這個類實現了所有ActiveX控件必要實現的接口,除此之外,這個類也是VCL和ATL轉換的關鍵,他有很多關鍵的方法,比如:

HRESULT OnDraw(ATL_DRAWINFO& di)
{
try
{
if (m_VclCtl)
m_VclCtl->PaintTo(di.hdcDraw, di.prcBounds->left, di.prcBounds->top);
}
catch (Exception& e)
{
return (static_cast<T*>(this))->Error(e.Message.c_str());
}
return S_OK;
}



這個方法可以把VCL元件的界面畫在ActiveX宿主窗體上。

經過層層撥絲,我們現在終於搞明白了TButtonXImpl的實現框架,但ActiveX運作的原理和如何同VCL交互的還是不清楚,好,我們現在再來看看,TButtonXImpl的實現代碼:

void __fastcall ClickEvent(TObject *Sender);
void __fastcall KeyPressEvent(TObject *Sender, char &Key);
public:
TVclControlImpl
void InitializeControl()
{
m_VclCtl->OnClick = ClickEvent;
m_VclCtl->OnKeyPress = KeyPressEvent;
}
BEGIN_COM_MAP(TButtonXImpl)
VCL_CONTROL_COM_INTERFACE_ENTRIES(IButtonX)
END_COM_MAP()
DECLARE_VCL_CONTROL_PERSISTENCE(TButtonXImpl, TButton);
DECLARE_ACTIVEXCONTROL_REGISTRY("ButtonXControl.ButtonX", 1);
protected:
STDMETHOD(_set_Font(IFontDisp** Value));
STDMETHOD(AboutBox());
STDMETHOD(DrawTextBiDiModeFlagsReadingOnly(long* Value));
...



又是很多的宏,不過作者不打算介紹了,他們在BCB生成的代碼註釋(這裏被刪除)裏解釋的很清楚了,大家可以自己看,我就提示一點,很多朋友問:如何改變ActiveX控件的圖標?更改這個


DECLARE_ACTIVEXCONTROL_REGISTRY(“ButtonXControl.ButtonX”, 1);


宏的參數就可以了,比如你已經將圖標資源(Bitmap),加入工程,並且這個資源ID爲2,則你可以這樣更改:


DECLARE_ACTIVEXCONTROL_REGISTRY(“ButtonXControl.ButtonX”, 2);


再看,在protected下有很多屬性、方法的申明,在cpp文件中,這些申明也得到了實現,但問題在於爲什麼是保護類型的?這樣ActiveX控件豈不是訪問不到這些屬性、方法?申明瞭又有什麼用?
是否還記得TButtonXImpl繼承了IButtonX接口呢?我們現在要到那裏去看看,爲此我們要分析一下ButtonXContrl_TLB單元,這個單元文件是由IDE維護的,一般情況是不需要理會這個文件的內容,Borland也不建議你更改這個文件,不過今天我們必須要跨入禁區了,於是就有了IButtonX的實現代碼:

interface IButtonX : public IDispatch
{
public:
virtual HRESULT STDMETHODCALLTYPE get_Cancel(VARIANT_BOOL* Value/*[out,retval]*/) = 0; // [1]
virtual HRESULT STDMETHODCALLTYPE set_Cancel(VARIANT_BOOL Value/*[in]*/) = 0; // [1]
virtual HRESULT STDMETHODCALLTYPE get_Caption(BSTR* Value/*[out,retval]*/) = 0; // [-518]
...
#if !defined(__TLB_NO_INTERFACE_WRAPPERS)

VARIANT_BOOL __fastcall get_Cancel(void)
{
VARIANT_BOOL Value;
OLECHECK(this->get_Cancel((VARIANT_BOOL*)&Value));
return Value;
}
...
__property VARIANT_BOOL Cancel = {read = get_Cancel, write = set_Cancel};
__property BSTR Caption = {read = get_Caption};
__property VARIANT_BOOL Default = {read = get_Default, write = set_Default};
__property short DragCursor = {read = get_DragCursor, write = set_DragCursor};
...
}


可以看到IButtonX同樣繼承自IDispatch接口,所以這也是一個自動化的接口,而且終於有了public,所以那些接口方法和屬性被公開了,我們不難得出這樣一張類的佈局圖:

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />

另外請大家注意:

VARIANT_BOOL __fastcall get_Default(void)
{
VARIANT_BOOL Value;
OLECHECK(this->get_Default((VARIANT_BOOL*)&Value));
return Value;
}



上面的實現代碼,OLECHECK用於檢查函數的執行結果,如果有錯誤,那麼還有一個機會去處理錯誤。
方法和屬性都有了,對於一個ActiveX控件還差事件,沒有事件支持的ActiveX控件就像一個沒有發條的鐘是不會動的,下面,我們再來看一下VCL是如何實現ActiveX控件的事件機制的。

事件機制

還是上面的代碼:

void __fastcall ClickEvent(TObject *Sender);
void __fastcall KeyPressEvent(TObject *Sender, char &Key);
public:
TVclControlImpl
void InitializeControl()
{
m_VclCtl->OnClick = ClickEvent;
m_VclCtl->OnKeyPress = KeyPressEvent;
}
...



看起來像是事件的處理代碼,啊?好像?有沒有搞錯?沒有搞錯,確實是好像,而且是表面的


m_VclCtl->OnClick = ClickEvent;
m_VclCtl->OnKeyPress = KeyPressEvent;


是標準的VCL消息處理函數機制,m_VclCtl通過模板參數最終對應於相應的VCL原類,這樣m_VclCtl的OnClick事件的處理就會轉交給ClickEvent函數,而OnKeyPress事件的處理也就交給了KeyPressEvent函數處理,有VCL經驗的人,都能猜到ClickEvent和KeyPressEvent函數是如何實現的,例如:


void __fastcall TButtonXImpl::KeyPressEvent(TObject *Sender, char &Key)
{
short TempKey;
TempKey = (short)Key;
Fire_OnKeyPress(&TempKey);
Key = (short)TempKey;
}


又跳轉了,忽略Sender參數,然後把Key又傳遞給了Fire_OnKeyPress函數處理,爲了保證VCL KeyPress事件的結構,Key參數先被保存到TempKey,然後傳遞,最後返回Key參數,注意:TempKey參數可能在ActiveX事件處理中被修改,這也符合Visual Basic的KeyPress事件結構。
不過問題在於Fire_OnKeyPress函數是從哪裏來的?要搞清楚這個問題,我們還要看看前面那個複雜的宏定義:

#define VCLCONTROL_IMPL(cppClass, CoClass, VclClass, intf, EventID) /
public TVclControlImpl<cppClass, VclClass, &CLSID_##CoClass, /
&IID_##intf, &EventID, LIBID_OF_##CoClass>,/
public IDispatchImpl<intf, &IID_##intf, LIBID_OF_##CoClass>, /
public TEvents_##CoClass<cppClass>



其中,TEvents_##CoClass(或者TEvents_CoClassName),提供了VCL控件的事件觸發機制,IDE會根據創建的VCL控件自動產生這個類,通過這個類,ActiveX控件可以在事件觸發的時候調用相應的方法來處理這些事件。

注意上面的##的編譯器指示字,它是用來連接2個宏參數的,比如TEvents_##CoClass會被替換爲TEvents_ButtonX,這也是一個類,不過是用IDE自動產生的,用於支持事件機制。
所以所有的事件奧祕都應該隱藏在這個TEvents_ButtonX類裏,如果你夠大膽的話,你可以猜測那個Fire_OnKeyPress函數就在這個類裏?再次跨越禁區,我們得到代碼:

template <class T>
class TEvents_ButtonX : public IConnectionPointImpl<T,
&DIID_IButtonXEvents,
CComUnkArray<CONNECTIONPOINT_ARRAY_SIZE> >
{
public:
void Fire_OnClick(void);
void Fire_OnKeyPress(short* Key);
void Fire_OnMouseMove(int Button, int X, int Y);
protected:
IButtonXEventsDisp m_EventIntfObj;
};



看到了確實如此,我們再來看看Fire_OnKeyPress是如何實現的?

template <class T> void
TEvents_ButtonX<T>::Fire_OnKeyPress(short* Key)
{
T * pT = (T*)this;
pT->Lock();
IUnknown ** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
m_EventIntfObj.Attach(*pp);
m_EventIntfObj.OnKeyPress(Key);
m_EventIntfObj.Attach(0);
}
pp++;
}
pT->Unlock();
}



剔除不必要的多線程訪問互斥代碼、對控件數組事件的支持代碼,關鍵在於:
m_EventIntfObj.OnKeyPress(Key);
看來我們又要跳轉了,最後來到:

template <class T> void __fastcall
IButtonXEventsDispT<T>::OnKeyPress(short* Key/*[in,out]*/)
{
_TDispID _dispid(/* OnKeyPress */ DISPID(8));
TAutoArgs<1> _args;
_args[1] = Key /*[VT_I2:1]*/;
OleProcedure(_dispid, _args);
}



至此,如果有ATL/COM知識的人,都可以看出來這是一套標準的OLE方法調用機制,如果你還想跟蹤下去,你會發現它就是調用IDispatch接口的Invoke方法來負責方法、屬性的調用的,不過這裏還可以注意一下:
_TDispID _dispid(/* OnKeyPress */ DISPID(8));
這裏DISPID(8)是接口方法的標識符,這個值來自於你設計IButtonXEvents接口時定義的ID號,所以Invoke方法會唯一的定位到這個方法來完成事件機制的最後一步:調用客戶代碼-也就是你在VB中提供的事件的代碼。
於是我們又得到了這樣一幅消息、事件流向圖:



VCL就是這樣一步一步實現ActiveX控件的事件機制的,可以看出來,他的實現還是挺麻煩的,不過考慮到COM原理本來的複雜性,這樣的實現複雜度還是可以接受的。


簡單的測試


由控件嚮導生成的TButtonX代碼,不需要任何改動,直接編譯就會產生一個TButtonX ActiveX控件,我們現在測試一下,點Register Active Server菜單,註冊這個控件,然後在開啓Visual Basic開發環境,加入剛纔註冊的控件,發現它確實是按照我們的設計工作的,注意:如果你在開始創建這個控件的時候,選擇了生成About對話框的選項,那麼還有一個About屬性用於顯示關於對話框。
那麼這個關於對話框又是怎麼回事?代碼爲我們展示這點:

void ShowButtonXAbout(void)
{
TButtonXAbout* Form;
Form = new TButtonXAbout(NULL);
try
{
Form->ShowModal();
}
catch(...)
{
Form->Free();
return;
}
Form->Free();
}



在TButtonImpl的AboutBox函數中調用了上面的函數來顯示對話框,不過有一點作者也不太清楚,就是:


Form->Free();


本來按照Borland的說法,在BCB中不推薦使用Free來釋放內存,而應該使用delete這個關鍵字,但這裏爲什麼這樣使用?不過作者做了測試,使用delete也是可以的,沒有發現什麼問題,所以作者猜測,這裏可能是Borland沒有更新那個代碼嚮導以適應BCB的開發(可能本來是爲Delphi設計的,而Borland只是簡單的做了一下Delphi到BCB的轉換)。
最後需要說明的是AboutBox函數的DISPID必須是-552,這樣ActiveX會把這個函數作爲About來對待,其實DISPID的設置還是有強制性,很多標準屬性必須是特定的DISPID,這些DISPID都是負值,有興趣的朋友可以看看MSDN或者COM原理的書籍。


重用 繼承?


上面的TButtonX控件簡單的通過繼承VCL的TButton的實現了一個按鈕的AcitiveX控件,如果是其他的VCL能不能同樣這樣簡單的繼承就可以方便的生成ActiveX控件呢?在問答前,我們先做一個試驗,把剛纔這個TButtonX放在Visual Basic開發環境中,然後使用Spy++這樣工具(這裏作者使用作者自己開發的MySpy,可以到http://siney.yeah.net下載)看看他的類名:



發現它的類名並不是ActiveX的控件的TButtonX,而是原來VCL的TButton,這能說明什麼?這從一個側面說明了,在BCB下我們開發ActiveX控件其實就是設計相應的VCL控件,而在最後把他再封裝成爲ActiveX控件,那麼到底什麼樣的VCL控件都可以封裝成爲ActiveX控件呢?通常來說只要從TWinControl繼承的VCL元件都可以封裝成爲ActiveX。而這樣VCL元件具有如下特徵:
? 可以獲得焦點
? 可以包含其他控件(僅是具有這樣能力,不代表一定具備)
? 擁有窗口句柄
還記得這段代碼嗎:

HRESULT OnDraw(ATL_DRAWINFO& di)
{
try
{
if (m_VclCtl)
m_VclCtl->PaintTo(di.hdcDraw, di.prcBounds->left, di.prcBounds->top);
}
catch (Exception& e)
{
return (static_cast<T*>(this))->Error(e.Message.c_str());
}
return S_OK;
}


前面說這是BCB實現ActiveX機制的關鍵類TVclControlImpl所具有的代碼,用於把控件畫在ActiveX宿主窗體上,而這個方法就是來源於TWinControl,所以ActiveX控件的必須繼承自這個類(當然如果是自己實現了,就另當別論),這樣,很容易想到的是像TLabel這樣的VCL控件是無法實現爲ActiveX控件的。
那麼是不是從TWinControl繼承的VCL元件都可以被封裝爲ActiveX呢?這也不一定,如果你已經把相應的的unit添加進入工程或者已經把它安裝了,則這個VCL元件可能不會出現在那個DropDown 列表裏,還有就是這個類沒有用RegisterNonActiveX函數註冊,這個函數專門用來設置那些類不能被封裝爲ActiveX,這個函數很複雜:


extern PACKAGE void __fastcall RegisterNonActiveX(System::TMetaClass*,
const * ComponentClasses,
const int ComponentClasses_Size,
TActiveXRegType AxRegType);


具體的使用方法可以參考幫助。在CSDN上和Borland新聞組作者也看到過網友詢問“爲什麼我的控件不能出現在AcitveX的生成嚮導裏”,希望下次看過這篇文章的朋友下次可以解決這個問題。


再完善一些


再回到剛纔Visual Basic的開發環境,我們來看看到底那些屬性和方法被表露了:
而同樣的TButton在BCB中卻具有非常多的屬性,爲什麼呢?開始作者認爲所有的VCL元件都是表露這些基本的屬性和方法,但後來作者又做了一個試驗,就是簡單的封裝了TEdit後發現他表露其他的更多屬性和方法,參考了一下幫助文檔,發現原來有如下規則:

  • 數據感知屬性不表露。

  • 任何與自動化不兼容的類型定義不表露。

  • 可以表露在VCL中未發佈的屬性,但這樣做不保證持久性(persist)。


如果VCL的屬性或方法不符合上述規則,我們就需要自己實現相應ActiveX代碼來表露他們;相反如果符合上述規定,而你又不想表露給最終用戶,你可以在Type Library Editor中刪除它們,刷新代碼後再刪除單元文件中相應的生成代碼。
這裏需要強調的是:在BCB中設計ActiveX控件在很大程度上是先設計對應的VCL後再封裝爲ActiveX,而不是像VC那樣直接開發ActiveX(其實BCB也可以像VC那樣開發ActiveX,畢竟都是使用ATL嘛),這樣設計不管是從難易程度還是調試都是非常輕鬆的。
知道了原理,我們現在要做的是在這個半成品的TButtonX中加入新的事件和屬性頁,使它看起來更像一個專業的ActiveX控件,對於屬性和方法,因爲編寫這些代碼非常簡單,爲了節省篇幅,這裏就不實際添加屬性和方法了。
從TButton封裝得到的AcitiveX缺少了一個重要的事件,OnMouseMove,下面我們就寫代碼來表露這個事件,根據我們上面講述的原理,完成這部分很容易,首先就是在Type Library Editor裏的事件支持接口添加相應方法,如圖:



刷新後IDE自動產生相應代碼,在Impl單元文件中,加入VCL的OnMouseMove消息處理的轉移代碼,如下黑體部分:

class ATL_NO_VTABLE TButtonXImpl:
VCLCONTROL_IMPL(TButtonXImpl, ButtonX, TButton, IButtonX, DIID_IButtonXEvents)
{
void __fastcall ClickEvent(TObject *Sender);
void __fastcall KeyPressEvent(TObject *Sender, char &Key);
void __fastcall MouseMoveEvent(TObject *Sender, TShiftState Shift, int X,
int Y);
public:

void InitializeControl()
{
m_VclCtl->OnClick = ClickEvent;
m_VclCtl->OnKeyPress = KeyPressEvent;
m_VclCtl->OnMouseMove = MouseMoveEvent;
}



這裏的m_VclCtl其實就是TButton,他是通過模板參數替換的,所以通過上面的代碼TButton的OnMouseMove消息的處理過程轉向了MouseMoveEvent,現在我們最後的工作就是編寫MouseMoveEvent這個函數處理OnMouseMove消息:

void __fastcall TButtonXImpl::MouseMoveEvent(TObject *Sender,
TShiftState Shift, int X, int Y)
{
int ss=0;
if(Shift.Contains(ssLeft))
ss=1;
else if(Shift.Contains(ssRight))
ss=2;
Fire_OnMouseMove(ss,X,Y);
}



忽略Sender參數後,再次轉發消息流,Fire_OnMouseMove這個函數是由IDE自動產生的,我們直接調用就可以了,它的代碼如下:

template <class T> void
TEvents_ButtonX<T>::Fire_OnMouseMove(int Button, int X, int Y)
{
T * pT = (T*)this;
pT->Lock();
IUnknown ** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
m_EventIntfObj.Attach(*pp);
m_EventIntfObj.OnMouseMove(Button, X, Y);
m_EventIntfObj.Attach(0);
}
pp++;
}
pT->Unlock();
}



可以看到這部分代碼與缺省的Fire_OnClick是一致的,這樣添加OnMouseMove事件支持的代碼就完成了。
接下來是添加一個屬性頁,由於VCL的封裝,使得開發設計屬性頁變得非常簡單,首先生成一個新的Property Page,這樣BCB會爲我們產生一個Form,這個Form與普通的Win32開發中Form的最大區別在於它繼承自TPropertyPage,所以它有一些獨有的方法是我們在設計需要注意的,爲了簡單起見,屬性頁裏只簡單地更改、反饋Caption屬性,在實際開發中複雜的屬性頁是類似的。
屬性頁與ActiveX交互就是通過2個函數來進行的,而這兩個函數都是來自於TPropertyPage類,在缺省生成的Property Page Form裏已經加入了這個兩個函數的缺省代碼,我們要做就是完成這2個函數:
UpdatePropertyPage(void),在打開屬性頁時,系統會調用這個函數,你可以在這個函數裏添加代碼用以反映ActiveX的屬性在屬性頁裏的顯示。
UpdateObject(void),在應用屬性時,系統會調用這個函數,你可以把屬性頁中改變的數據應用到實際的ActiveX控件中。
按照BCB的幫助這兩個函數實現起來非常簡單,比如UpdateObject(void),就只需要如下代碼:


void __fastcall TPropertyPage1::UpdateObject(void)
{
OleObject.OlePropertySet<WideString>("EditMask", WideString(InputMast->Text).Copy());
}


就可以把ActiveX控件的屬性更改爲屬性頁中設置的數據,但在作者的計算機上(BCB6+sp4),上面的代碼怎麼都無法通過編譯,無奈之下,作者只好使用原始的方法,代碼如下:

void __fastcall TpageNormal::UpdatePropertyPage(void)
{
// Update your controls from OleObjects
IDispatch* ctrl=OleObject;
CComPtr<IButtonX> btnctrl;
ctrl->QueryInterface<IButtonX>(&btnctrl);
edtcaption->Text=String(btnctrl->get_Caption());
}
//---------------------------------------------------------------------------
void __fastcall TpageNormal::UpdateObject(void)
{
// Update OleObjects from your controls
IDispatch* ctrl=OleObject;
CComPtr<IButtonX> btnctrl;
ctrl->QueryInterface<IButtonX>(&btnctrl);
btnctrl->set_Caption(WideString(edtcaption->Text));
}


對於沒有ATL、COM知識的讀者上面的代碼可能比較難懂,建議找些相關書籍熟悉一下。
通過上面的代碼,ActiveX控件和屬性頁之間就可以完美的交互了,最後在ButtonImpl.h文件中加入如下宏映:


BEGIN_PROPERTY_MAP(TButtonXImpl)
PROP_PAGE(CLSID_pageNormal)
END_PROPERTY_MAP()


關於這些宏的說明和原理因篇幅關係這裏就不討論了,BCB的幫助和源碼註釋裏都寫的很清楚,感興趣的朋友可以自己研究一下。至於上面的CLSID_pageNormal是生成屬性頁時,IDE自動爲改屬性頁生成的ClassID。
這樣一個比較完善的ActiveX控件就寫完了,是不是非常簡單,詳細代碼可以到作者網站下載。


寫在最後


由於篇幅關係,本來很多內容可以展開詳細討論,但作者都省略了,本文就當拋磚引玉,感興趣的讀者可以再深入研究。作者想再次強調的是,不管開發ActiveX控件還是Active Form,最好的方法都是封裝(或者轉換)爲ActiveX,而不是一切從頭來,比如你有一個工程,想以ActiveForm的形式用在Web上,那麼最好的方法是拿出單獨的Form然後轉換爲ActiveForm,這不需要多複雜的代碼,這也是Borland新聞組上專家給的建議。
由於編寫ActiveX控件的調試複雜性,所以保證代碼質量非常重要,除此之外就是善於利用一些工具來幫助調試,比如Visual Basic、ActiveX Control Test Container等工具,如果有機會作者會寫一些實際開發中可能遇到的調試問題和經驗。
參考文獻:

  • Borland C++ Builder5 Help Document

  • Borland VCL Source and Comment

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章