这一步我们将持久化一个接口,可能有些难度,因为用到了很多前面讲过的东西。
在第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.好象是弄完了,东西太多了,也许会有漏写的,有兴趣的如果试试未果,烦请指出。
附上控件和属性页效果图(请注意里面的图片等内容是本系列前面几步中的效果)