ActiveX控件的MFC设计之旅-第11步 .
发布时间:2020-12-16 22:49:11 所属栏目:大数据 来源:网络整理
导读:OLE自动化集合类 在VB中有下面的这种语法 Set docs = Application.DocumentsFor Each doc in docs MsgBox doc.TitleNext 在这里docs就是一个集合类,VB中还专门提供了一个Collection对象,可以组建你自己的集合类 当然,集合类还有许多特征,一个集合类都会有
OLE自动化集合类
在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 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |