Chinaunix首页 | 论坛 | 博客
  • 博客访问: 6062334
  • 博文数量: 1227
  • 博客积分: 10026
  • 博客等级: 上将
  • 技术积分: 20273
  • 用 户 组: 普通用户
  • 注册时间: 2008-01-16 12:40
文章分类

全部博文(1227)

文章存档

2010年(1)

2008年(1226)

我的朋友

分类: C/C++

2008-04-23 21:47:03

类似 MSN 信息发送框的制作(上)


作者:北方工业大学




一、引言
   用 MSN 和 QQ 等聊天的时候,当用户输入特定意义的字符串时,系统回自动用一张小图片替代.比如输入" : ) "系统
会用一个小笑脸代替。我要实现的就是这样一个信息输入框 。这个信息输入框由两部分组成:图案选择器和多功能文本框。本篇介绍多功能文本框。

二、原理简介

1、主要功能用CRichEditCtrl实现,像设置字体,设置字体颜色,字号等等CRichEditCtrl都提供了很完善的支持,我就不一一赘述了。

CRichEditCtrl 主要的不足在于以下几个方面:

  • (1).没有右键菜单
  • (2).不能插入图片(这是实现转义字符显示的关键)
  • (3).RTF格式输入输出不够方便(涉及到回调函数的递归调用)
  •    我扩展了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 )实现逆过程。具体代码参看本文附带的工程文件。

    三、到此,这个多功能文本框就已经基本能满足我的要求了。但是如何选择表情符号? 如何自动替换? 还是个问题。(待续)
    阅读(212) | 评论(0) | 转发(0) |
    给主人留下些什么吧!~~