翻譯|其它|編輯:郝浩|2004-01-12 21:31:00.000|閱讀 1779 次
概述:
# 界面/圖表報(bào)表/文檔/IDE等千款熱門(mén)軟控件火熱銷(xiāo)售中 >>
前言
這篇文章雖然是以VCL為題,但卻是基于BCB的,也就是說(shuō)是在VCL基礎(chǔ)上使用ATL實(shí)現(xiàn)的ActiveX的原理分析,如果你是Delphi程序員,這篇文章可能不適合你,不過(guò)作者如果有時(shí)間會(huì)再寫(xiě)一篇“Delphi版的深入分析”,本篇文章比較深入的分析了VCL實(shí)現(xiàn)ActiveX控件的原理、事件機(jī)制、屬性頁(yè)和ActiveX控件編寫(xiě)的相關(guān)知識(shí)。希望大家已經(jīng)掌握了VCL編寫(xiě)元件(Component)的知識(shí),COM原理及ATL/模板的相關(guān)知識(shí),因?yàn)樽髡卟粫?huì)對(duì)文章中探討的相關(guān)知識(shí)做詳細(xì)介紹,所以你可能因?yàn)槿狈ο鄳?yīng)知識(shí)而遇到一些困難,不過(guò)作者會(huì)盡量用簡(jiǎn)單的語(yǔ)言來(lái)闡述一切。另,作者本來(lái)水平有限,在寫(xiě)這篇文章的時(shí)候僅是參考了幫助文檔、分析了VCL源代碼再加上一些猜測(cè),如果有任何理解錯(cuò)誤敬請(qǐng)大家指教,好我們開(kāi)始吧!
從向?qū)ч_(kāi)始
為了有分析的對(duì)象,我們就從最基本的開(kāi)始,由向?qū)腡Button派生一個(gè)TButtonX的ActiveX控件(以下出現(xiàn)TButtonX都是指ActiveX控件),注意在向?qū)е羞x中“產(chǎn)生關(guān)于對(duì)話框”的選項(xiàng),這樣生成的TButtonX會(huì)有一個(gè)關(guān)于對(duì)話框。現(xiàn)在我們得到了ButtonImpl,ButtonXContrl_TLB這兩個(gè)主要的單元,所有的奧秘也在這里,我們進(jìn)入看看。
在ButtonImpl.h文件中,有這樣的申明:
class ATL_NO_VTABLE TButtonXImpl:
VCLCONTROL_IMPL(TButtonXImpl, ButtonX, TButton, IButtonX, DIID_IButtonXEvents)
{
…
}
這個(gè)TButtonXImpl實(shí)現(xiàn)了TButtonX,一般情況下針對(duì)ActiveX的工作都可以在這個(gè)單元內(nèi)完成,不過(guò)這個(gè)類看起來(lái)還是很奇怪:首先ATL_NO_VTABLE是個(gè)什么東西?這是一個(gè)宏,經(jīng)過(guò)豫編譯處理后,這個(gè)宏最終替換為_(kāi)_declspec(novtable),而這又是一個(gè)編譯器指示字,用于指示編譯器不要產(chǎn)生vtable(準(zhǔn)確的說(shuō),是不初始化vtable指針,這樣連接器可以排除那些需要vtable才能調(diào)用的函數(shù),比如虛函數(shù)),這又為什么?
我們知道COM是語(yǔ)言無(wú)關(guān)的,但是vtable的機(jī)制不是所有的語(yǔ)言都有,比如VB等,而我們編寫(xiě)ActiveX控件肯定是要拿到這些開(kāi)發(fā)工具上使用的,所以為了使得C++開(kāi)發(fā)的COM元件可以在VB下使用,我們不能使用vtable機(jī)制,而且ActiveX還要是自動(dòng)化對(duì)象,這在后面再討論。
還有一個(gè)宏VCLCONTROL_IMPL,這個(gè)宏是關(guān)鍵,它隱藏了VCL實(shí)現(xiàn)ActiveX控件的全部奧秘,看來(lái)必須分析它才能撥開(kāi)全部迷霧:
VCLCONTROL_IMPL 宏,封裝了ActiveX控件繼承的基類,展開(kāi)后:
#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++實(shí)現(xiàn)的類的名字,
CoClass是ActiveX的類名,
VclClass是ActiveX控件繼承的VCL基類的名字,
intf是ActiveX控件實(shí)現(xiàn)的IDispatch接口,
EventID是ActiveX控件事件接口的標(biāo)示符。
TVclControlImpl它封裝了VclClass指定的VCL類,通過(guò)它的窗口管理和消息處理機(jī)制,使得它可以工作在ActiveX的宿主環(huán)境下。TVclControlImpl實(shí)現(xiàn)了標(biāo)準(zhǔn)的ActiveX控件需要的接口,因?yàn)樗g接繼承自CComObjectRootEx和CComCoClass,使得它可以工作在C++ Builder的基與ATL的COM應(yīng)用程序中。
IDispatchImpl,它實(shí)現(xiàn)了IDispatch接口,所以從這個(gè)類派生的子類的屬性和方法可以被自動(dòng)化(Automation)操作。上面說(shuō)了ActiveX必須是自動(dòng)化對(duì)象,而自動(dòng)化對(duì)象必須繼承自IDispatch接口,這里正好說(shuō)明這一點(diǎn)。
TEvents_##CoClass(或者TEvents_CoClassName),提供了VCL控件的事件觸發(fā)機(jī)制,IDE會(huì)根據(jù)創(chuàng)建的VCL控件自動(dòng)產(chǎn)生這個(gè)類,通過(guò)這個(gè)類,ActiveX控件可以在事件觸發(fā)的時(shí)候調(diào)用相應(yīng)的方法來(lái)處理這些事件。
注意上面的##的編譯器指示字,它是用來(lái)連接2個(gè)宏參數(shù)的,比如TEvents_##CoClass會(huì)被替換為T(mén)Events_ButtonX,這也是一個(gè)類,不過(guò)是用IDE自動(dòng)產(chǎn)生的,用于支持事件機(jī)制。
而上面展開(kāi)代碼的關(guān)鍵在于TVclControlImpl這個(gè)類,我們?cè)倏纯此?/p>
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>
{
…
}
可以看到,這個(gè)類實(shí)現(xiàn)了所有ActiveX控件必要實(shí)現(xiàn)的接口,除此之外,這個(gè)類也是VCL和ATL轉(zhuǎn)換的關(guān)鍵,他有很多關(guān)鍵的方法,比如:
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;
}
這個(gè)方法可以把VCL元件的界面畫(huà)在ActiveX宿主窗體上。
經(jīng)過(guò)層層撥絲,我們現(xiàn)在終于搞明白了TButtonXImpl的實(shí)現(xiàn)框架,但ActiveX運(yùn)作的原理和如何同VCL交互的還是不清楚,好,我們現(xiàn)在再來(lái)看看,TButtonXImpl的實(shí)現(xiàn)代碼:
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));
...
又是很多的宏,不過(guò)作者不打算介紹了,他們?cè)贐CB生成的代碼注釋(這里被刪除)里解釋的很清楚了,大家可以自己看,我就提示一點(diǎn),很多朋友問(wèn):如何改變ActiveX控件的圖標(biāo)?更改這個(gè)
DECLARE_ACTIVEXCONTROL_REGISTRY(“ButtonXControl.ButtonX”,
1);
宏的參數(shù)就可以了,比如你已經(jīng)將圖標(biāo)資源(Bitmap),加入工程,并且這個(gè)資源ID為2,則你可以這樣更改:
DECLARE_ACTIVEXCONTROL_REGISTRY(“ButtonXControl.ButtonX”,
2);
再看,在protected下有很多屬性、方法的申明,在cpp文件中,這些申明也得到了實(shí)現(xiàn),但問(wèn)題在于為什么是保護(hù)類型的?這樣ActiveX控件豈不是訪問(wèn)不到這些屬性、方法?申明了又有什么用?
是否還記得TButtonXImpl繼承了IButtonX接口呢?我們現(xiàn)在要到那里去看看,為此我們要分析一下ButtonXContrl_TLB單元,這個(gè)單元文件是由IDE維護(hù)的,一般情況是不需要理會(huì)這個(gè)文件的內(nèi)容,Borland也不建議你更改這個(gè)文件,不過(guò)今天我們必須要跨入禁區(qū)了,于是就有了IButtonX的實(shí)現(xiàn)代碼:
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接口,所以這也是一個(gè)自動(dòng)化的接口,而且終于有了public,所以那些接口方法和屬性被公開(kāi)了,我們不難得出這樣一張類的布局圖:
另外請(qǐng)大家注意:
VARIANT_BOOL __fastcall get_Default(void)
{
VARIANT_BOOL Value;
OLECHECK(this->get_Default((VARIANT_BOOL*)&Value));
return Value;
}
上面的實(shí)現(xiàn)代碼,OLECHECK用于檢查函數(shù)的執(zhí)行結(jié)果,如果有錯(cuò)誤,那么還有一個(gè)機(jī)會(huì)去處理錯(cuò)誤。
方法和屬性都有了,對(duì)于一個(gè)ActiveX控件還差事件,沒(méi)有事件支持的ActiveX控件就像一個(gè)沒(méi)有發(fā)條的鐘是不會(huì)動(dòng)的,下面,我們?cè)賮?lái)看一下VCL是如何實(shí)現(xiàn)ActiveX控件的事件機(jī)制的。
事件機(jī)制
還是上面的代碼:
void __fastcall ClickEvent(TObject *Sender);
void __fastcall KeyPressEvent(TObject *Sender, char &Key);
public:
TVclControlImpl
void InitializeControl()
{
m_VclCtl->OnClick = ClickEvent;
m_VclCtl->OnKeyPress = KeyPressEvent;
}
...
看起來(lái)像是事件的處理代碼,啊?好像?有沒(méi)有搞錯(cuò)?沒(méi)有搞錯(cuò),確實(shí)是好像,而且是表面的
:
m_VclCtl->OnClick = ClickEvent;
m_VclCtl->OnKeyPress = KeyPressEvent;
是標(biāo)準(zhǔn)的VCL消息處理函數(shù)機(jī)制,m_VclCtl通過(guò)模板參數(shù)最終對(duì)應(yīng)于相應(yīng)的VCL原類,這樣m_VclCtl的OnClick事件的處理就會(huì)轉(zhuǎn)交給ClickEvent函數(shù),而OnKeyPress事件的處理也就交給了KeyPressEvent函數(shù)處理,有VCL經(jīng)驗(yàn)的人,都能猜到ClickEvent和KeyPressEvent函數(shù)是如何實(shí)現(xiàn)的,例如:
void __fastcall TButtonXImpl::KeyPressEvent(TObject *Sender,
char &Key)
{
short TempKey;
TempKey = (short)Key;
Fire_OnKeyPress(&TempKey);
Key = (short)TempKey;
}
又跳轉(zhuǎn)了,忽略Sender參數(shù),然后把Key又傳遞給了Fire_OnKeyPress函數(shù)處理,為了保證VCL KeyPress事件的結(jié)構(gòu),Key參數(shù)先被保存到TempKey,然后傳遞,最后返回Key參數(shù),注意:TempKey參數(shù)可能在ActiveX事件處理中被修改,這也符合Visual
Basic的KeyPress事件結(jié)構(gòu)。
不過(guò)問(wèn)題在于Fire_OnKeyPress函數(shù)是從哪里來(lái)的?要搞清楚這個(gè)問(wèn)題,我們還要看看前面那個(gè)復(fù)雜的宏定義:
#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控件的事件觸發(fā)機(jī)制,IDE會(huì)根據(jù)創(chuàng)建的VCL控件自動(dòng)產(chǎn)生這個(gè)類,通過(guò)這個(gè)類,ActiveX控件可以在事件觸發(fā)的時(shí)候調(diào)用相應(yīng)的方法來(lái)處理這些事件。
注意上面的##的編譯器指示字,它是用來(lái)連接2個(gè)宏參數(shù)的,比如TEvents_##CoClass會(huì)被替換為T(mén)Events_ButtonX,這也是一個(gè)類,不過(guò)是用IDE自動(dòng)產(chǎn)生的,用于支持事件機(jī)制。
所以所有的事件奧秘都應(yīng)該隱藏在這個(gè)TEvents_ButtonX類里,如果你夠大膽的話,你可以猜測(cè)那個(gè)Fire_OnKeyPress函數(shù)就在這個(gè)類里?再次跨越禁區(qū),我們得到代碼:
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;
};
看到了確實(shí)如此,我們?cè)賮?lái)看看Fire_OnKeyPress是如何實(shí)現(xiàn)的?
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();
}
剔除不必要的多線程訪問(wèn)互斥代碼、對(duì)控件數(shù)組事件的支持代碼,關(guān)鍵在于:
m_EventIntfObj.OnKeyPress(Key);
看來(lái)我們又要跳轉(zhuǎn)了,最后來(lái)到:
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知識(shí)的人,都可以看出來(lái)這是一套標(biāo)準(zhǔn)的OLE方法調(diào)用機(jī)制,如果你還想跟蹤下去,你會(huì)發(fā)現(xiàn)它就是調(diào)用IDispatch接口的Invoke方法來(lái)負(fù)責(zé)方法、屬性的調(diào)用的,不過(guò)這里還可以注意一下:
_TDispID _dispid(/* OnKeyPress */ DISPID(8));
這里DISPID(8)是接口方法的標(biāo)識(shí)符,這個(gè)值來(lái)自于你設(shè)計(jì)IButtonXEvents接口時(shí)定義的ID號(hào),所以Invoke方法會(huì)唯一的定位到這個(gè)方法來(lái)完成事件機(jī)制的最后一步:調(diào)用客戶代碼-也就是你在VB中提供的事件的代碼。
于是我們又得到了這樣一幅消息、事件流向圖:
VCL就是這樣一步一步實(shí)現(xiàn)ActiveX控件的事件機(jī)制的,可以看出來(lái),他的實(shí)現(xiàn)還是挺麻煩的,不過(guò)考慮到COM原理本來(lái)的復(fù)雜性,這樣的實(shí)現(xiàn)復(fù)雜度還是可以接受的。
簡(jiǎn)單的測(cè)試
由控件向?qū)傻腡ButtonX代碼,不需要任何改動(dòng),直接編譯就會(huì)產(chǎn)生一個(gè)TButtonX ActiveX控件,我們現(xiàn)在測(cè)試一下,點(diǎn)Register Active
Server菜單,注冊(cè)這個(gè)控件,然后在開(kāi)啟Visual Basic開(kāi)發(fā)環(huán)境,加入剛才注冊(cè)的控件,發(fā)現(xiàn)它確實(shí)是按照我們的設(shè)計(jì)工作的,注意:如果你在開(kāi)始創(chuàng)建這個(gè)控件的時(shí)候,選擇了生成About對(duì)話框的選項(xiàng),那么還有一個(gè)About屬性用于顯示關(guān)于對(duì)話框。
那么這個(gè)關(guān)于對(duì)話框又是怎么回事?代碼為我們展示這點(diǎn):
void ShowButtonXAbout(void)
{
TButtonXAbout* Form;
Form = new TButtonXAbout(NULL);
try
{
Form->ShowModal();
}
catch(...)
{
Form->Free();
return;
}
Form->Free();
}
在TButtonImpl的AboutBox函數(shù)中調(diào)用了上面的函數(shù)來(lái)顯示對(duì)話框,不過(guò)有一點(diǎn)作者也不太清楚,就是:
Form->Free();
本來(lái)按照Borland的說(shuō)法,在BCB中不推薦使用Free來(lái)釋放內(nèi)存,而應(yīng)該使用delete這個(gè)關(guān)鍵字,但這里為什么這樣使用?不過(guò)作者做了測(cè)試,使用delete也是可以的,沒(méi)有發(fā)現(xiàn)什么問(wèn)題,所以作者猜測(cè),這里可能是Borland沒(méi)有更新那個(gè)代碼向?qū)б赃m應(yīng)BCB的開(kāi)發(fā)(可能本來(lái)是為Delphi設(shè)計(jì)的,而B(niǎo)orland只是簡(jiǎn)單的做了一下Delphi到BCB的轉(zhuǎn)換)。
最后需要說(shuō)明的是AboutBox函數(shù)的DISPID必須是-552,這樣ActiveX會(huì)把這個(gè)函數(shù)作為About來(lái)對(duì)待,其實(shí)DISPID的設(shè)置還是有強(qiáng)制性,很多標(biāo)準(zhǔn)屬性必須是特定的DISPID,這些DISPID都是負(fù)值,有興趣的朋友可以看看MSDN或者COM原理的書(shū)籍。
重用 繼承?
上面的TButtonX控件簡(jiǎn)單的通過(guò)繼承VCL的TButton的實(shí)現(xiàn)了一個(gè)按鈕的AcitiveX控件,如果是其他的VCL能不能同樣這樣簡(jiǎn)單的繼承就可以方便的生成ActiveX控件呢?在問(wèn)答前,我們先做一個(gè)試驗(yàn),把剛才這個(gè)TButtonX放在Visual
Basic開(kāi)發(fā)環(huán)境中,然后使用Spy++這樣工具(這里作者使用作者自己開(kāi)發(fā)的MySpy,可以到下載)看看他的類名:
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;
}
前面說(shuō)這是BCB實(shí)現(xiàn)ActiveX機(jī)制的關(guān)鍵類TVclControlImpl所具有的代碼,用于把控件畫(huà)在ActiveX宿主窗體上,而這個(gè)方法就是來(lái)源于TWinControl,所以ActiveX控件的必須繼承自這個(gè)類(當(dāng)然如果是自己實(shí)現(xiàn)了,就另當(dāng)別論),這樣,很容易想到的是像TLabel這樣的VCL控件是無(wú)法實(shí)現(xiàn)為ActiveX控件的。
那么是不是從TWinControl繼承的VCL元件都可以被封裝為ActiveX呢?這也不一定,如果你已經(jīng)把相應(yīng)的的unit添加進(jìn)入工程或者已經(jīng)把它安裝了,則這個(gè)VCL元件可能不會(huì)出現(xiàn)在那個(gè)DropDown
列表里,還有就是這個(gè)類沒(méi)有用RegisterNonActiveX函數(shù)注冊(cè),這個(gè)函數(shù)專門(mén)用來(lái)設(shè)置那些類不能被封裝為ActiveX,這個(gè)函數(shù)很復(fù)雜:
extern PACKAGE void __fastcall RegisterNonActiveX(System::TMetaClass*,
const * ComponentClasses,
const int ComponentClasses_Size,
TActiveXRegType AxRegType);
具體的使用方法可以參考幫助。在CSDN上和Borland新聞組作者也看到過(guò)網(wǎng)友詢問(wèn)“為什么我的控件不能出現(xiàn)在AcitveX的生成向?qū)Ю铩保M麓慰催^(guò)這篇文章的朋友下次可以解決這個(gè)問(wèn)題。
再完善一些
再回到剛才Visual Basic的開(kāi)發(fā)環(huán)境,我們來(lái)看看到底那些屬性和方法被表露了:
而同樣的TButton在BCB中卻具有非常多的屬性,為什么呢?開(kāi)始作者認(rèn)為所有的VCL元件都是表露這些基本的屬性和方法,但后來(lái)作者又做了一個(gè)試驗(yàn),就是簡(jiǎn)單的封裝了TEdit后發(fā)現(xiàn)他表露其他的更多屬性和方法,參考了一下幫助文檔,發(fā)現(xiàn)原來(lái)有如下規(guī)則:
如果VCL的屬性或方法不符合上述規(guī)則,我們就需要自己實(shí)現(xiàn)相應(yīng)ActiveX代碼來(lái)表露他們;相反如果符合上述規(guī)定,而你又不想表露給最終用戶,你可以在Type
Library Editor中刪除它們,刷新代碼后再刪除單元文件中相應(yīng)的生成代碼。
這里需要強(qiáng)調(diào)的是:在BCB中設(shè)計(jì)ActiveX控件在很大程度上是先設(shè)計(jì)對(duì)應(yīng)的VCL后再封裝為ActiveX,而不是像VC那樣直接開(kāi)發(fā)ActiveX(其實(shí)BCB也可以像VC那樣開(kāi)發(fā)ActiveX,畢竟都是使用ATL嘛),這樣設(shè)計(jì)不管是從難易程度還是調(diào)試都是非常輕松的。
知道了原理,我們現(xiàn)在要做的是在這個(gè)半成品的TButtonX中加入新的事件和屬性頁(yè),使它看起來(lái)更像一個(gè)專業(yè)的ActiveX控件,對(duì)于屬性和方法,因?yàn)榫帉?xiě)這些代碼非常簡(jiǎn)單,為了節(jié)省篇幅,這里就不實(shí)際添加屬性和方法了。
從TButton封裝得到的AcitiveX缺少了一個(gè)重要的事件,OnMouseMove,下面我們就寫(xiě)代碼來(lái)表露這個(gè)事件,根據(jù)我們上面講述的原理,完成這部分很容易,首先就是在Type
Library Editor里的事件支持接口添加相應(yīng)方法,如圖:
刷新后IDE自動(dòng)產(chǎn)生相應(yīng)代碼,在Impl單元文件中,加入VCL的OnMouseMove消息處理的轉(zhuǎn)移代碼,如下黑體部分:
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其實(shí)就是TButton,他是通過(guò)模板參數(shù)替換的,所以通過(guò)上面的代碼TButton的OnMouseMove消息的處理過(guò)程轉(zhuǎn)向了MouseMoveEvent,現(xiàn)在我們最后的工作就是編寫(xiě)MouseMoveEvent這個(gè)函數(shù)處理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參數(shù)后,再次轉(zhuǎn)發(fā)消息流,F(xiàn)ire_OnMouseMove這個(gè)函數(shù)是由IDE自動(dòng)產(chǎn)生的,我們直接調(diào)用就可以了,它的代碼如下:
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事件支持的代碼就完成了。
接下來(lái)是添加一個(gè)屬性頁(yè),由于VCL的封裝,使得開(kāi)發(fā)設(shè)計(jì)屬性頁(yè)變得非常簡(jiǎn)單,首先生成一個(gè)新的Property Page,這樣BCB會(huì)為我們產(chǎn)生一個(gè)Form,這個(gè)Form與普通的Win32開(kāi)發(fā)中Form的最大區(qū)別在于它繼承自TPropertyPage,所以它有一些獨(dú)有的方法是我們?cè)谠O(shè)計(jì)需要注意的,為了簡(jiǎn)單起見(jiàn),屬性頁(yè)里只簡(jiǎn)單地更改、反饋Caption屬性,在實(shí)際開(kāi)發(fā)中復(fù)雜的屬性頁(yè)是類似的。
屬性頁(yè)與ActiveX交互就是通過(guò)2個(gè)函數(shù)來(lái)進(jìn)行的,而這兩個(gè)函數(shù)都是來(lái)自于TPropertyPage類,在缺省生成的Property Page Form里已經(jīng)加入了這個(gè)兩個(gè)函數(shù)的缺省代碼,我們要做就是完成這2個(gè)函數(shù):
UpdatePropertyPage(void),在打開(kāi)屬性頁(yè)時(shí),系統(tǒng)會(huì)調(diào)用這個(gè)函數(shù),你可以在這個(gè)函數(shù)里添加代碼用以反映ActiveX的屬性在屬性頁(yè)里的顯示。
UpdateObject(void),在應(yīng)用屬性時(shí),系統(tǒng)會(huì)調(diào)用這個(gè)函數(shù),你可以把屬性頁(yè)中改變的數(shù)據(jù)應(yīng)用到實(shí)際的ActiveX控件中。
按照BCB的幫助這兩個(gè)函數(shù)實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單,比如UpdateObject(void),就只需要如下代碼:
void __fastcall TPropertyPage1::UpdateObject(void)
{
OleObject.OlePropertySet<WideString>("EditMask", WideString(InputMast->Text).Copy());
}
就可以把ActiveX控件的屬性更改為屬性頁(yè)中設(shè)置的數(shù)據(jù),但在作者的計(jì)算機(jī)上(BCB6+sp4),上面的代碼怎么都無(wú)法通過(guò)編譯,無(wú)奈之下,作者只好使用原始的方法,代碼如下:
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));
}
對(duì)于沒(méi)有ATL、COM知識(shí)的讀者上面的代碼可能比較難懂,建議找些相關(guān)書(shū)籍熟悉一下。
通過(guò)上面的代碼,ActiveX控件和屬性頁(yè)之間就可以完美的交互了,最后在ButtonImpl.h文件中加入如下宏映:
BEGIN_PROPERTY_MAP(TButtonXImpl)
PROP_PAGE(CLSID_pageNormal)
END_PROPERTY_MAP()
關(guān)于這些宏的說(shuō)明和原理因篇幅關(guān)系這里就不討論了,BCB的幫助和源碼注釋里都寫(xiě)的很清楚,感興趣的朋友可以自己研究一下。至于上面的CLSID_pageNormal是生成屬性頁(yè)時(shí),IDE自動(dòng)為改屬性頁(yè)生成的ClassID。
這樣一個(gè)比較完善的ActiveX控件就寫(xiě)完了,是不是非常簡(jiǎn)單,詳細(xì)代碼可以到CSDN網(wǎng)站下載。
寫(xiě)在最后
由于篇幅關(guān)系,本來(lái)很多內(nèi)容可以展開(kāi)詳細(xì)討論,但作者都省略了,本文就當(dāng)拋磚引玉,感興趣的讀者可以再深入研究。作者想再次強(qiáng)調(diào)的是,不管開(kāi)發(fā)ActiveX控件還是Active
Form,最好的方法都是封裝(或者轉(zhuǎn)換)為ActiveX,而不是一切從頭來(lái),比如你有一個(gè)工程,想以ActiveForm的形式用在Web上,那么最好的方法是拿出單獨(dú)的Form然后轉(zhuǎn)換為ActiveForm,這不需要多復(fù)雜的代碼,這也是Borland新聞組上專家給的建議。
由于編寫(xiě)ActiveX控件的調(diào)試復(fù)雜性,所以保證代碼質(zhì)量非常重要,除此之外就是善于利用一些工具來(lái)幫助調(diào)試,比如Visual Basic、ActiveX
Control Test Container等工具,如果有機(jī)會(huì)作者會(huì)寫(xiě)一些實(shí)際開(kāi)發(fā)中可能遇到的調(diào)試問(wèn)題和經(jīng)驗(yàn)。
參考文獻(xiàn):
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn