ActiveX控件的MFC设计之旅-第13步

这一步我们将持久化一个接口,可能有些难度,因为用到了很多前面讲过的东西。

在第1步中,我们就实现了一个具有层次结构的控件,但是,我们并没有给这个控件提供持久化,更不用说,持久化控件的所有层次了。

在上一步中,实现了一个标准的图片接口(IPictureDisp)的持久化

在这一步中,会综合第一步和上一步中的内容,实现一个我们自定义的接口的持久化。

仍旧采用上一步中的Topp例子(和上一步的内容基本独立,可以从头建控件)
1.根据第1步中所述,建立一个新的Dispatch接口IToppBox,(从CCmdTarget派生类CToppBox,使用Createable by type ID选项),这个接口的功能是为我们的控件提供边框。
2.为新的IToppBox接口(实现类为CToppBox)添加long Width属性和OLE_COLOR Color属性
3.为控件类CToppCtrl添加LPDISPATCH Box属性,设置变量为LPDISPATCH m_pboxdisp;

CToppCtrl::CToppCtrl()
{
    InitializeIIDs(&IID_DTopp, &IID_DToppEvents);

    // TODO: Initialize your control's instance data here.
    m_pboxdisp = NULL;
}

LPDISPATCH CToppCtrl::GetBox()
{
    // TODO: Add your property handler here
    m_pboxdisp->AddRef();
    return m_pboxdisp;
    return NULL;
}

void CToppCtrl::SetBox(LPDISPATCH newValue)
{
    // TODO: Add your property handler here
    if(m_pboxdisp != NULL) m_pboxdisp->Release();
    m_pboxdisp = newValue;
    m_pboxdisp->AddRef();
    InvalidateControl();
    SetModifiedFlag();
}

4.修改.odl,更改Box属性的类型为IToppBox*,请参考本系列第2步

5.在OnDraw中绘制边框

void CToppCtrl::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)));
    pdc->Ellipse(rcBounds);*/
    //绘制边框
    CToppBox* pbox = (CToppBox*)CCmdTarget::FromIDispatch(m_pboxdisp);
    int nwid = pbox->m_width;
    if(nwid > 0){
        COLORREF crb = TranslateColor(pbox->m_color);
        CPen pen(PS_SOLID, nwid, crb);
        CPen* ppenold = pdc->SelectObject(&pen);
        pdc->Rectangle(&rcBounds);
        pdc->SelectObject(ppenold);
    }

    //绘制图片
    CRect rcfill = rcBounds;
    rcfill.DeflateRect(nwid, nwid, nwid, nwid);

    pdc->FillRect(&rcfill, &CBrush(TranslateColor(m_color)));
    m_pic.Render(pdc, rcfill, rcBounds);

    //绘制字符串数组
    int nhei = 18;
    int y = 2;
    for(int i=0; i<m_saItems.GetSize(); i++, y+=nhei){
        CRect rect(rcBounds.left + 4, y+2, rcBounds.right - 4, y + nhei);
        pdc->FillRect(&rect, &CBrush(RGB(255, 255, 255)));
        pdc->TextOut(rcBounds.left + 4, y + 2, m_saItems[i]);
    }
}


注意:这里,我们用直接调用CCmdTarget::FromIDispatch的方法来获得m_pboxdisp的实现类指针,从而获得m_width和m_color数据(如果用这种方法的话,也可以在CToppBox中加一void Draw(CDC* pDC)函数,直接在CToppBox类中实现绘图),我们也可以通过使用COleDispatchDriver类来获得IToppBox的属性(当然,这种方法时就不能用Draw方法了),以绘制相应的边框,在接下来的属性页设计中,我们会用这种方法来获得和设置控件的属性,以供参考。
6.现在,我们已经为我们的控件添加了一个LPDISPATCH类型的属性,我们的第一个目标,实现一个具有层次结构的控件,已经完成。接下来是持久化部分。

7.新建一对话框模板,连接一派生自COlePropertyPage的CBoxPropPage类。
a.这个属性页,其实是为我们的IToppBox接口对象设置属性,所以我们添加了一个成员变量LPDISPATCH m_pboxdisp;(虽然用的是LPDISPATCH,但实际上就是我们的IToppBox接口指针)
b.我们添加两个函数GetBoxDisp和SetBoxDisp以获得和设置m_pboxdisp。

void CBoxPropPage::GetBoxDisp()
{
    USES_CONVERSION;
    COleDispatchDriver PropDispDriver;
    ULONG nObjects = 0;
    LPDISPATCH* ppDisp = GetObjectArray(&nObjects);
    DISPID dwDispID;
    LPCOLESTR lpOleStr = T2COLE("Box");
    ppDisp[0]->GetIDsOfNames(IID_NULL, (LPOLESTR*)&lpOleStr, 1, 0, &dwDispID);

    if(m_pboxdisp != NULL){
        m_pboxdisp->Release();
        m_pboxdisp = NULL;
    }
    PropDispDriver.AttachDispatch(ppDisp[0]);
    PropDispDriver.GetProperty(dwDispID, VT_DISPATCH, (void*)&m_pboxdisp);
    PropDispDriver.DetachDispatch();
}

void CBoxPropPage::SetBoxDisp()
{
    USES_CONVERSION;
    COleDispatchDriver PropDispDriver;
    ULONG nObjects = 0;
    LPDISPATCH* ppDisp = GetObjectArray(&nObjects);

    DISPID dwDispID;
    LPCOLESTR lpOleStr = T2COLE("Box");
    ppDisp[0]->GetIDsOfNames(IID_NULL, (LPOLESTR*)&lpOleStr, 1, 0, &dwDispID);

    PropDispDriver.AttachDispatch(ppDisp[0]);
    PropDispDriver.SetProperty(dwDispID, VT_DISPATCH, m_pboxdisp);
    PropDispDriver.DetachDispatch();
}

c.和控件类产生关联

void CBoxPropPage::DoDataExchange(CDataExchange* pDX)
{
    // NOTE: ClassWizard will add DDP, DDX, and DDV calls here
    //    DO NOT EDIT what you see in these blocks of generated code !
    //{{AFX_DATA_MAP(CBoxPropPage)
    DDX_Text(pDX, IDC_EDIT_WIDTH, m_nWidth);
    //}}AFX_DATA_MAP
    if(pDX->m_bSaveAndValidate){
/*        if(m_bNewDisp){
            SetBoxDisp();
        }
        Refresh();*/
        SetBoxDisp();
    }
    else{
        GetBoxDisp();
    }
    DDP_PostProcessing(pDX);
}

d.为开始实际的属性设置,在对话框模板(IDD_PROPPAGE_BOX)中添加一编辑框(IDC_EDIT_WIDTH),用来设置Width属性,一按钮(“颜色”)和一静态文本框(IDC_STATIC_COLOR)来设置Color属性
e.添加GetColor,SetColor,GetWidth和SetWidth函数

COLORREF CBoxPropPage::GetColor()
{
    USES_CONVERSION;
    COLORREF cr = RGB(255, 0, 0);
    if(m_pboxdisp){
        COleDispatchDriver PropDispDriver;
        DISPID dwDispID;
        LPCOLESTR lpOleStr = T2COLE("Color");
        if (SUCCEEDED(m_pboxdisp->GetIDsOfNames(IID_NULL, (LPOLESTR*)&lpOleStr, 1, 0, &dwDispID)))
        {
            PropDispDriver.AttachDispatch(m_pboxdisp, FALSE);
            OLE_COLOR ocr;
            PropDispDriver.GetProperty(dwDispID, VT_COLOR, &ocr);
            PropDispDriver.DetachDispatch();
            ::OleTranslateColor(ocr, NULL, &cr);
        }
    }
    return cr;
}

void CBoxPropPage::SetColor(COLORREF crborder)
{
    USES_CONVERSION;
    if(m_pboxdisp){
        COleDispatchDriver PropDispDriver;
        DISPID dwDispID;
        LPCOLESTR lpOleStr = T2COLE("Color");
        if (SUCCEEDED(m_pboxdisp->GetIDsOfNames(IID_NULL, (LPOLESTR*)&lpOleStr, 1, 0, &dwDispID)))
        {
            PropDispDriver.AttachDispatch(m_pboxdisp, FALSE);
            PropDispDriver.SetProperty(dwDispID, VT_COLOR, crborder);
            PropDispDriver.DetachDispatch();
        }
    }
}

long CBoxPropPage::GetWidth()
{
    USES_CONVERSION;
    long l = 1;
    if(m_pboxdisp){
        COleDispatchDriver PropDispDriver;
        DISPID dwDispID;
        LPCOLESTR lpOleStr = T2COLE("Width");
        if (SUCCEEDED(m_pboxdisp->GetIDsOfNames(IID_NULL, (LPOLESTR*)&lpOleStr, 1, 0, &dwDispID)))
        {
            PropDispDriver.AttachDispatch(m_pboxdisp, FALSE);
            PropDispDriver.GetProperty(dwDispID, VT_I4, &l);
            PropDispDriver.DetachDispatch();
        }
    }
    return l;
}

void CBoxPropPage::SetWidth(long l)
{
    USES_CONVERSION;
    if(m_pboxdisp){
        COleDispatchDriver PropDispDriver;
        DISPID dwDispID;
        LPCOLESTR lpOleStr = T2COLE("Width");
        if (SUCCEEDED(m_pboxdisp->GetIDsOfNames(IID_NULL, (LPOLESTR*)&lpOleStr, 1, 0, &dwDispID)))
        {
            PropDispDriver.AttachDispatch(m_pboxdisp, FALSE);
            PropDispDriver.SetProperty(dwDispID, VT_I4, l);
            PropDispDriver.DetachDispatch();
        }
    }
}

在前几步中,我们说过,可以用GetPropText和SetPropText来简化属性的读取和设置,但是,这里有个前提是这个属性是控件的属性,但现在我们的属性页要设置的属性是IToppBox的,所以只好用COleDispatchDriver来实现这几个函数了。

f.响应IDC_EDIT_WIDTH的字符串改变消息,响应WM_CTLCOLOR消息处理函数,响应按钮(“颜色”)消息处理。

void CBoxPropPage::OnChangeEditWidth()
{
    // TODO: If this is a RICHEDIT control, the control will not
    // send this notification unless you override the COlePropertyPage::OnInitDialog()
    // function and call CRichEditCtrl().SetEventMask()
    // with the ENM_CHANGE flag ORed into the mask.
    
    // TODO: Add your control notification handler code here
    UpdateData();
    SetWidth(m_nWidth);
}

HBRUSH CBoxPropPage::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = COlePropertyPage::OnCtlColor(pDC, pWnd, nCtlColor);
    
    // TODO: Change any attributes of the DC here
    if(nCtlColor == CTLCOLOR_STATIC){
        COLORREF cr = GetColor();
        return ::CreateSolidBrush(cr);
    }
    else{
    // TODO: Return a different brush if the default is not desired
    return hbr;
    }
}

void CBoxPropPage::OnButtonColor()
{
    // TODO: Add your control notification handler code here
    CColorDialog dlg;
    if(dlg.DoModal() == IDOK){
        SetColor(dlg.GetColor());
        GetDlgItem(IDC_STATIC_COLOR)->Invalidate();
    }
}

g.另外,我们添加一个按钮(“新建”)和一个成员变量BOOL m_bNewDisp,用来表示我们是新建一个IToppBox对象还是用原来从控件获得的IToppBox对象,也就是说,用以下的伪代码表示就是:
新建时
box = new boxdisp;
box.width = 10;
box.color = 32048;
top.box = box;
非新建时
topp.box.width = 10;
topp.box.color = 32048;


下面是实现代码

void CBoxPropPage::OnButtonNew()
{
    // TODO: Add your control notification handler code here
    m_bNewDisp = TRUE;
    CToppBox* pbox = new CToppBox;
    m_pboxdisp->Release();
    //在CToppBox的构造函数中已经将引用参数+1,所以这里参数用了FALSE
    m_pboxdisp = pbox->GetIDispatch(FALSE);
}

在前面的OnDataExchange中
void CBoxPropPage::DoDataExchange(CDataExchange* pDX)
{
    // NOTE: ClassWizard will add DDP, DDX, and DDV calls here
    //    DO NOT EDIT what you see in these blocks of generated code !
    //{{AFX_DATA_MAP(CBoxPropPage)
    DDX_Text(pDX, IDC_EDIT_WIDTH, m_nWidth);
    //}}AFX_DATA_MAP
    if(pDX->m_bSaveAndValidate){
        //在未新建时,可以不调用SetBoxDisp(),但是,为了刷新控件,需要控件提供Refresh方法
/*        if(m_bNewDisp){
            SetBoxDisp();
        }
        Refresh();*/
        SetBoxDisp();
    }
    else{
        GetBoxDisp();
    }
    DDP_PostProcessing(pDX);
}

我们也是直接用SetBoxDisp()的,之所以这样,是因为我们需要用到控件类的InvalidateControl来刷新控件。

void CToppCtrl::SetBox(LPDISPATCH newValue)
{
    // TODO: Add your property handler here
    if(m_pboxdisp != NULL) m_pboxdisp->Release();
    m_pboxdisp = newValue;
    m_pboxdisp->AddRef();
    InvalidateControl();
    SetModifiedFlag();
}

h.在用完m_pboxdisp,结束属性页时,需要释放m_pboxdisp,令人头疼的是属性页的析构函数不知道跑哪去了,我只好在OnDestroy中释放了,如果有哪位朋友知道如何用这里的析构函数,请帮助告知,谢谢

void CBoxPropPage::OnObjectsChanged()
{
    ULONG ul = 0;
    GetObjectArray(&ul);
    if(ul == 0){
        if(m_pboxdisp) {
            m_pboxdisp->Release();
            m_pboxdisp = NULL;
        }
    }
}

void CBoxPropPage::OnDestroy()
{
    COlePropertyPage::OnDestroy();
    
    // TODO: Add your message handler code here
    if(m_pboxdisp) {
        m_pboxdisp->Release();
        m_pboxdisp = NULL;
    }
}

可能有点罗嗦了,不过,属性页总算是弄完了,现在是真正的持久化的时候了

8.添加函数BOOL PX_Box(CPropExchange* pPX),并由DoPropExchange调用它

void CToppCtrl::DoPropExchange(CPropExchange* pPX)
{
    ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
    COleControl::DoPropExchange(pPX);

    // TODO: Call PX_ functions for each persistent custom property.
    PX_Color(pPX, "Color", m_color, TranslateColor(RGB(0, 0, 0)));
    PX_Picture(pPX, _T("Picture"), m_pic);
    PX_Box(pPX);
    PX_Items(pPX);
}
只有PX_Box是本步必须的,其它的几个PX_xxx都是本系列上几步中的内容,请无视于它们。

9.实现PX_Box

BOOL CToppCtrl::PX_Box(CPropExchange *pPX)
{
    CToppBox* pbox = new CToppBox;
    LPDISPATCH pboxdisp = pbox->GetIDispatch(FALSE);
    LPUNKNOWN& pUnkd = (LPUNKNOWN&)pboxdisp;
    LPUNKNOWN& pUnk = (LPUNKNOWN&)m_pboxdisp;
    PX_IUnknown(pPX, _T("Box"), pUnk, IID_IDispatch, pUnkd);
    pUnkd->Release();
    return TRUE;
}

10.看起来,似乎是好了,但是令人遗憾的是,这时编译测试的话,会发现根本无法持久化。为什么,很简单,我们的CToppBox没有提供实际的持久化操作。为此,提出两种方法,一种是使用本系列前面几步中介绍的PX_Blob一样,不用PX_IUnknown,自己往CPropExchange中读写数据,自己实现PX_IUnknown,不过IToppBox接口就不太通用了;另外一种是让CToppBox实现持久化接口,本文采用第2种方法。
下面是MFC实现接口持久化时的部分代码
                        LPPERSISTSTREAM pps = NULL;
                        if (SUCCEEDED((*ppUnk)->QueryInterface(
                                IID_IPersistStream, (void**)&pps)) ||
                            SUCCEEDED((*ppUnk)->QueryInterface(
                                IID_IPersistStreamInit, (void**)&pps)))
                        {
                            ASSERT_POINTER(pps, IPersistStream);
                            bResult = SUCCEEDED(pps->Load(pstm));
                            pps->Release();
                        }
可以看到,它会请求IPersistStream或IPersistStreamInit接口,所以我们得让我们的CToppBox实现这些接口(这些原理性的东西很难说,这里也不详说了),这里我们只实现了IPersistStreamInit,在OLE嵌入时,还需要实现IPersistStorage等等接口,有兴趣的朋友可以一一实现。

在MFC中,实现这些接口,并不复杂,我们在本系列前面几步中的添加双接口一文中也基本讲到了。
在ToppBox.h中,加入黑体部分代码
public:
    virtual HRESULT GetClassID(LPCLSID pclsid);
    HRESULT SaveState(IStream* pstm);
    HRESULT LoadState(IStream* pstm);
    BOOL m_bModified;
    // IPersistStreamInit
    BEGIN_INTERFACE_PART(PersistStreamInit, IPersistStreamInit)
        INIT_INTERFACE_PART(CToppBox, PersistStreamInit)
        STDMETHOD(GetClassID)(LPCLSID);
        STDMETHOD(IsDirty)();
        STDMETHOD(Load)(LPSTREAM);
        STDMETHOD(Save)(LPSTREAM, BOOL);
        STDMETHOD(GetSizeMax)(ULARGE_INTEGER *);
        STDMETHOD(InitNew)();
    END_INTERFACE_PART_STATIC(PersistStreamInit)
在ToppBox.cpp中实现接口函数

STDMETHODIMP_(ULONG) CToppBox::XPersistStreamInit::AddRef()
{
    METHOD_PROLOGUE_EX_(CToppBox, PersistStreamInit)
    return (ULONG)pThis->ExternalAddRef();
}

STDMETHODIMP_(ULONG) CToppBox::XPersistStreamInit::Release()
{
    METHOD_PROLOGUE_EX_(CToppBox, PersistStreamInit)
    return (ULONG)pThis->ExternalRelease();
}

STDMETHODIMP CToppBox::XPersistStreamInit::QueryInterface(
    REFIID iid, LPVOID* ppvObj)
{
    METHOD_PROLOGUE_EX_(CToppBox, PersistStreamInit)
    return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

STDMETHODIMP CToppBox::XPersistStreamInit::GetClassID(LPCLSID lpClassID)
{
    METHOD_PROLOGUE_EX_(CToppBox, PersistStreamInit)
    return pThis->GetClassID(lpClassID);
}

STDMETHODIMP CToppBox::XPersistStreamInit::IsDirty()
{
    METHOD_PROLOGUE_EX_(CToppBox, PersistStreamInit)
    return pThis->m_bModified ? S_OK : S_FALSE;
}

STDMETHODIMP CToppBox::XPersistStreamInit::Load(LPSTREAM pStm)
{
    METHOD_PROLOGUE_EX(CToppBox, PersistStreamInit)
    return pThis->LoadState(pStm);
}

STDMETHODIMP CToppBox::XPersistStreamInit::Save(LPSTREAM pStm,
    BOOL fClearDirty)
{
    METHOD_PROLOGUE_EX(CToppBox, PersistStreamInit)

    // Delegate to SaveState.
    HRESULT hr = pThis->SaveState(pStm);

    // Bookkeeping:  Clear the dirty flag, if requested.
    if (fClearDirty)
        pThis->m_bModified = FALSE;

    return hr;
}

STDMETHODIMP CToppBox::XPersistStreamInit::GetSizeMax(ULARGE_INTEGER*)
{
    return E_NOTIMPL;
}

STDMETHODIMP CToppBox::XPersistStreamInit::InitNew()
{
    METHOD_PROLOGUE_EX(CToppBox, PersistStreamInit)

    // Delegate to OnResetState.
//    pThis->OnResetState();

    // Unless IOleObject::SetClientSite is called after this, we can
    // count on ambient properties being available while loading.
//    pThis->m_bCountOnAmbients = TRUE;

    // Properties have been initialized
//    pThis->m_bInitialized = TRUE;

    // Uncache cached ambient properties
//    _afxAmbientCache->Cache(NULL);

    return S_OK;
}

可以发现,在上面的实现中,真正重要的保存和载入代码其实在CToppBox的函数LoadState和SaveState中,所以,添加这两个函数

HRESULT CToppBox::LoadState(IStream *pstm)
{
    DWORD dw = 0;
    pstm->Read(&m_width, sizeof(long), &dw);
    pstm->Read(&m_color, sizeof(OLE_COLOR), &dw);
    return S_OK;
}

HRESULT CToppBox::SaveState(IStream *pstm)
{
    DWORD dw = 0;
    pstm->Write(&m_width, sizeof(long), &dw);
    pstm->Write(&m_color, sizeof(OLE_COLOR), &dw);
    return S_OK;
}

另外,还需要GetClassID函数

HRESULT CToppBox::GetClassID(LPCLSID pclsid)
{
    //guid实际定义在IMPLEMENT_OLECREATE宏中
    *pclsid = guid;
    return NOERROR;
}
和成员变量
BOOL m_bModified;

接下来是添加IPersistStreamInit接口到接口映射表了

BEGIN_INTERFACE_MAP(CToppBox, CCmdTarget)
    INTERFACE_PART(CToppBox, IID_IToppBox, Dispatch)
    INTERFACE_PART(CToppBox, IID_IPersistStreamInit, PersistStreamInit)
END_INTERFACE_MAP()

11.好象是弄完了,东西太多了,也许会有漏写的,有兴趣的如果试试未果,烦请指出。

附上控件和属性页效果图(请注意里面的图片等内容是本系列前面几步中的效果)

你可能感兴趣的:(properties,null,Integer,mfc,interface,attributes)