现在的IM软件有很多种,采用Richedit实现占用的资源比较少,并且richedit的开发也比较容易。在这篇文章中,主要讨论richedit的高级应用和dynamicGif控件的永久化存储、保存到文件等功能。
dynamicGif控件进行了扩充,支持从流中创建对象和保存到文件。下面介绍如何保存控件中的文件:
BOOL SaveToFile( IOleObject* pOleObject, LPCWSTR wszDstFile)
{
// 要保存的目标文件名称
BOOL bRet = FALSE;
CComPtr<IDynamicGif> spDynGif;
HRESULT hr = pOleObject->QueryInterface( &spDynGif );
if( SUCCEEDED(hr) )
{
CComBSTR bstrFile;
// 注意在对象的生存期内,对应的文件不能删除,
// 否则在保存到文件或者拷贝时会失败
hr = spDynGif->GetFilePath( &bstrFile );
if( bstrFile.Length() > 0 )
{
bRet = CopyFileW( (BSTR)bstrFile,wszDstFile,FALSE);
}
}
retur bRet;
}
下面介绍Richedit在IM方面的一些高级应用,主要是Ole方面的内容,然后给出一个简单的实现。
首先介绍Richedit控件的序列化和CrichEditCtrl控件在对话框里的使用的注意事项。
1、 Richedit控件的序列化
Richedit控件中的内容保存到文件或者流中时,可以选择多种方式,比如纯文本、rtf等格式,可以通过设定参数完成。序列化包括读入和写出,对应两个消息:EM_STREAMIN和EM_STREAMOUT,CRichEditCtrl对这两个消息进行了简单的封装,分别是StreamIn()和StreamOut()两个函数。
下面给出MFC的实现,可扩展性还是很强的。
首先定义一个用于回调函数的结构,RichEditCookie:
class RichEditCookie
{
public:
CArchive& m_ar;
DWORD m_dwError;
RichEditCookie(CArchive& ar) : m_ar(ar) {m_dwError=0;}
};
然后定义如何序列化Richedit中的内容,Stream函数中采用CArchive作为序列化的源或者目标,可扩展性很强,只需要在构造CArchive对象时采用不同的文件类型,如CFile/CMemFile/CSharedFile,就可以从文件/内存/全局内存或资源中读取或写出。
这里Stream根据ar的类型来判断是读入还是写出,bSelection表示在读入数据时是替换选中的部分还是替换全部内容。nFormat是格式,以文本的格式还是以rtf格式输入/出数据。
void CMyRrichEditCtrl::Stream(CArchive& ar, BOOL bSelection,int nFormat)
{
EDITSTREAM es = {0, 0, EditStreamCallBack};
RichEditCookie cookie(ar);
es.dwCookie = (DWORD_PTR)&cookie;
if (bSelection)
nFormat |= SFF_SELECTION;
if (ar.IsStoring())
StreamOut(nFormat, es);
else
{
StreamIn(nFormat, es); }
if (cookie.m_dwError != 0)
AfxThrowFileException(cookie.m_dwError);
}
下面是回调函数的定义:
// return 0 for no error, otherwise return error code
DWORD CALLBACK EditStreamCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
RichEditCookie* pCookie = (RichEditCookie*)dwCookie;
CArchive& ar = pCookie->m_ar;
ar.Flush();
DWORD dw = 0;
*pcb = cb;
TRY
{
if (ar.IsStoring())
ar.GetFile()->Write(pbBuff, cb);
else
*pcb = ar.GetFile()->Read(pbBuff, cb);
}
CATCH(CFileException, e)
{
*pcb = 0;
pCookie->m_dwError = (DWORD)e->m_cause;
dw = 1;
DELETE_EXCEPTION(e);
}
AND_CATCH_ALL(e)
{
*pcb = 0;
pCookie->m_dwError = (DWORD)CFileException::generic;
dw = 1;
DELETE_EXCEPTION(e);
}
END_CATCH_ALL
return dw;
}
2、 Richedit在Ole部分的高级应用:
Richedit作为一个容器对象,允许用户设置一个回调接口,来完成Ole方面的一些支持,包括请求插入对象/获取IStorage接口/获取上下文菜单等。对于IRichEditOleCallback接口的详细定义,请参考msdn,这里仅讨论其和DynamicGif相关的实现。
首先讨论如何实现IRichEditOleCallback接口,这里为了方便起见,我采用MFC的中采用的COM接口实现方式,定义内嵌类,并且把比较复杂的接口实现委托给控件来实现。
下面给出的控件的详细定义:
class CMyRichEditCtrl : public CRichEditCtrl
{
DECLARE_DYNAMIC(CMyRichEditCtrl)
public:
CMyRichEditCtrl();
virtual ~CMyRichEditCtrl();
protected:
DECLARE_MESSAGE_MAP()
public:
// 采用内嵌类的方式,实现IRichEditOleCallback回调接口
BEGIN_INTERFACE_PART(RichEditOleCallback, IRichEditOleCallback)
INIT_INTERFACE_PART(CRichEditView, RichEditOleCallback)
STDMETHOD(GetNewStorage) (LPSTORAGE*);
STDMETHOD(GetInPlaceContext) (LPOLEINPLACEFRAME*,
LPOLEINPLACEUIWINDOW*,
LPOLEINPLACEFRAMEINFO);
STDMETHOD(ShowContainerUI) (BOOL);
STDMETHOD(QueryInsertObject) (LPCLSID, LPSTORAGE, LONG);
STDMETHOD(DeleteObject) (LPOLEOBJECT);
STDMETHOD(QueryAcceptData) (LPDATAOBJECT, CLIPFORMAT*, DWORD,BOOL, HGLOBAL);
STDMETHOD(ContextSensitiveHelp) (BOOL);
STDMETHOD(GetClipboardData) (CHARRANGE*, DWORD, LPDATAOBJECT*);
STDMETHOD(GetDragDropEffect) (BOOL, DWORD, LPDWORD);
STDMETHOD(GetContextMenu) (WORD, LPOLEOBJECT, CHARRANGE*, HMENU*);
END_INTERFACE_PART(RichEditOleCallback)
DECLARE_INTERFACE_MAP()
protected:
virtual void PreSubclassWindow();
public:
afx_msg void OnOleSave2file();
// 回调接口的实现,根据不同的上下文获取不同的右键菜单
HMENU GetContextMenu( WORD seltype, LPOLEOBJECT lpoleobj, CHARRANGE* lpchrg);
};
// CPP文件中的实现部分
IMPLEMENT_DYNAMIC(CMyRichEditCtrl, CRichEditCtrl)
CMyRichEditCtrl::CMyRichEditCtrl()
{
EnableActiveAccessibility();
}
CMyRichEditCtrl::~CMyRichEditCtrl()
{
}
BEGIN_INTERFACE_MAP(CMyRichEditCtrl, CRichEditCtrl)
// we use IID_IUnknown because richedit doesn't define an IID
INTERFACE_PART(CMyRichEditCtrl, IID_IUnknown, RichEditOleCallback)
END_INTERFACE_MAP()
STDMETHODIMP_(ULONG) CMyRichEditCtrl::XRichEditOleCallback::AddRef()
{
METHOD_PROLOGUE_EX_(CMyRichEditCtrl, RichEditOleCallback)
return (ULONG)pThis->InternalAddRef();
}
STDMETHODIMP_(ULONG) CMyRichEditCtrl::XRichEditOleCallback::Release()
{
METHOD_PROLOGUE_EX_(CMyRichEditCtrl, RichEditOleCallback)
return (ULONG)pThis->InternalRelease();
}
STDMETHODIMP CMyRichEditCtrl::XRichEditOleCallback::QueryInterface(
REFIID iid, LPVOID* ppvObj)
{
METHOD_PROLOGUE_EX_(CMyRichEditCtrl, RichEditOleCallback)
return (HRESULT)pThis->InternalQueryInterface(&iid, ppvObj);
}
STDMETHODIMP CMyRichEditCtrl::XRichEditOleCallback::GetNewStorage(LPSTORAGE* ppstg)
{
METHOD_PROLOGUE_EX_(CMyRichEditCtrl, RichEditOleCallback)
COleClientItem item;
item.GetItemStorageFlat();
*ppstg = item.m_lpStorage;
HRESULT hRes = E_OUTOFMEMORY;
if (item.m_lpStorage != NULL)
{
item.m_lpStorage = NULL;
hRes = S_OK;
}
return hRes;
}
STDMETHODIMP CMyRichEditCtrl::XRichEditOleCallback::GetInPlaceContext(
LPOLEINPLACEFRAME* lplpFrame, LPOLEINPLACEUIWINDOW* lplpDoc,
LPOLEINPLACEFRAMEINFO lpFrameInfo)
{
METHOD_PROLOGUE_EX(CMyRichEditCtrl, RichEditOleCallback)
ATLTRACENOTIMPL(_T("CMyRichEditCtrl::XRichEditOleCallback::GetInPlaceContext()/r/n"));
}
STDMETHODIMP CMyRichEditCtrl::XRichEditOleCallback::ShowContainerUI(BOOL fShow)
{
METHOD_PROLOGUE_EX(CMyRichEditCtrl, RichEditOleCallback)
ATLTRACENOTIMPL(_T("CMyRichEditCtrl::XRichEditOleCallback::ShowContainerUI()/r/n"));
}
STDMETHODIMP CMyRichEditCtrl::XRichEditOleCallback::QueryInsertObject(
LPCLSID /*lpclsid*/, LPSTORAGE /*pstg*/, LONG /*cp*/)
{
METHOD_PROLOGUE_EX(CMyRichEditCtrl, RichEditOleCallback)
ATLTRACE(_T("CMyRichEditCtrl::XRichEditOleCallback::QueryInsertObject()/r/n"));
return S_OK;
}
STDMETHODIMP CMyRichEditCtrl::XRichEditOleCallback::DeleteObject(LPOLEOBJECT /*lpoleobj*/)
{
METHOD_PROLOGUE_EX_(CMyRichEditCtrl, RichEditOleCallback)
ATLTRACENOTIMPL(_T("CMyRichEditCtrl::XRichEditOleCallback::DeleteObject()/r/n"));
}
STDMETHODIMP CMyRichEditCtrl::XRichEditOleCallback::QueryAcceptData(
LPDATAOBJECT lpdataobj, CLIPFORMAT* lpcfFormat, DWORD reco,
BOOL fReally, HGLOBAL hMetaPict)
{
METHOD_PROLOGUE_EX(CMyRichEditCtrl, RichEditOleCallback)
ATLTRACENOTIMPL(_T("CMyRichEditCtrl::XRichEditOleCallback::QueryAcceptData()/r/n"));
}
STDMETHODIMP CMyRichEditCtrl::XRichEditOleCallback::ContextSensitiveHelp(BOOL /*fEnterMode*/)
{
ATLTRACENOTIMPL(_T("CMyRichEditCtrl::XRichEditOleCallback::ContextSensitiveHelp()/r/n"));
}
STDMETHODIMP CMyRichEditCtrl::XRichEditOleCallback::GetClipboardData(
CHARRANGE* lpchrg, DWORD reco, LPDATAOBJECT* lplpdataobj)
{
METHOD_PROLOGUE_EX(CMyRichEditCtrl, RichEditOleCallback)
ATLTRACENOTIMPL(_T("CMyRichEditCtrl::XRichEditOleCallback::GetClipboardData()/r/n"));
}
STDMETHODIMP CMyRichEditCtrl::XRichEditOleCallback::GetDragDropEffect(
BOOL fDrag, DWORD grfKeyState, LPDWORD pdwEffect)
{
return E_NOTIMPL;
}
STDMETHODIMP CMyRichEditCtrl::XRichEditOleCallback::GetContextMenu(
WORD seltype, LPOLEOBJECT lpoleobj, CHARRANGE* lpchrg,
HMENU* lphmenu)
{
METHOD_PROLOGUE_EX(CMyRichEditCtrl, RichEditOleCallback)
ATLTRACE(_T("CMyRichEditCtrl::XRichEditOleCallback::GetContextMenu()/r/n"));
// 这里把右键菜单的处理委托给控件来处理
HMENU hMenu = pThis->GetContextMenu(seltype, lpoleobj, lpchrg);
if (hMenu == NULL)
return E_NOTIMPL;
*lphmenu = hMenu;
return S_OK;
}
// CMyRichEditCtrl 消息处理程序
void CMyRichEditCtrl::PreSubclassWindow()
{
SetEventMask(ENM_SELCHANGE | ENM_CHANGE | ENM_SCROLL);
// 在这里设置回调接口
VERIFY(SetOLECallback(&m_xRichEditOleCallback));
CRichEditCtrl::PreSubclassWindow();
}
// 回调接口的实现,根据不同的上下文获取不同的右键菜单
HMENU CMyRichEditCtrl::GetContextMenu( WORD seltype, LPOLEOBJECT lpoleobj, CHARRANGE* lpchrg)
{
ATLTRACE(_T("CMyRichEditCtrl::GetContextMenu()/r/n"));
CMenu menu,*pSubMenu = NULL;
HMENU hMenu = NULL;
if( seltype == SEL_OBJECT )
{
menu.LoadMenu( IDR_MENU1 );
pSubMenu = menu.GetSubMenu( 0 );
POINT pt;
GetCursorPos( &pt );
DWORD dwCmd = pSubMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_TOPALIGN|TPM_RETURNCMD,pt.x,pt.y,this);
if( dwCmd == ID_OLE_SAVE2FILE )
{
CComPtr<IDynamicGif> spDynGif;
lpoleobj->QueryInterface( &spDynGif );
if( spDynGif ){
CComBSTR bstrFile;
// 保存文件到另外一个文件,这里控件根据控件中文件类型的不同设置
// 不同的扩展名,如果采用对话框的形式保存文件时注意分析文件的扩展名,来正确的保存文件类型。
spDynGif->GetFilePath( &bstrFile );
CopyFileW( (BSTR)bstrFile,L"d://1111.gif",FALSE );
}
}
}
return hMenu;
}
上面的代码中基本上都有注释,这里不作进一步的讨论。
3、 通过上面两步,基本的界面功能就能够实现了,如果仅在windows平台上的简单聊天工具,通过stream方式导出/导入信息就可以了,不过这样没有可扩展性,不利于跨平台扩展。如果有时间,我会在下一篇文章中给出一个简单的协议实现和一个比较完备的实现。(这篇文章写作比较匆忙,以后有时间详细解释一下)。