在VB中有下面的这种语法
Set docs = Application.Documents For Each doc in docs MsgBox doc.Title Next在这里docs就是一个集合类,VB中还专门提供了一个Collection对象,可以组建你自己的集合类
当然,集合类还有许多特征,一个集合类都会有下面的几个方法或属性
Count只读属性
Item方法,可以带1个或多个参数,返回集合内的对象,一般设置为集合类的缺省属性(方法)
_NewEnum只读属性,这个属性对用户不可见,主要就用于上面的for each语法
一般来说,可能还会有Add和Remove等方法
下面,我们就将一步步来实现这个一个集合类Tcoll
前面的步骤都不说了
1.新建一个派生自CCmdTarget,选上Createable by type ID选项的CTcItem类,添加BSTR Key和BSTR Text属性,作为我们的集合中的元素,其中属性Key为关键字,用于利用关键字来选择集合元素,属性Text即为元素的文本,用于在控件在显示,这是个 很简单的类
另外,我们为CTcItem添加一个控件的指针CTcollCtrl* m_pCtrl;作用是为了在Text属性改变时,能够刷新控件
class CTcollCtrl;
class CTcItem : public CCmdTarget
{
...
public:
CTcollCtrl* m_pCtrl;
CString m_key;
CString m_text;
...
}
m_key和m_text用向导生成时,是protected的,但是,这里将它们移到了public下,因为后面会用到。
CTcItem::CTcItem()
{
EnableAutomation();
// To keep the application running as long as an OLE automation
// object is active,the constructor calls AfxOleLockApp.
m_pCtrl = NULL;
AfxOleLockApp();
}
void CTcItem::OnTextChanged()
{
// TODO: Add notification handler code
if(m_pCtrl)
m_pCtrl->InvalidateControl();
}
void CTcItem::OnKeyChanged()
{
// TODO: Add notification handler code
}
2.新建一个派生自CCmdTarget,选上Automation选项的CColl类,作为我们的集合类,添加long Count只读属性,LPDISPATCH Item(VARIANT Index)方法,LPUNKNOWN NewEnum属性,LPDISPATCH Add(VARIATN strKey,VARIANT strText)方法和void Remove(VARIANT Index)方法
对于Item方法,需要设置为缺省属性,可是似乎向导对于方法,并不提供Default Property这一选项,所以我们得自己加上了,下面的DISP_DEFVALUE宏就是用于设置缺省属性的,另外得将Item的DispID改为0
NewEnum是很特殊的,因为,它的名字并不重要,你可以改成其它的阿狗阿猫,但是它的DispID必须为DISPID_NEWENUM=-4
所以上面的这些更改都在两个地方完成,一是在集合类的.cpp中,另一个是在.odl中,如下:
Coll.cpp:
BEGIN_DISPATCH_MAP(CColl,CCmdTarget)
//{{AFX_DISPATCH_MAP(CColl)
DISP_PROPERTY_EX(CColl,"Count",GetCount,SetNotSupported,VT_I4)
DISP_FUNCTION(CColl,"Item",Item,VT_DISPATCH,VTS_VARIANT)
DISP_DEFVALUE(CColl,"Item")
DISP_PROPERTY_EX_ID(CColl,"NewEnum",DISPID_NEWENUM,_NewEnum,VT_UNKNOWN)
DISP_FUNCTION(CColl,"Remove",Remove,VT_EMPTY,VTS_VARIANT)
DISP_FUNCTION(CColl,"Add",Add,VTS_VARIANT VTS_VARIANT)
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
Tcoll.odl:
// Primary dispatch interface for CColl
#define DISPID_NEWENUM -4
[ uuid(0D58DCBE-EBDF-4746-80FD-8F389CF6BB0E) ]
dispinterface IColl
{
properties:
[id(DISPID_NEWENUM)] IUnknown* _NewEnum;
// NOTE - ClassWizard will maintain property information here.
// Use extreme caution when editing this section.
//{{AFX_ODL_PROP(CColl)
[id(1)] long Count;
//}}AFX_ODL_PROP
methods:
[id(0)] ITcItem* Item(VARIANT Index);
// NOTE - ClassWizard will maintain method information here.
// Use extreme caution when editing this section.
//{{AFX_ODL_METHOD(CColl)
[id(3)] void Remove(VARIANT Index);
[id(4)] ITcItem* Add([optional]VARIANT strKey,[optional]VARIANT strText);
//}}AFX_ODL_METHOD
};
注:
a.这里将_NewEnum和Item都提到了向导生成代码的外面,是为了避免新增加属性或方法时向导乱改DispID,而且,很显然,向导也不认识DISPID_NEWENUM之类的DispID。
b.这里的Item和Add的返回值都从IDispatch*改成了ITcItem*,这是为了能在VB设计代码时显示出接口的属性或方法。
c.这里将Add方法的两个参数均加上了optional选项,是为了可以使用缺省参数,就象ListView控件的ListItems对象的Add方法 ListView1.ListItems.Add,"Hello"一样的使用,要使用缺省参数的话,就一定要用VARIANT,所以这里用了VARIANT,而不是BSTR
d.这里的Item和Remove中的索引都是用的VARIANT,因为我们有可能会用关键字来选择集合中的元素。
e.将元素添加到集合中的Add方法是由你自己定的,这里是由函数来生成一个元素,你也可以直接添加一个IDispatch(ITcItem)元素到集合 中(在用CCmdTarget派生时,需要设置createable by type ID选项),下面的注释掉的代码中有这种方法的实现,仅供参考。
根据上面的描述,不难写出下面的实现代码
long CColl::GetCount()
{
// TODO: Add your property handler here
return m_olItems.GetCount();
return 0;
}
LPDISPATCH CColl::Item(const VARIANT FAR& Index)
{
// TODO: Add your dispatch handler code here
if(Index.vt == VT_I4){
POSITION pos = m_olItems.FindIndex(Index.lVal);
if(pos){
CTcItem* pitem = (CTcItem*)m_olItems.GetAt(pos);
if(pitem != NULL){
return pitem->GetIDispatch(TRUE);
}
}
return NULL;
}
else if(Index.vt == VT_BSTR){
POSITION pos = m_olItems.GetHeadPosition();
while(pos){
CTcItem* pitem = (CTcItem*)m_olItems.GetNext(pos);
if(pitem->m_key == Index.bstrVal){
return pitem->GetIDispatch(TRUE);
}
}
return NULL;
}
return NULL;
}
LPUNKNOWN CColl::_NewEnum()
{
// TODO: Add your property handler here
m_xEnumVARIANT.m_posCurrent = m_olItems.GetHeadPosition();
LPUNKNOWN pUnk = GetInterface(&IID_IEnumVARIANT);
if(pUnk) pUnk->AddRef();
return pUnk;
return NULL;
}
LPDISPATCH CColl::Add(const VARIANT FAR& strKey,const VARIANT FAR& strText)
{
// TODO: Add your dispatch handler code here
CTcItem* pitem = new CTcItem;
pitem->m_pCtrl = m_pCtrl;
if(strKey.vt == VT_ERROR && strKey.scode == DISP_E_PARAMNOTFOUND){
pitem->m_key = "";
}
else if(strKey.vt != VT_BSTR){
COleVariant v;
v.ChangeType(VT_BSTR,(LPVARIANT)&strKey);
pitem->m_key = v.bstrVal;
}
else{
pitem->m_key = strKey.bstrVal;
}
if(strText.vt == VT_ERROR && strText.scode == DISP_E_PARAMNOTFOUND){
pitem->m_text = "";
}
else if(strText.vt != VT_BSTR){
COleVariant v;
v.ChangeType(VT_BSTR,(LPVARIANT)&strText);
pitem->m_text = v.bstrVal;
}
else{
pitem->m_text = strText.bstrVal;
}
m_olItems.AddTail(pitem);
if(m_pCtrl) m_pCtrl->InvalidateControl();
return pitem->GetIDispatch(TRUE);
}
void CColl::Remove(const VARIANT FAR& Index)
{
// TODO: Add your dispatch handler code here
if(Index.vt == VT_I4){
POSITION pos = m_olItems.FindIndex(Index.lVal);
delete m_olItems.GetAt(pos);
m_olItems.RemoveAt(pos);
}
else if(Index.vt == VT_BSTR){
POSITION pos = m_olItems.GetHeadPosition();
POSITION poscur = pos;
while(pos){
CTcItem* pitem = (CTcItem*)m_olItems.GetNext(pos);
if(pitem->m_key = Index.bstrVal){
delete pitem;
m_olItems.RemoveAt(poscur);
}
poscur = pos;
}
}
if(m_pCtrl) m_pCtrl->InvalidateControl();
}
/*
LPDISPATCH CColl::Add(LPDISPATCH tcItem)
{
// TODO: Add your dispatch handler code here
CTcItem* pitem = (CTcItem*)CCmdTarget::FromIDispatch(tcItem);
m_olItems.AddTail(pitem);
if(m_pCtrl) m_pCtrl->InvalidateControl();
return tcItem;
return NULL;
}
*/
注:
a.请看这一句 if(strKey.vt == VT_ERROR && strKey.scode == DISP_E_PARAMNOTFOUND),这就是判断是否有设置缺省参数的,如果为TRUE,就表示未设置参数,你可以做相应的处理了,这里只是简单 的设置了缺省的参数。
b.这里在参数不是字符串时,都转换成字符串,也就是说,你可以直接写成.Add 1,1。
c..这里和元素类中一样,也定义了CTcollCtrl* m_pCtrl,用途也一样,也是用来调用InvalidateControl刷新控件的。
d.这里有m_olItems,它的定义是CObList m_olItems;用来保存所有元素指针,应该不陌生吧,在集合类的析构函数中,需要删除所有的元素
CColl::~CColl()
{
POSITION pos = m_olItems.GetHeadPosition();
while(pos){
delete m_olItems.GetNext(pos);
}
}
e.在
LPUNKNOWN CColl::_NewEnum()
{
// TODO: Add your property handler here
m_xEnumVARIANT.m_posCurrent = m_olItems.GetHeadPosition();
LPUNKNOWN pUnk = GetInterface(&IID_IEnumVARIANT);
if(pUnk) pUnk->AddRef();
return pUnk;
return NULL;
}
中,用到了m_xEnumVARIANT和IID_IEnumVARIANT,这就是接下来要做的,添加IEnumVARIANT接口
3.要能在VB中的for each语法中使用,一个很重要的部分就是要实现一个IEnumVARIANT接口,在for each时,VB会自动调用集合的_NewEnum来请求这个IEnumVARIANT接口(参见上面的_NewEnum实现代码)。 IEnumVARIANT接口可以在任何地方实现,只要能从_NewEnum中获得这个接口指针就可以了。我们这里还是用MFC的聚合类的方法来实现, m_xEnumVARIANT就是集合类中的聚合类对象,具体的方法不多述了,本系列的上面几步中已讲了很多了。
Coll.h
...
DECLARE_DISPATCH_MAP()
DECLARE_INTERFACE_MAP()
public:
CTcollCtrl* m_pCtrl;
void Draw(CDC* pdc);
CObList m_olItems;
BEGIN_INTERFACE_PART(EnumVARIANT,IEnumVARIANT)
STDMETHOD(Next)(THIS_ ULONG celt,VARIANT FAR* rgvar,
ULONG FAR* pceltFetched);
STDMETHOD(Skip)(THIS_ ULONG celt) ;
STDMETHOD(Reset)(THIS) ;
STDMETHOD(Clone)(THIS_ IEnumVARIANT FAR* FAR* ppenum) ;
XEnumVARIANT() ; // constructor to set m_posCurrent
POSITION m_posCurrent ; // Next() requires we keep track of our current item
END_INTERFACE_PART(EnumVARIANT)
...
Coll.cpp
...
BEGIN_INTERFACE_MAP(CColl,CCmdTarget)
INTERFACE_PART(CColl,IID_IColl,Dispatch)
INTERFACE_PART(CColl,IID_IEnumVARIANT,EnumVARIANT) END_INTERFACE_MAP() CColl::XEnumVARIANT::XEnumVARIANT() { m_posCurrent = NULL ; } STDMETHODIMP_(ULONG) CColl::XEnumVARIANT::AddRef() { METHOD_PROLOGUE(CColl,EnumVARIANT) return pThis->ExternalAddRef() ; } STDMETHODIMP_(ULONG) CColl::XEnumVARIANT::Release() { METHOD_PROLOGUE(CColl,EnumVARIANT) return pThis->ExternalRelease() ; } STDMETHODIMP CColl::XEnumVARIANT::QueryInterface( REFIID iid,void FAR* FAR* ppvObj ) { METHOD_PROLOGUE(CColl,EnumVARIANT) return (HRESULT)pThis->ExternalQueryInterface( (void FAR*)&iid,ppvObj) ; } // IEnumVARIANT::Next // STDMETHODIMP CColl::XEnumVARIANT::Next( ULONG celt,ULONG FAR* pceltFetched) { // This sets up the "pThis" pointer so that it points to our // containing CDocuments instance // METHOD_PROLOGUE(CColl,EnumVARIANT) HRESULT hr; ULONG l ; CTcItem* pItem = NULL ; if(pceltFetched != NULL) *pceltFetched = 0; else if(celt > 1){ return ResultFromScode(E_INVALIDARG); } for(l=0; l<celt; l++){ VariantInit(&rgvar[l]); } hr = NOERROR; for(l=0; m_posCurrent != NULL && celt != 0; l++){ pItem = (CTcItem*)pThis->m_olItems.GetNext(m_posCurrent); celt--; if(pItem){ rgvar[l].vt = VT_DISPATCH; rgvar[l].pdispVal = pItem->GetIDispatch(TRUE); if(pceltFetched != NULL) (*pceltFetched)++; } else{ return ResultFromScode( E_UNEXPECTED ); } } if (celt != 0) hr = ResultFromScode( S_FALSE ) ; return hr; } // IEnumVARIANT::Skip // STDMETHODIMP CColl::XEnumVARIANT::Skip(ULONG celt) { METHOD_PROLOGUE(CColl,EnumVARIANT) while(m_posCurrent != NULL && celt--) pThis->m_olItems.GetNext(m_posCurrent); return (celt == 0 ? NOERROR : ResultFromScode( S_FALSE )) ; } STDMETHODIMP CColl::XEnumVARIANT::Reset() { METHOD_PROLOGUE(CColl,EnumVARIANT) m_posCurrent = pThis->m_olItems.GetHeadPosition(); return NOERROR ; } STDMETHODIMP CColl::XEnumVARIANT::Clone(IEnumVARIANT FAR* FAR* ppenum) { METHOD_PROLOGUE(CColl,EnumVARIANT) CColl* p = new CColl ; if (p) { p->m_xEnumVARIANT.m_posCurrent = m_posCurrent ; return NOERROR ; } else return ResultFromScode( E_OUTOFMEMORY ) ; } 注: a.在前面的_NewEnum属性实现中 LPUNKNOWN CColl::_NewEnum() { // TODO: Add your property handler here m_xEnumVARIANT.m_posCurrent = m_olItems.GetHeadPosition(); LPUNKNOWN pUnk = GetInterface(&IID_IEnumVARIANT); if(pUnk) pUnk->AddRef(); return pUnk; return NULL; } m_xEnumVARIANT.m_posCurrent = m_olItems.GetHeadPosition(); 这一句就是用来设置枚举的起始位置的。 4.集合类基本完毕,现在是将集合类和控件类联起来的时候了,这个很简单,为控件类加一个LPDISPATCH Coll只读属性,另外由于集合类需要控件类的指针,所以在构造函数中将控件类的指针传递给了集合类的m_pCtrl成员,同样在集合的元素类中,也会需 要控件类的指针以及时刷新控件,这个指针的传递在集合类的Add方法中 LPDISPATCH CColl::Add(const VARIANT FAR& strKey,const VARIANT FAR& strText) { // TODO: Add your dispatch handler code here CTcItem* pitem = new CTcItem; pitem->m_pCtrl = m_pCtrl; ...... } TcollCtl.cpp ...... CTcollCtrl::CTcollCtrl() { InitializeIIDs(&IID_DTcoll,&IID_DTcollEvents); // TODO: Initialize your control's instance data here. m_pColl = new CColl; m_pColl->m_pCtrl = this; } ///////////////////////////////////////////////////////////////////////////// // CTcollCtrl::~CTcollCtrl - Destructor CTcollCtrl::~CTcollCtrl() { // TODO: Cleanup your control's instance data here. delete m_pColl; } ... LPDISPATCH CTcollCtrl::GetColl() { // TODO: Add your property handler here return m_pColl->GetIDispatch(TRUE); return NULL; } tcoll.odl ...... [ uuid(7852D333-7E2F-49DA-BA8F-EAC8748A23B2),helpstring("Dispatch interface for Tcoll Control"),hidden ] dispinterface _DTcoll { properties: // NOTE - ClassWizard will maintain property information here. // Use extreme caution when editing this section. //{{AFX_ODL_PROP(CTcollCtrl) [id(1)] IColl* Coll; //}}AFX_ODL_PROP methods: // NOTE - ClassWizard will maintain method information here. // Use extreme caution when editing this section. //{{AFX_ODL_METHOD(CTcollCtrl) //}}AFX_ODL_METHOD [id(DISPID_ABOUTBox)] void AboutBox(); }; ...... 5.整个框架好了,现在还需要一个外在的表现了,OnDraw函数上场了。 void CTcollCtrl::OnDraw( CDC* pdc,const CRect& rcBounds,const CRect& rcInvalid) { // TODO: Replace the following code with your own drawing code. pdc->FillRect(rcBounds,CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH))); m_pColl->Draw(pdc); } void CColl::Draw(CDC *pdc) { int x = 5; int y = 5; POSITION pos = m_olItems.GetHeadPosition(); while(pos){ CTcItem* pitem = (CTcItem*)m_olItems.GetNext(pos); pdc->TextOut(x,y,pitem->m_text); y += 20; } } 控件类先画底,然后调用集合类的Draw函数,在集合类的Draw函数中将所有集合元素中的Text属性列了出来。 6.编译,在VB下测试 Private Sub Form_Load() With Tcoll1 .Coll.Add "keyHello","Hello" .Coll.Add,"Good" .Coll.Add "keyThank" MsgBox .Coll("keyHello").Text Dim c As TcItem For Each c In .Coll MsgBox c.Text & c.Key Next End With End Sub 参考资料:刚刚发现,很多关于ActiveX和OLE的资料 MSDN98/98VS/2052/techart.chm 本文的相关参考也在其中 MSDN98/98VS/2052/techart.chm::/html/msdn_collect.htm