Click here to Skip to main content
15,868,016 members
Articles / Desktop Programming / MFC
Article

Animated Emoticons like those in MSN Messenger

Rate me:
Please Sign up or sign in to vote.
3.38/5 (33 votes)
16 May 20052 min read 247.9K   9.6K   64   24
How to create a RichEdit control to display animated emoticons like MSN Messenger's.

Image 1

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:

C++
[
  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.

//use this line to import the dll and genetate tlh and tli file.
#import "D:\\Program files\\tencent\\qq\\ImageOle.dll" named_guids

ImageOle.tlh

// Created by Microsoft (R) C/C++ Compiler Version 12.00.8168.0 (9de7951a).
//
// d:\myproject\msger\debug\ImageOle.tlh
//
// C++ source equivalent of Win32 type library
// D:\\Program files\\tencent\\qq\\ImageOle.dll
// compiler-generated file created 10/25/04 at 22:00:58 - DO NOT EDIT!
#pragma once
#pragma pack(push, 8)
#include <comdef.h>

namespace ImageOleLib {

//
// Forward references and typedefs
//

struct /* coclass */ GifAnimator;
struct __declspec(uuid("0c1cf2df-05a3-4fef-8cd4-f5cfc4355a16"))
/* dual interface */ IGifAnimator;

//
// Smart pointer typedef declarations
//

_COM_SMARTPTR_TYPEDEF(IGifAnimator, __uuidof(IGifAnimator));

//
// Type library items
//

struct __declspec(uuid("06ada938-0fb0-4bc0-b19b-0a38ab17f182"))
GifAnimator;
    // [ default ] interface IGifAnimator

struct __declspec(uuid("0c1cf2df-05a3-4fef-8cd4-f5cfc4355a16"))
IGifAnimator : IDispatch
{
    //
    // Wrapper methods for error-handling
    //

    HRESULT LoadFromFile (
        _bstr_t FileName );
    VARIANT_BOOL TriggerFrameChange ( );
    _bstr_t GetFilePath ( );
    HRESULT ShowText (
        _bstr_t Text );

    //
    // Raw methods provided by interface
    //

    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;
};

//
// Named GUID constants initializations
//

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}};

//
// Wrapper method implementations
//
#include "d:\myproject\msger\debug\ImageOle.tli"

} // namespace ImageOleLib
#pragma pack(pop)

ImageOle.tli

// Created by Microsoft (R) C/C++ Compiler Version 12.00.8168.0 (79a657ba).
//
// ImageOle.tli
//
// Wrapper implementations for Win32 type library
// D:\\Program Files\\Tencent\\qq\\ImageOle.dll
// compiler-generated file created 10/11/04 at 18:24:40 - DO NOT EDIT!

#pragma once

