前两天在优快云上看到一篇文章介绍如何在RichEditCtrl控件中添加一个动态的Gif图,比较有意思。去年用老版的QQ时倒是模仿写了一下,当时只解决插入bmp和词法解析,后来就没有继续下去。
优快云上的文章,采用的是腾讯QQ带的组件库ImageOle.dll提供的GifAnimator类提供的ActiveX对象来实现这个功能。该对象暴露一个IGifAnimator接口,具体的定义如下:
interface IGifAnimator : IDispatch {
HRESULT LoadFromFile([in] BSTR FileName);
HRESULT TriggerFrameChange([out, retval] VARIANT_BOOL* pbChanged);
HRESULT GetFilePath([out, retval] BSTR* pFilePath);
HRESULT ShowText([in] BSTR Text);
};
优快云中给出了一个例子,采用Import方式倒入com类,并且给出了如何插入到RichEdit中。不过,我不太喜欢这种方式,自己建了一个。
下面是通过从类型库创建类的方式创建的一个类,比较简洁。
#include <initguid.h>
DEFINE_GUID(CLSID_GIFAnimator,
0x06ADA938,0x0FB0,0x4BC0,0xB1,0x9B,0x 0A ,0x38,0xAB,0x17,0xF1,0x82);
DEFINE_GUID(IID_IGIFAnimator,
0x 0C 1CF2DF,0x 05A 3,0x4FEF,0x 8C ,0xD4,0xF5,0xCF,0xC4,0x35,0x 5A ,0x16);
class CGifAnimator : public COleDispatchDriver
{
public:
CGifAnimator(){} // 调用 COleDispatchDriver 默认构造函数
CGifAnimator(LPDISPATCH pDispatch) : COleDispatchDriver(pDispatch) {}
CGifAnimator(const CGifAnimator& dispatchSrc) : COleDispatchDriver(dispatchSrc) {}
// 操作
public:
// IGifAnimator 方法
public:
void LoadFromFile(LPCTSTR FileName){
static BYTE parms[] = VTS_BSTR ;
InvokeHelper(0x1, DISPATCH_METHOD, VT_EMPTY, NULL, parms, FileName);
}
BOOL TriggerFrameChange(){
BOOL result;
InvokeHelper(0x2, DISPATCH_METHOD, VT_BOOL, (void*)&result, NULL);
return result;
}
CString GetFilePath(){
CString result;
InvokeHelper(0x3, DISPATCH_METHOD, VT_BSTR, (void*)&result, NULL);
return result;
}
void ShowText(LPCTSTR Text){
static BYTE parms[] = VTS_BSTR ;
InvokeHelper(0x4, DISPATCH_METHOD, VT_EMPTY, NULL, parms, Text);
}
};
通过编码,对上面的四个函数进行了简单的测试,每个函数的功能如下:
1、 void LoadFromFile(LPCTSTR FileName)
装载要显示的文件到控件,文件为图像类型,不局限于GIF格式。失败时抛出异常。
2、 BOOL TriggerFrameChange()
发出消息,动态的GIF动起来。在对话框中有问题,可以通过向父窗口发送WM_PAINT消息来激活。
3、 CString GetFilePath()
返回装入的文件的名称。
4、 void ShowText(LPCTSTR Text)
没有测出其作用,只是调用以后,动态的GIF不动了。
下面给出的代码,将该控件插入到RichEdit控件。
BOOL CImageRichEditCtrl::InsertGifAnimator(LPCTSTR lpszGifName)
{
CGifAnimator animator;
IStorage* lpStorage = NULL;
IOleObject* lpOleObject = NULL;
LPLOCKBYTES lpLockBytes = NULL;
IOleClientSite* lpOleClientSite = NULL;
IRichEditOle* lpRichEditOle = NULL;
CLSID clsid;
REOBJECT reobject;
lpRichEditOle = GetIRichEditOle();
if( lpRichEditOle == NULL ) return FALSE;
BOOL bRet = animator.CreateDispatch(CLSID_GIFAnimator);
if( bRet == FALSE ){
lpRichEditOle->Release();
return FALSE;
}
try{
animator.LoadFromFile(lpszGifName); //Load失败时,抛出异常
HRESULT hr = animator.m_lpDispatch->QueryInterface(&lpOleObject);
if( hr != S_OK )
AfxThrowOleException(hr);
hr = lpOleObject->GetUserClassID(&clsid);
if ( hr != S_OK)
AfxThrowOleException(hr);
hr = ::CreateILockBytesOnHGlobal(NULL, TRUE, &lpLockBytes);
if (hr != S_OK)
AfxThrowOleException(hr);
ASSERT(lpLockBytes != NULL);
hr = ::StgCreateDocfileOnILockBytes(lpLockBytes,
STGM_SHARE_EXCLUSIVE|STGM_CREATE|STGM_READWRITE, 0, &lpStorage);
if (hr != S_OK)
{
VERIFY(lpLockBytes->Release() == 0);
lpLockBytes = NULL;
AfxThrowOleException(hr);
}
lpRichEditOle->GetClientSite(&lpOleClientSite);
ZeroMemory(&reobject, sizeof(REOBJECT));
reobject.cbStruct = sizeof(REOBJECT);
reobject.clsid = clsid;
reobject.cp = REO_CP_SELECTION;
reobject.dvaspect = DVASPECT_CONTENT;
reobject.dwFlags = REO_BELOWBASELINE;
reobject.poleobj = lpOleObject;
reobject.polesite = lpOleClientSite;
reobject.pstg = lpStorage;
hr = lpRichEditOle->InsertObject( &reobject );
if (hr != S_OK)
AfxThrowOleException(hr);
OleSetContainedObject(lpOleObject,TRUE);
// bRet = animator.TriggerFrameChange();
// 作用是更新显示,让ActiveX控件动起来,不过在对话框中的控件无效。
//如果本函数在OnInitDialog中调用,本消息不能被处理,可能不会动态显示
SendMessage(WM_PAINT,0,0);
}
catch( COleException* e )
{
TRACE(_T("CImageRichEditCtrl::InsertGifAnimator() OleException
code:%d"),e->m_sc);
e->Delete();
bRet = FALSE;
}
// release the interface
lpRichEditOle->Release();
if( lpOleObject != NULL ) lpOleObject->Release();
if( lpOleClientSite != NULL ) lpOleClientSite->Release();
if( lpStorage != NULL ) lpStorage->Release();
return bRet;
}
这样,通过调用InsertGifAnimator就可以将一个图像文件插入到RichEdit控件中,并且如果是动态的GIF则可以动起来,对于聊天来说,效果比较好。
那么这个控件是如何实现的呢?通过的分析,实现控件需要的技术要点如下:
1、 必须是ActiveX控件,这可以用ATL模板来生成,Easy。
2、 用于可以插入很多控件实例,为了尽可能的少占用系统资源,必须为WndLess控件。
3、 为了动态显示GIF和TIFF文件,必须采用时钟,(线程占用资源太大)。
4、 为了显示所有的图像文件,这里采用Gdiplus来简化图像的操作。
5、 为了支持Undo操作,必须在处理Undo后的动态显示。
而上述操作中,相对较难的是时钟处理和Undo恢复操作。
经过几天的努力,我也写了一个支持在RichEdit控件中显示动画的控件DynamicGif,下面是测试结果(在插入了6个128×128的动态GIF文件后),和QQ带的ImageOle.dll相比较。
| CPU% | 内存(KB) | 句柄数 | 线程数 | GDI |
未插入控件时 | 0% | 6104 | 94 | 2 | 59 |
DynamicGif | 28% | 8332 | 118 | 3 | 69 |
QQ库 | 44% | 8372 | 118 | 3 | 70 |
测试结果:资源占用比较