Chinaunix首页 | 论坛 | 博客
  • 博客访问: 185506
  • 博文数量: 90
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 0
  • 用 户 组: 普通用户
  • 注册时间: 2017-08-23 16:48
文章分类

全部博文(90)

文章存档

2015年(1)

2011年(21)

2010年(59)

2009年(9)

我的朋友

分类: C/C++

2010-07-14 10:09:46

OLE自动化集合类
在VB中有下面的这种语法
Set docs = Application.DocumentsFor Each doc in docs   MsgBox doc.TitleNext在这里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, SetNotSupported, VT_UNKNOWN)
    DISP_FUNCTION(CColl, "Remove", Remove, VT_EMPTY, VTS_VARIANT)
    DISP_FUNCTION(CColl, "Add", Add, VT_DISPATCH, 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, VARIANT FAR* rgvar, 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        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
 
阅读(1863) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~