实战经验:CRichEditCtrl插入图片(不止是位图)

实战经验:CRichEditCtrl插入图片(不止是位图)

作者:BlogUpdater |  时间:2019-04-21 |  浏览:5055 |  评论已关闭 条评论

问题

在一次功能开发中,我希望可以在CRichEditCtrl控件中插入一些图片,这些图片可能是位图(bmp),也可能是其他格式(jpg, png等)。

 

以下是一种思路
1) 图片文件可以通过GDI+中的Bitmap类来加载。
2) 如果要插入图片到CRichEditCtrl,则需要涉及到OLE对象的插入。可以这样理解,每一个图片文件都是一个OLE对象,这些OLE对象通过CRichEditCtrl类暴露的IRichEditOle接口的InsertObject方法来进行插入操作(这句话有点长啊,请不要着急,慢慢理解)。
3) OLE的世界中,有一个涉及OLE对象存储介质的类:STGMEDIUM。这个类中,有一个数据成员hBitmap,其类型为HBITMAP,也即位图句柄。我们可以通过将GDI+的Bitmap对象转换为HBITMAP来将我们加载的图片文件传递给STGMEDIUM。
4) 除了以上的步骤,还有以下常规的OLE函数调用,需要引入。
CreateILockBytesOnHGlobal
StgCreateDocfileOnILockBytes
OleCreateStaticFromData
GetUserClassID

 

执行图片加载
以下代码演示了如何使用GDI+的Bitmap来加载图片。

Gdiplus::Bitmap * pBmp = Gdiplus::Bitmap::FromFile(pszFilePath);
HBITMAP hBmp = NULL;
pBmp->GetHBITMAP(0, &hBmp);

代码解析
1) 通过Gdiplus::Bitmap的FromFile方法来加载图片,图片的文件可以是位图,也可以是jpg等其他格式。
2) 加载完成后,会返回一个Gdiplus::Bitmap对象指针。
3) 通过Gdiplus::Bitmap对象的GitHBITMAP方法获取对应的位图句柄。
4) 此获取到的位图句柄,在以下插入图片的代码中会被使用到。

 

插入图片到CRichEdit
以下代码演示了如果将一个位图句柄插入到CRichEditCtrl中。

void CMyRichEditCtrl::InsertPicture(HBITMAP hBmp)
{
	STGMEDIUM stgm;
	stgm.tymed = TYMED_GDI;
	stgm.hBitmap = hBmp;
	stgm.pUnkForRelease = NULL;
	
	FORMATETC fm;
	fm.cfFormat = CF_BITMAP;
	fm.ptd = NULL;
	fm.dwAspect = DVASPECT_CONTENT;
	fm.lindex = -1;
	fm.tymed = TYMED_GDI;
	
	COleDataSource oleDataSource;
	oleDataSource.CacheData(CF_BITMAP, &stgm);
	LPDATAOBJECT dataObject = (LPDATAOBJECT)oleDataSource.GetInterface(&IID_IDataObject);
	
	if (OleQueryCreateFromData(dataObject) != OLE_S_STATIC)
		return;
	
	LPOLECLIENTSITE oleClientSite;
	if (S_OK != GetIRichEditOle()->GetClientSite(&oleClientSite))
		return;
	
	// 内存分配
	LPLOCKBYTES lockBytes = NULL;
	if (S_OK == CreateILockBytesOnHGlobal(NULL, TRUE, &lockBytes) && lockBytes)
	{
		IStorage *storage = NULL;
		if (S_OK == StgCreateDocfileOnILockBytes(lockBytes, STGM_SHARE_EXCLUSIVE | STGM_CREATE | STGM_READWRITE, 0, &storage) && storage)
		{
			IOleObject *oleObject = NULL;
			if (S_OK == OleCreateStaticFromData(dataObject, IID_IOleObject, OLERENDER_FORMAT, &fm, oleClientSite, storage, (void **)&oleObject) && oleObject)
			{
				CLSID clsid;
				if (S_OK == oleObject->GetUserClassID(&clsid))
				{
					REOBJECT reobject = { sizeof(REOBJECT) };
					reobject.clsid = clsid;
					reobject.cp = REO_CP_SELECTION;
					reobject.dvaspect = DVASPECT_CONTENT;
					reobject.poleobj = oleObject;
					reobject.polesite = oleClientSite;
					reobject.pstg = storage;
		
					// 插入OLE对象
					GetIRichEditOle()->InsertObject(&reobject);
				}
				oleObject->Release();
			}
			storage->Release();
		}
		lockBytes->Release();
	}
	oleClientSite->Release();	
}

代码解析
1) 创建一个STGMEDIUM对象,将上一个步骤中得到的位图句柄赋值给它的hBitmap成员,另外需要设定其tymed为TYMED_GDI。
2) 创建一个FORMATETC对象,初始化其各个成员,具体可以参考以上代码片段。
3) 创建一个COleDataSource,并调用其CacheData方法,将数据从STGMEDIUM对象中缓存至COleDataSource中。
4) 内存分配部分为常规调用序列,需要注意的是,在最终调用InsertObject时,需要先准备一个reobject对象,这个对象的各个接口指针需要确保赋值正确。例如poleobj,polesite和pstg。
5) 通过GetIRichEditOle获取CRichEditCtrl的IRichEditOle接口,并调用InsertObject实现OLE对象(也就是这里的图片文件)的插入。
6) 所有工作完成之后,记得调用Release释放各个接口指针,以防止出现资源泄漏。

 

总结
1) CRichEditCtrl,所谓的富文本编辑器,从功能上说,的确是很丰富,如果想体验它强大的功能,可以打开一个写字板(wordpad),因为写字板就是基于CRichEditCtrl控件实现的。
2) 插入图片的需求是如此普遍,以至于我忍不住在这里记录这部分代码,以方便需要使用它的人。
3) 以上代码只是对基本原理进行了介绍,并未添加完整的错误处理逻辑。

 

标签:

评论已关闭。