一、引言
用 MSN 和 QQ 等聊天的時候,當用戶輸入特定意義的字符串時,系統回自動用一張小圖片替代.比如輸入" : ) "系統
會用一個小笑臉代替。我要實現的就是這樣一個信息輸入框 。這個信息輸入框由兩部分組成:圖案選擇器和多功能文本框。本篇介紹多功能文本框。
二、原理簡介
1、主要功能用CRichEditCtrl實現,像設置字體,設置字體顏色,字號等等CRichEditCtrl都提供了很完善的支持,我就不一一贅述了。
CRichEditCtrl 主要的不足在於以下幾個方面:
我擴展了CRichEditCtrl類CRichEditCtrlEx實現了上述功能.參考了很多網上的文章,對所有公開源碼的開發人員表示崇高的敬意!!
2、實現右鍵菜單:
///生成右鍵菜單
void CRichEditCtrlEx::OnRButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
//設置為焦點
SetFocus();
//創建一個彈出式菜單
CMenu popmenu;
popmenu.CreatePopupMenu();
//添加菜單項目
popmenu.AppendMenu(0, ID_RICH_UNDO, "&Undo");
popmenu.AppendMenu(0, MF_SEPARATOR);
popmenu.AppendMenu(0, ID_RICH_CUT, "&Cut");
popmenu.AppendMenu(0, ID_RICH_COPY, "C&opy");
popmenu.AppendMenu(0, ID_RICH_PASTE, "&Paste");
popmenu.AppendMenu(0, ID_RICH_CLEAR, "C&lear");
popmenu.AppendMenu(0, MF_SEPARATOR);
popmenu.AppendMenu(0, ID_RICH_SELECTALL, "Select &All");
popmenu.AppendMenu(0, MF_SEPARATOR);
popmenu.AppendMenu(0, ID_RICH_SETFONT, "Select &Font");
//初始化菜單項
UINT nUndo=(CanUndo() ? 0 : MF_GRAYED );
popmenu.EnableMenuItem(ID_RICH_UNDO, MF_BYCOMMAND|nUndo);
UINT nSel=((GetSelectionType()!=SEL_EMPTY) ? 0 : MF_GRAYED) ;
popmenu.EnableMenuItem(ID_RICH_CUT, MF_BYCOMMAND|nSel);
popmenu.EnableMenuItem(ID_RICH_COPY, MF_BYCOMMAND|nSel);
popmenu.EnableMenuItem(ID_RICH_CLEAR, MF_BYCOMMAND|nSel);
UINT nPaste=(CanPaste() ? 0 : MF_GRAYED) ;
popmenu.EnableMenuItem(ID_RICH_PASTE, MF_BYCOMMAND|nPaste);
//顯示菜單
CPoint pt;
GetCursorPos(&pt);
popmenu.TrackPopupMenu(TPM_RIGHTBUTTON, pt.x, pt.y, this);
popmenu.DestroyMenu();
CRichEditCtrl::OnRButtonDown(nFlags, point);
CRichEditCtrl::OnRButtonUp(nFlags, point);
}
3、關於如何把圖片插入到RichEdit中,國外由很多文章介紹,都是(我看到的都是)通過插入OLE對象來實現.主要用兩個函數,還涉及到了和多接口的調用。
(1)從文件創建OLE對象OleCreateFromFile();
void CRichEditCtrlEx::InsertBitmap(CString szFileName)
{
USES_CONVERSION;
SCODE sc = ::CreateILockBytesOnHGlobal(NULL, TRUE, &m_lpLockBytes);
if (sc != S_OK)
AfxThrowOleException(sc);
ASSERT(m_lpLockBytes != NULL);
sc = ::StgCreateDocfileOnILockBytes(m_lpLockBytes,
STGM_SHARE_EXCLUSIVE|STGM_CREATE|STGM_READWRITE, 0, &m_lpStorage);
if (sc != S_OK)
{
VERIFY(m_lpLockBytes->Release() == 0);
m_lpLockBytes = NULL;
AfxThrowOleException(sc);
}
// attempt to create the object
sc = ::OleCreateFromFile(CLSID_NULL, T2COLE(szFileName),
IID_IUnknown, OLERENDER_DRAW, NULL, NULL,
m_lpStorage, (void **)&m_lpObject);
if ( sc != S_OK )
{
TCHAR * lpMsgBuf;
::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM, NULL,
::GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf, 0, NULL );
CString msg( lpMsgBuf );
msg += _T("\n\n\nThe following file, created in\n"
"Simulation->Plot, may be missing due\n"
"to not doing a File->Save Workspace:\n\n" );
msg += szFileName;
AfxMessageBox( msg, MB_OK );
::LocalFree( lpMsgBuf );
return;
}
// m_lpObject is currently an IUnknown, convert to IOleObject
if (m_lpObject != NULL)
{
LPUNKNOWN lpUnk = m_lpObject;
m_lpObject = QUERYINTERFACE(lpUnk, IOleObject);
lpUnk->Release();
if (m_lpObject == NULL)
AfxThrowOleException(E_OUTOFMEMORY);
}
// cache the IViewObject interface
m_lpViewObject = QUERYINTERFACE(m_lpObject, IViewObject2);
if (m_lpViewObject == NULL)
return;
// setup for advises; we assume that OLE cleans them up properly
LPADVISESINK lpAdviseSink =
(LPADVISESINK)GetInterface(&IID_IAdviseSink);
// set up view advise
VERIFY(m_lpViewObject->SetAdvise(DVASPECT_CONTENT, 0, lpAdviseSink)
== S_OK);
// the server shows these in its user-interface
// (as document title and in File Exit menu)
m_lpObject->SetHostNames(T2COLE(AfxGetAppName()),
T2COLE(_T("Test")));
// all items are "contained" -- this makes our reference to this object
// weak -- which is needed for links to embedding silent update.
OleSetContainedObject(m_lpObject, TRUE);
CHARRANGE cr;
this->GetSel( cr );
cr.cpMin = cr.cpMax -1;
this->SetSel( cr );
REOBJECT reo;
memset( &reo, 0, sizeof( reo ) );
reo.cbStruct = sizeof( reo );
CLSID classID;
if ( m_lpObject->GetUserClassID( &classID ) != S_OK)
classID = CLSID_NULL;
reo.clsid = classID;
reo.cp = REO_CP_SELECTION;
reo.poleobj = m_lpObject;
reo.pstg = m_lpStorage;
LPOLECLIENTSITE lpClientSite;
this->GetIRichEditOle()->GetClientSite( &lpClientSite );
reo.polesite = lpClientSite;
SIZEL sizel;
sizel.cx = sizel.cy = 0; // let richedit determine initial size
reo.sizel = sizel;
reo.dvaspect = DVASPECT_CONTENT;
reo.dwFlags = REO_RESIZABLE;
reo.dwUser = 0;
HRESULT hr = this->GetIRichEditOle()->InsertObject( &reo );
}
(2)根據位圖句柄創建OleCreateStaticFromData();用這個函數可以把資源中的圖片插入到文本框中
void CRichEditCtrlEx::InsertBitmap(HBITMAP hBitmap)
{
STGMEDIUM stgm;
stgm.tymed = TYMED_GDI; // Storage medium = HBITMAP handle
stgm.hBitmap = hBitmap;
stgm.pUnkForRelease = NULL; // Use ReleaseStgMedium
FORMATETC fm;
fm.cfFormat = CF_BITMAP; // Clipboard format = CF_BITMAP
fm.ptd = NULL; // Target Device = Screen
fm.dwAspect = DVASPECT_CONTENT; // Level of detail = Full content
fm.lindex = -1; // Index = Not applicaple
fm.tymed = TYMED_GDI;
////創建輸入數據源
IStorage *pStorage;
///分配內存
LPLOCKBYTES lpLockBytes = NULL;
SCODE sc = ::CreateILockBytesOnHGlobal(NULL, TRUE, &lpLockBytes);
if (sc != S_OK)
AfxThrowOleException(sc);
ASSERT(lpLockBytes != NULL);
sc = ::StgCreateDocfileOnILockBytes(lpLockBytes,
STGM_SHARE_EXCLUSIVE|STGM_CREATE|STGM_READWRITE, 0, &pStorage);
if (sc != S_OK)
{
VERIFY(lpLockBytes->Release() == 0);
lpLockBytes = NULL;
AfxThrowOleException(sc);
}
ASSERT(pStorage != NULL);
COleDataSource *pDataSource = new COleDataSource;
pDataSource->CacheData(CF_BITMAP, &stgm);
LPDATAOBJECT lpDataObject =
(LPDATAOBJECT)pDataSource->GetInterface(&IID_IDataObject);
///獲取RichEdit的OLEClientSite
LPOLECLIENTSITE lpClientSite;
this->GetIRichEditOle()->GetClientSite( &lpClientSite );
///創建OLE對象
IOleObject *pOleObject;
sc = OleCreateStaticFromData(lpDataObject,IID_IOleObject,OLERENDER_FORMAT,
&fm,lpClientSite,pStorage,(void **)&pOleObject);
if(sc!=S_OK)
AfxThrowOleException(sc);
///插入OLE對象
REOBJECT reobject;
ZeroMemory(&reobject, sizeof(REOBJECT));
reobject.cbStruct = sizeof(REOBJECT);
CLSID clsid;
sc = pOleObject->GetUserClassID(&clsid);
if (sc != S_OK)
AfxThrowOleException(sc);
reobject.clsid = clsid;
reobject.cp = REO_CP_SELECTION;
reobject.dvaspect = DVASPECT_CONTENT;
reobject.poleobj = pOleObject;
reobject.polesite = lpClientSite;
reobject.pstg = pStorage;
HRESULT hr = this->GetIRichEditOle()->InsertObject( &reobject );
}
4、讀取/寫入RTF格式字符串
CRichEditCtrl 提供了兩個函數StreamIn()和StreamOut()來實現這個功能,輸出的內容包含文本信息和字體信息。我把這兩個函數重新包裝了一下 ,用GetRTF()把格式文本返回到一個CString變量中SetRTF(CString )實現逆過程。具體代碼參看本文附帶的工程文件。
三、到此,這個多功能文本框就已經基本能滿足我的要求了。但是如何選擇表情符號? 如何自動替換? 還是個問題。(待續)