//
// interface IGifAnimator wrapper method implementations
//

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;
    //print to RichEdit' s IClientSite
    LPOLECLIENTSITE m_lpClientSite;
    //A smart point to IAnimator
    IGifAnimatorPtr    m_lpAnimator;
    //ptr 2 storage    
    LPSTORAGE m_lpStorage;
    //the object 2 b insert 2
    LPOLEOBJECT    m_lpObject;

    //Create lockbytes
    sc = ::CreateILockBytesOnHGlobal(NULL, TRUE, &lpLockBytes);
    if (sc != S_OK)
        AfxThrowOleException(sc);
    ASSERT(lpLockBytes != NULL);
    
    //use lockbytes to create storage
    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);
    
    //get the ClientSite of the very RichEditCtrl
    GetIRichEditOle()->GetClientSite(&m_lpClientSite);
    ASSERT(m_lpClientSite != NULL);

    try
    {
        //Initlize COM interface
        hr = ::CoInitializeEx( NULL, COINIT_APARTMENTTHREADED );
        if( FAILED(hr) )
            _com_issue_error(hr);
        
        //Get GifAnimator object
        //here, I used a smart point, so I do not need to free it
        hr = m_lpAnimator.CreateInstance(CLSID_GifAnimator);    
        if( FAILED(hr) )
                _com_issue_error(hr);
        //COM operation need BSTR, so get a BSTR
        BSTR path = strPicPath.AllocSysString();

        //Load the gif
        hr = m_lpAnimator->LoadFromFile(path);
        if( FAILED(hr) )
            _com_issue_error(hr);
            
        TRACE0( m_lpAnimator->GetFilePath() );
        
        //get the IOleObject
        hr = m_lpAnimator.QueryInterface(IID_IOleObject, (void**)&m_lpObject);
        if( FAILED(hr) )
            _com_issue_error(hr);
        
        //Set it 2 b inserted
        OleSetContainedObject(m_lpObject, TRUE);
        
        //2 insert in 2 richedit, you need a struct of REOBJECT
        REOBJECT reobject;
        ZeroMemory(&reobject, sizeof(REOBJECT));

        reobject.cbStruct = sizeof(REOBJECT);    
        CLSID clsid;
        sc = m_lpObject->GetUserClassID(&clsid);
        if (sc != S_OK)
            AfxThrowOleException(sc);
        //set clsid
        reobject.clsid = clsid;
        //can be selected
        reobject.cp = REO_CP_SELECTION;
        //content, but not static
        reobject.dvaspect = DVASPECT_CONTENT;
        //goes in the same line of text line
        reobject.dwFlags = REO_BELOWBASELINE; //REO_RESIZABLE |
        reobject.dwUser = 0;
        //the very object
        reobject.poleobj = m_lpObject;
        //client site contain the object
        reobject.polesite = m_lpClientSite;
        //the storage 
        reobject.pstg = m_lpStorage;
        
        SIZEL sizel;
        sizel.cx = sizel.cy = 0;
        reobject.sizel = sizel;
        HWND hWndRT = this->m_hWnd;
        
        //Sel all text
//        ::SendMessage(hWndRT, EM_SETSEL, 0, -1);
//        DWORD dwStart, dwEnd;
//        ::SendMessage(hWndRT, EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwEnd);
//        ::SendMessage(hWndRT, EM_SETSEL, dwEnd+1, dwEnd+1);

        //Insert after the line of text
        GetIRichEditOle()->InsertObject(&reobject);
        ::SendMessage(hWndRT, EM_SCROLLCARET, (WPARAM)0, (LPARAM)0);
        VARIANT_BOOL ret;
        //do frame changing
        ret = m_lpAnimator->TriggerFrameChange();
        //show it
        m_lpObject->DoVerb(OLEIVERB_UIACTIVATE, NULL, m_lpClientSite, 0, 
                                                             m_hWnd, NULL);
        m_lpObject->DoVerb(OLEIVERB_SHOW, NULL, m_lpClientSite, 0, m_hWnd, 
                                                                       NULL);
        
        //redraw the window to show animation
        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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generalbad word filter to protect my kids. Pin
ZUPERKOOL7-Feb-10 6:30
ZUPERKOOL7-Feb-10 6:30 
GeneralNice article, but... Pin
phoaivu28-May-08 21:40
phoaivu28-May-08 21:40 
GeneralRe: Nice article, but... Pin
phoaivu2-Nov-09 23:34
phoaivu2-Nov-09 23:34 
GeneralError: Pin
inamnak23-Aug-07 20:09
inamnak23-Aug-07 20:09 
Generalsource code download Pin
GABBBGA11-Dec-06 22:17
GABBBGA11-Dec-06 22:17 
GeneralRe: source code download Pin
ductv8-Oct-07 9:47
ductv8-Oct-07 9:47 
Generalhelp Pin
lsmart17-May-06 23:22
lsmart17-May-06 23:22 
Generalhelp Pin
lsmart17-May-06 23:18
lsmart17-May-06 23:18 
Generalneed help! Pin
lsmart17-May-06 23:14
lsmart17-May-06 23:14 
QuestionSouce Code ??? Pin
__Silver__25-May-05 1:16
__Silver__25-May-05 1:16 
AnswerRe: Souce Code ??? Pin
gxulg26-May-05 19:18
gxulg26-May-05 19:18 
GeneralRe: Souce Code ??? Pin
jialei_sun18-Oct-05 18:09
jialei_sun18-Oct-05 18:09 
QuestionIMPORTANT : in vb.net?? Pin
maguxx15-Apr-05 3:17
maguxx15-Apr-05 3:17 
AnswerRe: IMPORTANT : in vb.net?? Pin
tecznology25-Jan-06 2:58
tecznology25-Jan-06 2:58 
GeneralMemory and resources Pin
cguy14-Apr-05 10:36
cguy14-Apr-05 10:36 
GeneralRe: Memory and resources Pin
dTianx22-Apr-05 2:50
dTianx22-Apr-05 2:50 
Questionwhere is the Source code? Pin
zhaozhencn12-Apr-05 20:57
zhaozhencn12-Apr-05 20:57 
AnswerRe: where is the Source code? Pin
mier12-Apr-05 22:17
mier12-Apr-05 22:17 
GeneralRe: where is the Source code? Pin
lam cuong14-Apr-05 20:48
lam cuong14-Apr-05 20:48 
AnswerRe: where is the Source code? Pin
toxcct16-May-05 2:25
toxcct16-May-05 2:25 
AnswerRe: where is the Source code? Pin
Miss Fress23-May-05 17:35
Miss Fress23-May-05 17:35 
CodeProject admin, remove this useless article. No source code! Hmmm | :|
AnswerRe: where is the Source code? Pin
ranmoon11-Aug-06 21:09
ranmoon11-Aug-06 21:09 
GeneralJust what I nedded and planned to do! Pin
Robert W.9-Apr-05 0:53
Robert W.9-Apr-05 0:53 
GeneralBut ... where is the source code???! Pin
Robert W.11-Apr-05 3:47
Robert W.11-Apr-05 3:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.