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

来源:互联网 发布:国家税务局网络 编辑:程序博客网 时间:2024/05/29 13:22
 一般情况下,我们在设计状态下设计控件时,会打开属性页,然后输入几个值,来初始化控件,然后关闭属性页,这样在属性页中输入的值就保存起来了,然后下次再打开属性页,这些值又会加载进来,这个过程有个术语叫持久化。
从表面上看,控件的持久化是在属性页中完成的呢,但事实上控件的持久化需要各方的配合,最终是由控件的容器来完成真正的持久化操作(保存或加载到某种介质,如磁盘文件等)。
属性页在控件的持久化中并没有做多少的工作,它的主要工作是获得和设置控件的属性(有时候可能会调用控件的方法等),在MFC中,一般基本上是在DoDataExchange中完成的(加载控件的时候会调用,保存控件的时候也会调用),有时候,可能会在其它地方完成,前面几步已经讲了在其它地方访问控件属性的方法了。
控件做了大部分的持久化工作,在MFC中,它们是在DoPropExchange中完成的。MFC提供了下面的一些函数来帮助完成持久化工作

BOOL AFX_CDECL PX_Short(CPropExchange* pPX, LPCTSTR pszPropName, short& sValue);

BOOL AFX_CDECL PX_Short(CPropExchange* pPX, LPCTSTR pszPropName, short& sValue,
    short sDefault);

BOOL AFX_CDECL PX_UShort(CPropExchange* pPX, LPCTSTR pszPropName, USHORT& usValue);

BOOL AFX_CDECL PX_UShort(CPropExchange* pPX, LPCTSTR pszPropName, USHORT& usValue,
    USHORT usDefault);

BOOL AFX_CDECL PX_Long(CPropExchange* pPX, LPCTSTR pszPropName, long& lValue);

BOOL AFX_CDECL PX_Long(CPropExchange* pPX, LPCTSTR pszPropName, long& lValue,
    long lDefault);

BOOL AFX_CDECL PX_ULong(CPropExchange* pPX, LPCTSTR pszPropName, ULONG& ulValue);

BOOL AFX_CDECL PX_ULong(CPropExchange* pPX, LPCTSTR pszPropName, ULONG& ulValue,
    ULONG ulDefault);

BOOL AFX_CDECL PX_Color(CPropExchange* pPX, LPCTSTR pszPropName, OLE_COLOR& clrValue);

BOOL AFX_CDECL PX_Color(CPropExchange* pPX, LPCTSTR pszPropName, OLE_COLOR& clrValue,
    OLE_COLOR clrDefault);

BOOL AFX_CDECL PX_Bool(CPropExchange* pPX, LPCTSTR pszPropName, BOOL& bValue);

BOOL AFX_CDECL PX_Bool(CPropExchange* pPX, LPCTSTR pszPropName, BOOL& bValue,
    BOOL bDefault);

BOOL AFX_CDECL PX_String(CPropExchange* pPX, LPCTSTR pszPropName, CString& strValue);

BOOL AFX_CDECL PX_String(CPropExchange* pPX, LPCTSTR pszPropName, CString& strValue,
    const CString& strDefault);
BOOL AFX_CDECL PX_String(CPropExchange* pPX, LPCTSTR pszPropName, CString& strValue,
    LPCTSTR lpszDefault);

BOOL AFX_CDECL PX_Currency(CPropExchange* pPX, LPCTSTR pszPropName, CY& cyValue);

BOOL AFX_CDECL PX_Currency(CPropExchange* pPX, LPCTSTR pszPropName, CY& cyValue,
    CY cyDefault);

BOOL AFX_CDECL PX_Float(CPropExchange* pPX, LPCTSTR pszPropName, float& floatValue);

BOOL AFX_CDECL PX_Float(CPropExchange* pPX, LPCTSTR pszPropName, float& floatValue,
    float floatDefault);

BOOL AFX_CDECL PX_Double(CPropExchange* pPX, LPCTSTR pszPropName, double& doubleValue);

BOOL AFX_CDECL PX_Double(CPropExchange* pPX, LPCTSTR pszPropName, double& doubleValue,
    double doubleDefault);

BOOL AFX_CDECL PX_Blob(CPropExchange* pPX, LPCTSTR pszPropName, HGLOBAL& hBlob,
    HGLOBAL hBlobDefault = NULL);

BOOL AFX_CDECL PX_Font(CPropExchange* pPX, LPCTSTR pszPropName, CFontHolder& font,
    const FONTDESC* pFontDesc = NULL,
    LPFONTDISP pFontDispAmbient = NULL);

BOOL AFX_CDECL PX_Picture(CPropExchange* pPX, LPCTSTR pszPropName,
    CPictureHolder& pict);

BOOL AFX_CDECL PX_Picture(CPropExchange* pPX, LPCTSTR pszPropName,
    CPictureHolder& pict, CPictureHolder& pictDefault);

BOOL AFX_CDECL PX_IUnknown(CPropExchange* pPX, LPCTSTR pszPropName, LPUNKNOWN& pUnk,
    REFIID iid, LPUNKNOWN pUnkDefault = NULL);

BOOL AFX_CDECL PX_VBXFontConvert(CPropExchange* pPX, CFontHolder& font);

BOOL AFX_CDECL PX_DataPath(CPropExchange* pPX, LPCTSTR pszPropName,
    CDataPathProperty& dataPathProp, LPCTSTR pszDefault = NULL);

BOOL AFX_CDECL PX_DataPath(CPropExchange* pPX, LPCTSTR pszPropName,
    CDataPathProperty& dataPathProp, const CString& strDefault);

看起来很多,其实最终都是调用到了CPropExchange的几个函数
    virtual BOOL ExchangeVersion(DWORD& dwVersionLoaded, DWORD dwVersionDefault,
        BOOL bConvert);

    virtual BOOL ExchangeProp(LPCTSTR pszPropName, VARTYPE vtProp,
                void* pvProp, const void* pvDefault = NULL) = 0;
    virtual BOOL ExchangeBlobProp(LPCTSTR pszPropName, HGLOBAL* phBlob,
                HGLOBAL hBlobDefault = NULL) = 0;
    virtual BOOL ExchangeFontProp(LPCTSTR pszPropName, CFontHolder& font,
                const FONTDESC* pFontDesc,
                LPFONTDISP pFontDispAmbient) = 0;
    virtual BOOL ExchangePersistentProp(LPCTSTR pszPropName,
                LPUNKNOWN* ppUnk, REFIID iid, LPUNKNOWN pUnkDefault) = 0;
或者更确切些说,只有三个比较重要的,其它的也都是简单的调用下面三个函数的
ExchangeProp,ExchangeBlobProp,ExchangePersistentProp

好象有点说远了,回到主题:一般的属性持久化,就不多说了,这里要说的是无法用普通属性的方法来持久化的控件中的信息,该如何持久化呢。
我们来看上一步中的例子topp,我想持久化里面的字符串数组,怎么办呢?
办法是有的,用PX_Blob,可以说,PX_Blob几乎能持久化任何你想要持久化的信息。

首先,为了能看到持久化后的效果,我们来稍微调整一下控件
1.添加OnDraw代码,以显示一个以Color属性为底色的背景,然后一行一行显示所有的字符串,有几个字符串就显示几行

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);*/
    pdc->FillRect(&rcBounds, &CBrush(TranslateColor(m_color)));
    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]);
    }
}

2.调整几个控件的属性代码,以便更改属性时,能反应到界面上来,说白了,就是加几个InvaldiateControl()

void CToppCtrl::OnColorChanged()
{
    // TODO: Add notification handler code
    InvalidateControl();
    SetModifiedFlag();
}

long CToppCtrl::Add(LPCTSTR strItem)
{
    // TODO: Add your dispatch handler code here
    long l = m_saItems.Add(strItem);
    InvalidateControl();
    return l;
    return m_saItems.Add(strItem);
    return 0;
}

void CToppCtrl::SetItem(long nItem, LPCTSTR lpszNewValue)
{
    // TODO: Add your property handler here
    if(nItem >= 0 && nItem < m_saItems.GetSize()){
        m_saItems[nItem] = lpszNewValue;
    }
    InvalidateControl();
    SetModifiedFlag();
}

3.调整属性页中的几个函数,使它们还原为用第9步所讲的方法来调用,为什么要这样做,很简单,也是为了要调用到InvlidateControl()。
第10步中所讲的方法都是直接访问内部成员变量了,如果一定要这么做的话,还得在属性页中访问成员变量之后,再调用一下COleControl的InvalidateControl()了,这感觉上就有些乱了。

BOOL CToppPropPage::AddItem(LPCTSTR lpszItem)
{
    USES_CONVERSION;
    COleDispatchDriver PropDispDriver;
    BOOL bResult = FALSE;
    ULONG nObjects = 0;
    LPDISPATCH* ppDisp = GetObjectArray(&nObjects);
    for (ULONG i = 0; i < nObjects; i++)
    {
        DISPID dwDispID;
        LPCOLESTR lpOleStr = T2COLE("Add");
        if (SUCCEEDED(ppDisp[i]->GetIDsOfNames(IID_NULL, (LPOLESTR*)&lpOleStr, 1, 0, &dwDispID)))
        {
            PropDispDriver.AttachDispatch(ppDisp[i], FALSE);
            BYTE rgbParams[] = VTS_BSTR;
            long lret = 0;
            PropDispDriver.InvokeHelper(dwDispID, DISPATCH_METHOD, VT_I4, &lret, rgbParams, lpszItem);
            PropDispDriver.DetachDispatch();
            bResult = TRUE;
        }
    }
    return bResult;
}

CString CToppPropPage::GetItem(long lItem)
{
    CString str = _T("");
    USES_CONVERSION;
    COleDispatchDriver PropDispDriver;
    ULONG nObjects = 0;
    LPDISPATCH* ppDisp = GetObjectArray(&nObjects);
    for (ULONG i = 0; i < nObjects; i++)
    {
        DISPID dwDispID;
        LPCOLESTR lpOleStr = T2COLE("Item");
        if (SUCCEEDED(ppDisp[i]->GetIDsOfNames(IID_NULL, (LPOLESTR*)&lpOleStr, 1, 0, &dwDispID)))
        {
            PropDispDriver.AttachDispatch(ppDisp[i], FALSE);
            BYTE rgbParams[] = VTS_I4;
            PropDispDriver.InvokeHelper(dwDispID, DISPATCH_PROPERTYGET, VT_BSTR, &str, rgbParams, lItem);
            PropDispDriver.DetachDispatch();
        }
    }
    return str;
}

COLORREF CToppPropPage::GetColor()
{
    long l = 0;
    GetPropText("Color", &l);
    COLORREF cr = RGB(0, 0, 0);
    ::OleTranslateColor((OLE_COLOR)l, NULL, &cr);
    return cr;
}

void CToppPropPage::SetColor(COLORREF cr)
{
    SetPropText("Color", cr);
}

long CToppPropPage::GetCount()
{
    long lcount = 0;
    GetPropText("Count", &lcount);
    return lcount;
}


4.增加一个Remove方法,免得字符串越加越多了:)

void CToppCtrl::Remove(long lItem)
{
    // TODO: Add your dispatch handler code here
    m_saItems.RemoveAt(lItem);
    InvalidateControl();
}

5.在属性页中,增加一个按钮("删除项目"),添加一函数void RemoveItem(long lItem)和按钮处理函数

void CToppPropPage::RemoveItem(long lItem)
{
    USES_CONVERSION;
    COleDispatchDriver PropDispDriver;
    ULONG nObjects = 0;
    LPDISPATCH* ppDisp = GetObjectArray(&nObjects);
    for (ULONG i = 0; i < nObjects; i++)
    {
        DISPID dwDispID;
        LPCOLESTR lpOleStr = T2COLE("Remove");
        if (SUCCEEDED(ppDisp[i]->GetIDsOfNames(IID_NULL, (LPOLESTR*)&lpOleStr, 1, 0, &dwDispID)))
        {
            PropDispDriver.AttachDispatch(ppDisp[i], FALSE);
            BYTE rgbParams[] = VTS_I4;
            long lret = 0;
            PropDispDriver.InvokeHelper(dwDispID, DISPATCH_METHOD, VT_EMPTY, &lret, rgbParams, lItem);
            PropDispDriver.DetachDispatch();
        }
    }
}

void CToppPropPage::OnButtonRemoveitem()
{
    // TODO: Add your control notification handler code here
    int n = m_list.GetCurSel();
    if(n >= 0){
        RemoveItem(n);
    }

    long lcount = GetCount();
    m_list.ResetContent();
    for(int i=0; i<lcount; i++){
        m_list.AddString(GetItem(i));
    }
}

编译一下,运行,就可以发现在属性页中的任何修改都能影响到控件的显示,可惜的是控件重新打开时还是空白一片,显然还需要持久化的支持。
那么现在我们就切入正题,添加持久化。

1.增加函数BOOL PX_Items(CPropExchange* pPX),用来持久化m_saItems
这里要注意的是
BOOL PX_Blob( CPropExchange* pPX, LPCTSTRpszPropName, HGLOBAL&hBlob, HGLOBALhBlobDefault = NULL );
函数中的hBlob内存块的开关必须是一个DWORD类型的长度,表示后面实际数据的字节数。

BOOL CToppCtrl::PX_Items(CPropExchange *pPX)
{
    if(!pPX->IsLoading())
    {
        DWORD cbGuess = 5000;//分配足够大的内存空间来存放你要保存的信息
        HANDLE hMem = GlobalAlloc(GPTR, cbGuess);
        if(hMem != NULL)
        {
            BYTE* pMem = (BYTE*)hMem;
            //任何能够保存在文件中的信息,都能保存在控件的持久化中
            CSharedFile file;
            //pMem必须以一个DWORD带头,表示接下来的数据块的字节数
            file.Attach(pMem + sizeof(DWORD), cbGuess-sizeof(DWORD));
            CArchive ar(&file, CArchive::store);
            int n = m_saItems.GetSize();
            ar << n;
            for(int i=0; i<n; i++){
                CString str = m_saItems[i];
                ar << str;
            }
            ar.Close();
            *(DWORD*)pMem = file.GetLength();
            file.Close();
            PX_Blob(pPX, _T("_Items"), hMem);
            GlobalFree(hMem);
            hMem = NULL;
        }
        else{
            return FALSE;
        }
    }
    else{
        HANDLE hMem = NULL;
        PX_Blob(pPX, _T("_Items"), hMem);
        if(hMem){
            BYTE* pMem = (BYTE*)GlobalLock(hMem);
            CSharedFile file;
            ULONG ul = GlobalSize(hMem);
            file.Attach(pMem+sizeof(DWORD), ul-sizeof(DWORD));
            CArchive ar(&file, CArchive::load);
            int n = 0;
            ar >> n;
            for(int i=0; i<n; i++){
                CString str;
                ar >> str;
                m_saItems.Add(str);
            }
            ar.Close();
            file.Close();
            GlobalUnlock(hMem);
            GlobalFree(hMem);
            hMem = NULL;
        }
        else{
            //这里初始化数据,我们的m_saItems不用初始化什么
        }
    }
    return TRUE;
}
//这里"_Items"可以是任何的字符串,不过最好别和已经在在的属性相冲突,所以加了个'_',它的作用只是挂个名而已,基本无用处。

2.为DoPropExchange添加代码,这里只需要保存Color属性和m_saItems数据就可以了。

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_Items(pPX);
}

3.在属性页的OnInitDialog中填充列表框,这样属性页一打开,就能在列表框中见到m_saItems的内容了

BOOL CToppPropPage::OnInitDialog()
{
    COlePropertyPage::OnInitDialog();
   
    // TODO: Add extra initialization here
    //填充列表框的过程在各处都有,因此最好写成函数,这里演示用,就展开写了
    long lcount = GetCount();
    m_list.ResetContent();
    for(int i=0; i<lcount; i++){
        m_list.AddString(GetItem(i));
    }
    return TRUE;  // return TRUE unless you set the focus to a control
                  // EXCEPTION: OCX Property Pages should return FALSE
}

好象完了,编译运行就可以了,效果自个看了。

附上属性页图片和控件的效果图,因为是过一段时间后附上的,可能加上了其它的效果,请自行判断,也可以看看本系列文章后面内容。