Introduction
Most of us know that in MSN Messenger, while chatting, we can insert animated emoticons in the chat window. That's very cool. In China, there is another famous messenger called QQ (the former OICQ) that can display GIFs as emoticons. After I read some code about RichEdit and COM, and after many tests on QQ and MSN Messenger, I got the code and put it on my blog on CSDN . :)
Background
First, how does MSN Messenger show animated emoticons? In MSN Messenger, it uses PNG files as emoticons, every PNG image shows a list of frames for a whole animation. In QQ, it uses GIFs as emoticons, so the user can customize the emoticons by changing the image, in fact, in QQ, we can send any image to a friend, and it can set to be an emoticon. If we just need to display static emoticons, you can refer to Insert any HBITMAP (Bitmap) in your RichEdit Control .
Can CRichEditCtrl
display a dynamic GIF? Of course not. But CRichEditCtrl
can display a COM object, then what can a COM object do? Nearly anything. So we insert a COM object into the CRichEditCtrl
instance, then what we can see will be the COM object. Is this what we want to see? No. What we want to see is animated emoticons! So we display the GIF in the COM object. Then what we can see will be the emoticons.
Using the code
First we need a COM object to display a GIF and for it to be inserted in the richedit. This COM object can be developed using ATL. If you do not know how to display a GIF, you can get the Gif89a source code or CPictureEx
source code. If you do not care about the size of your application, GDI+ can be your choice. If you want to ask me which of the above I used to display GIF, my answer may be "none". Because I used a DLL in QQ, this DLL is a COM object, named ImageOle.dll. It is inserted and can show GIFs (in this module, it use GDI+ to show GIFs).
Follow these steps to get your own emoticons RichEdit:
First, open your OLE/COM Viewer in Microsoft Visual Studio 6.0 Tools. Use View TypeLib... to open ImageOle.dll (you' d better register it with regsvr32.exe), then you can get the text below:
[
uuid(0C1CF2DF-05A3-4FEF-8CD4-F5CFC4355A16),
helpstring("IGifAnimator Interface"),
dual,
nonextensible
]
dispinterface IGifAnimator {
properties:
methods:
[id(0x00000001), helpstring("method LoadFromFile")]
void LoadFromFile([in] BSTR FileName);
[id(0x00000002), helpstring("method TriggerFrameChange")]
VARIANT_BOOL TriggerFrameChange();
[id(0x00000003), helpstring("method GetFilePath")]
BSTR GetFilePath();
[id(0x00000004), helpstring("method ShowText")]
void ShowText([in] BSTR Text);
};
This object implements an interface called IGifAnimator
, we can use it to display GIFs. To see the effect, you can run ActiveX Control Test Container to test it. First invoke LoadFromFile
, then TriggerFrameChange
.
#import "D:\\Program files\\tencent\\qq\\ImageOle.dll" named_guids
ImageOle.tlh
#pragma once
#pragma pack(push, 8)
#include <comdef.h>
namespace ImageOleLib {
struct GifAnimator;
struct __declspec(uuid("0c1cf2df-05a3-4fef-8cd4-f5cfc4355a16"))
IGifAnimator;
_COM_SMARTPTR_TYPEDEF(IGifAnimator, __uuidof(IGifAnimator));
struct __declspec(uuid("06ada938-0fb0-4bc0-b19b-0a38ab17f182"))
GifAnimator;
struct __declspec(uuid("0c1cf2df-05a3-4fef-8cd4-f5cfc4355a16"))
IGifAnimator : IDispatch
{
HRESULT LoadFromFile (
_bstr_t FileName );
VARIANT_BOOL TriggerFrameChange ( );
_bstr_t GetFilePath ( );
HRESULT ShowText (
_bstr_t Text );
virtual HRESULT __stdcall raw_LoadFromFile (
BSTR FileName ) = 0;
virtual HRESULT __stdcall raw_TriggerFrameChange (
VARIANT_BOOL * pbChanged ) = 0;
virtual HRESULT __stdcall raw_GetFilePath (
BSTR * pFilePath ) = 0;
virtual HRESULT __stdcall raw_ShowText (
BSTR Text ) = 0;
};
extern "C" const GUID __declspec(selectany) LIBID_ImageOleLib =
{0x710993a2,0x4f87,0x41d7,{0xb6,0xfe,0xf5,0xa2,0x03,0x68,0x46,0x5f}};
extern "C" const GUID __declspec(selectany) CLSID_GifAnimator =
{0x06ada938,0x0fb0,0x4bc0,{0xb1,0x9b,0x0a,0x38,0xab,0x17,0xf1,0x82}};
extern "C" const GUID __declspec(selectany) IID_IGifAnimator =
{0x0c1cf2df,0x05a3,0x4fef,{0x8c,0xd4,0xf5,0xcf,0xc4,0x35,0x5a,0x16}};
#include "d:\myproject\msger\debug\ImageOle.tli"
}
#pragma pack(pop)
ImageOle.tli
#pragma once
inline HRESULT IGifAnimator::LoadFromFile ( _bstr_t FileName ) {
HRESULT _hr = raw_LoadFromFile(FileName);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _hr;
}
inline VARIANT_BOOL IGifAnimator::TriggerFrameChange ( ) {
VARIANT_BOOL _result;
HRESULT _hr = raw_TriggerFrameChange(&_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _result;
}
inline _bstr_t IGifAnimator::GetFilePath ( ) {
BSTR _result;
HRESULT _hr = raw_GetFilePath(&_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _bstr_t(_result, false);
}
inline HRESULT IGifAnimator::ShowText ( _bstr_t Text ) {
HRESULT _hr = raw_ShowText(Text);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _hr;
}
How can we use it? Here is the code:
LPLOCKBYTES lpLockBytes = NULL;
SCODE sc;
HRESULT hr;
LPOLECLIENTSITE m_lpClientSite;
IGifAnimatorPtr m_lpAnimator;
LPSTORAGE m_lpStorage;
LPOLEOBJECT m_lpObject;
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, &m_lpStorage);
if (sc != S_OK)
{
VERIFY(lpLockBytes->Release() == 0);
lpLockBytes = NULL;
AfxThrowOleException(sc);
}
ASSERT(m_lpStorage != NULL);
GetIRichEditOle()->GetClientSite(&m_lpClientSite);
ASSERT(m_lpClientSite != NULL);
try
{
hr = ::CoInitializeEx( NULL, COINIT_APARTMENTTHREADED );
if( FAILED(hr) )
_com_issue_error(hr);
hr = m_lpAnimator.CreateInstance(CLSID_GifAnimator);
if( FAILED(hr) )
_com_issue_error(hr);
BSTR path = strPicPath.AllocSysString();
hr = m_lpAnimator->LoadFromFile(path);
if( FAILED(hr) )
_com_issue_error(hr);
TRACE0( m_lpAnimator->GetFilePath() );
hr = m_lpAnimator.QueryInterface(IID_IOleObject, (void**)&m_lpObject);
if( FAILED(hr) )
_com_issue_error(hr);
OleSetContainedObject(m_lpObject, TRUE);
REOBJECT reobject;
ZeroMemory(&reobject, sizeof(REOBJECT));
reobject.cbStruct = sizeof(REOBJECT);
CLSID clsid;
sc = m_lpObject->GetUserClassID(&clsid);
if (sc != S_OK)
AfxThrowOleException(sc);
reobject.clsid = clsid;
reobject.cp = REO_CP_SELECTION;
reobject.dvaspect = DVASPECT_CONTENT;
reobject.dwFlags = REO_BELOWBASELINE;
reobject.dwUser = 0;
reobject.poleobj = m_lpObject;
reobject.polesite = m_lpClientSite;
reobject.pstg = m_lpStorage;
SIZEL sizel;
sizel.cx = sizel.cy = 0;
reobject.sizel = sizel;
HWND hWndRT = this->m_hWnd;
GetIRichEditOle()->InsertObject(&reobject);
::SendMessage(hWndRT, EM_SCROLLCARET, (WPARAM)0, (LPARAM)0);
VARIANT_BOOL ret;
ret = m_lpAnimator->TriggerFrameChange();
m_lpObject->DoVerb(OLEIVERB_UIACTIVATE, NULL, m_lpClientSite, 0,
m_hWnd, NULL);
m_lpObject->DoVerb(OLEIVERB_SHOW, NULL, m_lpClientSite, 0, m_hWnd,
NULL);
RedrawWindow();
if (m_lpClientSite)
{
m_lpClientSite->Release();
m_lpClientSite = NULL;
}
if (m_lpObject)
{
m_lpObject->Release();
m_lpObject = NULL;
}
if (m_lpStorage)
{
m_lpStorage->Release();
m_lpStorage = NULL;
}
SysFreeString(path);
}
catch( _com_error e )
{
AfxMessageBox(e.ErrorMessage());
::CoUninitialize();
}
After that, your CEditCtrl
can show animated GIFs.