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

全部博文(90)

文章存档

2015年(1)

2011年(21)

2010年(59)

2009年(9)

我的朋友

分类: C/C++

2010-07-14 10:04:45

一般情况下,我们在设计状态下设计控件时,会打开属性页,然后输入几个值,来初始化控件,然后关闭属性页,这样在属性页中输入的值就保存起来了,然后下次再打开属性页,这些值又会加载进来,这个过程有个术语叫持久化。
从表面上看,控件的持久化是在属性页中完成的呢,但事实上控件的持久化需要各方的配合,最终是由控件的容器来完成真正的持久化操作(保存或加载到某种介质,如磁盘文件等)。
属性页在控件的持久化中并没有做多少的工作,它的主要工作是获得和设置控件的属性(有时候可能会调用控件的方法等),在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        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        m_list.AddString(GetItem(i));
    }
}
编译一下,运行,就可以发现在属性页中的任何修改都能影响到控件的显示,可惜的是控件重新打开时还是空白一片,显然还需要持久化的支持。
那么现在我们就切入正题,添加持久化。
1.增加函数BOOL PX_Items(CPropExchange* pPX),用来持久化m_saItems
这里要注意的是
BOOL PX_Blob( CPropExchange* pPX, LPCTSTR pszPropName, HGLOBAL& hBlob, HGLOBAL hBlobDefault = 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                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                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        m_list.AddString(GetItem(i));
    }
    return TRUE;  // return TRUE unless you set the focus to a control
                  // EXCEPTION: OCX Property Pages should return FALSE
}
好象完了,编译运行就可以了,效果自个看了。
附上属性页图片和控件的效果图,因为是过一段时间后附上的,可能加上了其它的效果,请自行判断,也可以看看本系列文章后面内容。

 
 
阅读(1893) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~