2.8 RichEdit控件典型实例

实例074 利用RichEdit显示Word文档

本实例是一个提高基础技能的程序

实例位置:光盘\mingrisoft\02\074

实例说明

RichEdit控件除了像编辑框一样显示基本数据外,还有多种用途,本实例将使用RichEdit控件显示Word文档中的内容,运行程序,单击“查找Word文档”按钮,在“打开”对话框中选择一个Word文档,单击“显示”按钮,文档中的内容将显示在RichEdit控件中,效果如图2.36所示。

图2.36 利用RichEdit显示Word文档

技术要点

要实现显示Word文档功能,首先需要通过类向导向工程中导入几个Word类,分别是应用程序类(_Application)、文档管理对象(Documents)、文档对象(_Document)、选区(Range);然后根据需要对RichEdit控件的属性进行设置。

RichEdit控件的主要属性如下。

● Multiline:能够显示多行文本,如果用户想要按Enter键在控件中换行,还需要控件具有AutoHScroll和Want return属性。

● Number:只允许输入数字。

● Horizontal scroll:为多行控件提供水平滚动条。

● Auto Hscroll:当用户在控件右方输入字符时,自动地向右方滚动文本。

● Vertical scroll:为多行文本控件提供垂直滚动条。

● Auto Vscroll:在多行文本控件中,当用户在最后一行按Enter键时,文本自动向上滚动。

● Password:以“*”符号代替显示的文本。

● No hide selection:当失去或获得焦点时,不隐藏被选中的部分。

● OEM convert:能够转换OEM字符集。

● Want return:当用户在多行文本控件中按Enter键时,插入回车符。

● Border:具有边框。

● Uppercase:将所有字符转换为大写。

● Lowercase:将所有字符转换为小写。

● Read-only:文本是只读的。

● Left scrollbar:如果垂直滚动条被提供,它将显示在左边的客户区域。

实现过程

(1)新建一个基于对话框的应用程序。

(2)向窗体中添加一个RichEdit控件、一个编辑框控件,为RichEdit控件设置Multiline、Horizontal scroll、Auto Hscroll、Vertical scroll、Want return、Border等属性。

(3)在InitInstance函数中调用AfxInitRichEdit函数,用于初始化RichEdit控件。

(4)主要程序代码如下:

        void CXSWordDlg::OnButxs()
        {
        //Word应用程序
        _Application app;
        //初始化连接
        app.CreateDispatch("word.Application");
        Documents doc;
        CComVariant a (_T(strText)),b(false),c(0),d(true);
        _Document doc1;
        doc.AttachDispatch( app.GetDocuments());
        doc1.AttachDispatch(doc.Add(&a,&b,&c,&d));
        Range range;
        //求出文档的所选区域
        range = doc1.GetContent();//取出文件内容
        CString str;
        str = range.GetText();
        m_rich.SetWindowText(str);
        //关闭
        app.Quit(&b,&c,&c);
        //释放环境
        app.ReleaseDispatch();
        }

举一反三

根据本实例,读者可以:

使用RichEdit控件显示文本文件。

实例075 利用RichEdit控件实现文字定位与标识

这是一个可以提高基础技能的实例

实例位置:光盘\mingrisoft\02\075

图2.37 利用RichEdit控件实现文字定位与标识

实例说明

文本编辑软件一般都有查找字符的功能,本实例实现了在RichEdit控件中查找字符,并将查找到的字符设为选中状态。运行程序,在文本框中输入想要查找的字符串,单击“查找”按钮,程序将在RichEdit控件中进行查找,再次单击“查找”按钮就会查找下一个相匹配的字符。实例运行结果如图2.37所示。

技术要点

本实例的实现主要通过CRichEdit类的SetSel、LineFro mChar和LineIndex等方法和CString类的Find方法实现。SetSel方法是将控件中的字符设置为选中状态;LineFromChar方法是根据字符在控件中的索引获得所在行的索引;LineIndex方法用于获得字符在本行的索引数;Find方法用来查找字符,并能返回字符的位置索引,根据这个位置索引可以计算出下一次开始查找的位置。

实现过程

(1)新建一个基于对话框的应用程序。

(2)在对话框上添加RichEdit控件,设置ID属性为IDC_RICHEDIT1,添加成员变量m_richedit;添加编辑框控件,设置ID属性为IDC_EDFIND,添加成员变量m_edfind;添加Button控件,设置ID属性为IDC_BTNFIND。

(3)RichTextCharDlg.h文件中加入变量声明:

          CString str;
          CString tmp;
          int istartpos;
          int lineindex;
          int movepos;

(4)在OnInitDialog中实现文本文件的读取并显示在RichEdit控件中,代码如下:

          BOOL CRichTextCharDlg::OnInitDialog()
          {
          CDialog::OnInitDialog();
          …//此处代码省略
          CStdioFile file;
          file.Open("test.txt",CFile::modeRead);          //打开文件
          while(1)
          {
              DWORD i=file.ReadString(str);                //读取文件中字符串
              if(i==0)goto end;
              tmp+=str;
              tmp+="\n";
          }
          end:m_richedit.SetWindowText(tmp);              //设置控件显示文本
          lineindex=0;
          istartpos=0;
          movepos=0;
          return TRUE;
          }

(5)处理“查找”按钮的单击事件,该事件的实现函数的代码如下:

          void CRichTextCharDlg::OnFind()
          {
          m_richedit.LineScroll(-lineindex);
          CString strfind;
          GetDlgItem(IDC_EDFIND)->GetWindowText(strfind);       //获得待查找的字符串
          int ret=tmp.Find(strfind,istartpos);                   //查找字符串
          int strlen=strfind.GetLength();                        //获得查找字符串长度
          m_richedit.SetSel(ret,ret+strlen);                    //选中找到的字符串
          istartpos=ret+strlen;
          lineindex=m_richedit.LineFromChar(ret);
          int linepos=m_richedit.LineIndex(lineindex);
          m_richedit.LineScroll(lineindex);
          m_richedit.SetFocus();
          }

举一反三

根据本实例,读者可以:

在RichEdit控件中标识所有匹配的字符。

实例076 利用带利用RichEdit控件显示图文数据

本实例是一个提高基础技能的程序

实例位置:光盘\mingrisoft\02\076

实例说明

在对一些技术知识进行讲解时,如果适当地插入一些图片,将会为用户更好地掌握知识提供有力的帮助。运行程序,用户直接在窗体中编辑文本,在适当的位置定位光标,单击“插入图片”按钮插入图片,如图2.38所示。

技术要点

要实现图文显示功能,首先需要使用API函数LoadImage装载图片,然后创建并插入OLE对象。

图2.38 利用RichEdit控件显示图文数据

LoadImage函数装载图标、光标或位图。函数原型如下:

            HANDLE LoadImage(HINSTANCE hinst,LPCTSTR lpszName,UINT uType,int cxDesired,int cyDesired,UINT fuLoad);

参数说明:

● hinst:处理包含被装载图像模块的特例。若要装载OEM图像,则设此参数值为0。

● lpszName:处理图像装载。

● uType:指定被装载图像类型。

● cxDesired:指定图标或光标的宽度,以像素为单位。

● cyDesired:指定图标或光标的高度,以像素为单位。

● fuLoad:表示文件加载标识,可选值如表2.3所示。

表2.3 fuLoad参数可选值表

实现过程

(1)新建一个基于对话框的应用程序,将窗体标题改为“利用RichEdit控件显示图文数据”。

(2)向窗体中添加一个RichEdit控件、一个编辑框,为RichEdit控件设置Multiline、Horizontal scroll、Auto Hscroll、Vertical scroll、Want return 、Border等属性。

(3)在InitInstance函数中调用AfxInitRichEdit函数,用于初始化RichEdit控件。

(4)主要程序代码如下:

void CNewrich::InsertBitmap(CString *pBmpFile){
          HBITMAP bmp;
          //创建HBITMAP
          bmp = (HBITMAP)::LoadImage(NULL, *pBmpFile, IMAGE_BITMAP, 0, 0,
          LR_LOADFROMFILE|LR_DEFAULTCOLOR|LR_DEFAULTSIZE);  //加载图片资源
          //设置STGMEDIUM结构
          STGMEDIUM stgm;
          stgm.tymed = TYMED_GDI;
          stgm.hBitmap = bmp;
          stgm.pUnkForRelease = NULL;
          //设置FORMATETC结构
          FORMATETC fm;
          fm.cfFormat = CF_BITMAP;
          fm.ptd = NULL;
          fm.dwAspect = DVASPECT_CONTENT;
          fm.lindex = -1;
          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结构
          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 );
          delete pDataSource;
          }

举一反三

根据本实例,读者可以:

利用RichEdit控件制作画图工具。

实例077 在RichEdit中显示不同字体和颜色的文本

本实例是一个提高基础技能的程序

实例位置:光盘\mingrisoft\02\077

实例说明

RichEdit控件和编辑框控件都可以显示文本,不同之处在于RichEdit控件可以显示不同的字体、颜色及图片信息。本实例就通过RichEdit控件显示不同字体和颜色的文本。实例运行结果如图2.39所示。

图2.39 在RichEdit中显示不同字体和颜色的文本

技术要点

本实例主要通过GetDefaultCharFormat方法、SetWord CharFormat方法、SetSel方法和ReplaceSel方法来实现。

(1)GetDefaultCharFormat方法。GetDefaultCharFormat方法用于获得RichEdit控件默认的字符格式化属性,该方法的语法格式如下:

DWORD GetDefaultCharFormat( CHARFORMAT& cf ) const;

参数说明:

● cf:指向一个CHARFORMAT结构的指针,该结构将包含默认的字符格式化属性。

(2)SetWordCharFormat方法。SetWordCharFormat方法用于设置RichEdit控件当前选择的文本的字符格式化属性,其语法格式如下:

BOOL SetWordCharFormat( CHARFORMAT& cf );

参数说明:

● cf:一个CHARFORMAT结构,包含了当前选择的字符格式化属性。

(3)SetSel方法。SetSel方法用于设置RichEdit控件当前选择的文本,其语法格式如下:

        void SetSel( long nStartChar, long nEndChar );
        void SetSel( CHARRANGE& cr );

参数说明:

● nStartChar:标识起始位置。

● nEndChar:标识结束位置。

● cr:一个CHARRANGE结构,包含了当前选择的界线。

注意:对于SetSel方法,当参数为-1和-1时,将选中结尾行,当参数为0和-1时将选中编辑框所有内容。

(4)ReplaceSel方法。ReplaceSel方法用于使用指定的文本替换RichEdit控件中当前选中的文本,语法如下:

void ReplaceSel( LPCTSTR lpszNewText, BOOL bCanUndo = FALSE );

参数说明:

● lpszNewText:用于替换的文本。

● bCanUndo:是否具有取消操作的功能。

实现过程

(1)新建一个基于对话框的应用程序。

(2)向对话框中添加一个RichEdit控件和一个按钮控件,设置RichEdit控件的Multiline、Horizontal scroll、Auto HScroll、Vertical scroll、Auto Vscroll、Want return属性,添加成员变量m_RichEdit;

(3)主要程序代码如下:

          void CEditShowDlg::OnButfont()
          {
              CFontDialog dlg;                                      //初始化字体信息
              if(dlg.DoModal()==IDOK)                              //判断是否按下“确定”按钮
              {
                  LOGFONT temp;                                     //声明LOGFONT结构指针
                  dlg.GetCurrentFont(&temp);                       //获取当前字体信息
                  CHARFORMAT cf;                                    //声明CHARFORMAT变量
                  memset(&cf,0,sizeof(CHARFORMAT));                //分配内存
                  m_RichEdit.GetDefaultCharFormat(cf);             //获得缺省的字符格式化属性
                  cf.yHeight  =temp.lfWeight;                      //设置字号
                  cf.dwMask = CFM_COLOR | CFM_SIZE | CFM_FACE; //设置标记属性
                  cf.dwEffects=CFE_BOLD;                           //设置标记属性有效
                  cf.crTextColor=dlg.GetColor();                   //设置颜色
                  strcpy(cf.szFaceName,temp.lfFaceName);           //设置字体
                  m_RichEdit.SetWordCharFormat(cf);                //设置控件显示字体
                  m_RichEdit.SetSel(-1,-1);                        //选择最后一行
                  m_RichEdit.ReplaceSel("\n");                     //插入换行符
                  m_RichEdit.SetSel(-1,-1);                        //选择最后一行
              }
          }

举一反三

根据本实例,读者可以:

设计RichEdit控件边框和背景。

实例078 在RichEdit中显示GIF动画

本实例是一个提高基础技能的程序

实例位置:光盘\mingrisoft\02\078

实例说明

用户在使用腾讯OICQ聊天软件时,就被支持各种图像格式的编辑框控件所吸引,本实例就设计了一款可以显示GIF动画的RichEdit控件。运行程序,单击“打开”按钮插入GIF动画,如图2.40所示。

图2.40 在RichEdit中显示GIF动画

技术要点

本实例使用RichEdit控件显示GIF动画,要实现这一功能可以分两步进行,首先设计一个ATL控件,然后将ATL控件插入到RichEdit控件中,下面就来介绍一下。

1.设计ATL控件

在Visual C++ 中设计ATL控件比较容易,但是显示GIF动画并不容易。为了降低程序的难度,本例采用了GDI+实现GIF动画的显示。GDI+是微软公司.NET类库的一个组成部分,它并没有集成在Visual C++ 6.0开发环境中,但是用户可以在Visual C++ 6.0环境下使用GDI+。下面介绍如何在Visual C++ 6.0中使用GDI+。

(1)下载GDI+包文件。

(2)引用Gdiplus.h头文件。

(3)引用Gdiplus命名空间。

          using namespace Gdiplus;                          //引用命名空间

(4)定义两个全局变量。

        GdiplusStartupInput m_Gdiplus;
        ULONG_PTR m_pGdiToken;

(5)在应用程序或对话框初始化时加载GDI+。

        GdiplusStartup(&m_pGdiToken,&m_Gdiplus,NULL);      //初始化GDI+

(6)在应用程序结束时卸载GDI+。

        GdiplusShutdown(m_pGdiToken);                    //卸载GDI+

(7)在程序中链接gdiplus.lib库文件。

        #pragma comment(lib,"gdiplus.lib")                  //链接库文件

(8)显示GIF动画。

        Bitmap*pBmp=Bitmap::FromFile(m_SrcFile.AllocSysString());                    //根据文件名称获取图像对象
        Graphics gh(di.hdcDraw);
        gh.DrawImage(pBmp,rc.left+1,rc.top+1,pBmp->GetWidth(),pBmp->GetHeight());    //显示图像

了解了GDI+的使用方法以后,就可以开始设计ATL控件了,设计步骤如下。

(1)创建一个ATC Com工程,在向导中选择支持MFC,如图2.41所示。

图2.41 ATL Com向导窗口

(2)单击“Finish”按钮完成工程的创建。在工作区的类视图窗口中用鼠标右键单击根节点,在弹出的快捷菜单中选择“New ATL Object”菜单项,打开ATL对象向导窗口,如图2.42所示。

(3)在Category列表下选择Controls,在Objects列表中选择Full Control,创建一个ATL控件,单击Next按钮,设置ATL控件信息,如图2.43所示。

(4)选择Attributes选项卡,设置ATL控件属性,如图2.44所示。

(5)选择Miscelaneous选项卡,为ATL控件选择一个基类,如图2.45所示。

图2.42 ATL对象向导

图2.43 ATL对象向导

图2.44 ATL对象属性窗口

图2.45 设置ATL控件基类

(6)单击“确定”按钮创建ATL控件,向ATL控件中添加成员变量。代码如下:

            GdiplusStartupInput       m_Gdiplus;               //定义GDI+初始化变量
            ULONG_PTR                 m_pGdiToken;             //定义GID+标识
              Bitmap                   *m_pBmp;                  //定义位图对象,派生于Image类
              UINT                     m_Count;                  //记录维数
              UINT                     m_FrameCount;             //帧数
              PropertyItem*            pItem;                    //定义图像属性
              int                      fcount;                   //定义一个临时整型变量
              UINT                     delay;                    //第一帧的延时
              RECT                     m_RC;
              static BOOL               m_bChange;
              BOOL                     m_bNewInsert;             //标记是否有对象被插入
              static BOOL               m_bSetHook;               //标记是否挂钩
              static WNDPROC            RichProc;                 //定义窗口函数指针
              int                      m_Num;
              static int                m_ImageNum;               //记录插入的图像数量
              static CCGif              *m_ImageInfo[MAX_IMGNUM];
              static HHOOK              m_hHook;
              static HANDLE             m_hThread;
              ATL_DRAWINFO             m_DrawInfo;
              BOOL                     m_bLoaded;                //是否已经加载了图像
              ImageTypeEx              m_ImageType;
              int                      m_InsertIndex;
              HWND                     hTmp;                     //临时句柄,标记ATL控件的父窗口
              WCHAR                    m_wchFileName[MAX_PATH]; //记录文件名称
              HGLOBAL                  m_hMem;                   //表示加载图像使用的内容空间
              GUID                     ImageID;
              BOOL                     m_bDraw;                  //图像是否显示
              SYSTEMTIME               m_DrawTime;               //绘画时间

(7)在构造函数中初始化非静态成员。代码如下:

          m_bLoaded          =FALSE;
          m_bChange          =FALSE;
          m_ImageType        =IT_UNKNOWN;                  //图像类型未知
          m_bNewInsert       =TRUE;
          m_InsertIndex      =0;
          hTmp               =NULL;
          m_hMem             =NULL;
          m_bDraw            =FALSE;
          m_Num              =0;

在全局区域初始化静态成员。代码如下:

          //初始化静态成员
          BOOL CCGif::m_bSetHook      =FALSE;
          BOOL  CCGif::m_bChange     =FALSE;
          WNDPROC CCGif::RichProc     =NULL;
          int CCGif::m_ImageNum       =0;
          HANDLE CCGif::m_hThread     =NULL;
          HHOOK CCGif::m_hHook        =NULL;
          CCGif *CCGif::m_ImageInfo[MAX_IMGNUM ] = {0};

(8)改写基类的IOleObject_SetClientSite方法,目的是防止m_spClientSite成员为空时出错。代码如下:

          //改写基类的IOleObject_SetClientSite方法,设置m_bNewInsert成员
          inline HRESULT IOleObject_SetClientSite(IOleClientSite *pClientSite)
          {
              //防止m_spClientSite为空时弹出错误对话框
              //ATLASSERT(pClientSite == NULL || m_spClientSite == NULL);
              m_bNewInsert = TRUE;
              if (pClientSite==NULL)
                  return S_OK;
              m_spClientSite=pClientSite;                              //根据参数设置成员变量
              m_spAmbientDispatch.Release();                           //释放m_spAmbientDispatch对象
              if (m_spClientSite != NULL)
              {
                  m_spClientSite->QueryInterface(IID_IDispatch,
                      (void**)&m_spAmbientDispatch.p);                 //重新获取m_spAmbientDispatch对象信息
              }
              return S_OK;
          }

(9)在ATL控件的OnDraw方法中绘制图像,所有的绘图操作都是在该方法中进行的。这里遇到的一个问题是对于GIF动画,需要时时更新,以显示下一帧图像。由于我们创建的ATL控件不是窗口控件,不能利用WM_TIMER消息来更新图像,这里笔者采用的方式是使用线程来更新图像。代码如下:

            HRESULT OnDraw(ATL_DRAWINFO&di)
            {
            if (m_bLoaded)
            {
              memcpy(&m_DrawInfo, &di, sizeof(ATL_DRAWINFO));
              //获取图像的显示区域
              RECT& rc = *(RECT*)di.prcBounds;
              GUID Guid = FrameDimensionTime;
              //如果图像已经插入到控件中,获取插入对象的父窗口句柄
              if (m_bNewInsert == TRUE)
              {
                    IOleInPlaceSite* pSite = NULL;
                    if (m_spClientSite != NULL)
                    {
                        m_spClientSite->QueryInterface(__uuidof(IOleInPlaceSite), (void**)&pSite);
                        if (pSite != NULL)
                        {
                            pSite->GetWindow(&hTmp);
                        }
                        m_bNewInsert = FALSE;
                    }
              }
              //是否为GIF动画
              if(m_ImageType == IT_DYNAMIC)
              {
                    if (m_bSetHook == FALSE)
                    {
                        m_bSetHook = TRUE;
                        //创建一个线程,用于显示动画
                        m_hThread = CreateThread(NULL, 0, ThreadProc, (void*)this, 0, 0);
                        //设置一个消息钩子,截获父窗口的消息,用于更新动画
                        m_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, 0, ::GetCurrentThreadId());
                    }
              }
              //显示图像
              Graphics gh(di.hdcDraw);
              gh.DrawImage(m_pBmp,rc.left+1,rc.top+1,m_pBmp->GetWidth(),m_pBmp->GetHeight());
              //记录当前图像的区域
              m_RC.left =rc.left+1;
              m_RC.top = rc.top+1;
              m_RC.right = m_RC.left+m_pBmp->GetWidth();
              m_RC.bottom =m_RC.top +m_pBmp->GetHeight();
              //如果是Gif动画,显示下一帧图像
              if (m_ImageType==IT_DYNAMIC)
              {
                    if (m_Num == 0)
                    {
                        m_pBmp->SelectActiveFrame(&Guid,fcount++);
                    }
                    if(fcount >= m_FrameCount)
                    {
                        fcount = 0;
                    }
              }
            }
            return S_OK;
        }

(10)编写线程函数,向ATL控件的父窗口发送更新窗口的消息,实现GIF动画的显示。代码如下:

        static DWORD __stdcall ThreadProc( LPVOID lpParameter)
        {
            while (true)
            {
              Sleep(250);                                             //演示250毫秒
              for(int i=0;i<m_ImageNum;i++)                            //遍历已经插入的ATL控件
              {
                    if (m_ImageInfo[i] != NULL)
                    {
                        if(m_ImageInfo[i]->m_ImageType==IT_DYNAMIC)   //如果是GIF动画
                        {
                            //更新父窗口的局部区域
                            ::InvalidateRect(m_ImageInfo[i]->hTmp, &m_ImageInfo[i]->m_RC, FALSE);
                            m_ImageInfo[i]->m_Num = 0;
                        }
                        }
                    }
                    m_bChange=FALSE;
              }
              return 0;
            }

(11)编写钩子函数,当用户在多功能编辑控件中移动鼠标或按下按键时刷新窗口,目的是显示下一帧GIF动画。在向多功能编辑框中插入GIF动画时,如果用户在编辑框中输入文本或者选中文本,GIF图像会快速的切换,为此,笔者设计了一个钩子,截获多功能编辑框的按键消息。代码如下:

          static LRESULT __stdcall GetMsgProc(int code,WPARAM wParam,LPARAM lParam)
          {
              MSG *pMsg = (MSG*)lParam;
              if (pMsg != NULL)
              {
                  char clName[MAX_PATH] = {0};
                  GetClassName(pMsg->hwnd,clName,MAX_PATH);
                  if (pMsg->message == WM_KEYDOWN || pMsg->message == WM_MOUSEMOVE)
                  {
                      for (int i=0; i< m_ImageNum; i++)
                      {
                          if (m_ImageInfo[i] != NULL)
                          {
                              if (m_ImageInfo[i]->hTmp == pMsg->hwnd)
                              {
                                  m_ImageInfo[i]->m_Num = 1;
                                  break;
                              }
                          }
                      }
                      return S_OK;
                  }
                  else if (pMsg->message == WM_PAINT )
                  {
                      for (int i=0; i< m_ImageNum; i++)
                      {
                          if (m_ImageInfo[i] != NULL && m_ImageInfo[i]->m_Num ==1)
                          {
                              if (m_ImageInfo[i]->hTmp == pMsg->hwnd)
                              {
                                  m_ImageInfo[i]->m_Num = 2;
                                  break;
                              }
                          }
                      }
                      return S_OK;
                  }
              }
              return CallNextHookEx(0,code,wParam,lParam);
          }

(12)向ATL控件的接口中添加LoadFromFile方法,加载并显示图像文件。当客户端向多功能编辑控件中插入ATL控件后,需要调用该方法来显示图像。代码如下:

          STDMETHODIMP CCGif::LoadFromFile(LPCTSTR FileName)
          {
              AFX_MANAGE_STATE(AfxGetStaticModuleState())
                    m_bChange=FALSE;
                    RECT rc;                                        //定义区域对象
                    rc.left=2;                                     //初始化区域对象的大小
                    rc.top=2;
                    rc.right=30;
                    rc.bottom=30;
                    GdiplusStartup(&m_pGdiToken,&m_Gdiplus,NULL);  //初始化GDI+
                    m_FileName=(BSTR)FileName;                     //记录文件名称
                    if(m_hMem!=NULL)                               //判断之前是否分配了内存
                    {
                        GlobalFree(m_hMem);                        //释放内存空间
                        m_hMem=NULL;                               初始化成员m_hMem
                    }
                    //获取宽字节字符换为多字节字符的长度
                    int nLen=WideCharToMultiByte(CP_ACP,0,(unsigned short*)FileName,-1,NULL,0,NULL,NULL);
              char*pdata=new  char[nLen];                       //定义字符缓冲区
              //将宽字节转换为多字节字符
              WideCharToMultiByte(CP_ACP,0,(unsigned short*)FileName,-1,pdata,nLen,NULL,NULL);
              CFile file;                                        //定义文件对象
              file.Open(pdata,CFile::modeRead);                 //以读方式打开文件
              DWORD dwLen=file.GetLength();                      //获取文件长度
              delete[]pdata;                                    //释放字符缓冲区
              m_hMem=GlobalAlloc(GMEM_FIXED,dwLen);             //为文件数据分配内存
              BYTE*pData=(BYTE*)GlobalLock(m_hMem);             //获取内存空间的指针
              file.ReadHuge(pData,dwLen);                       //将文件数据读入内存
              file.Close();                                     //关闭文件
              IStream*pStm=NULL;                                //定义流接口指针
              CreateStreamOnHGlobal(m_hMem,FALSE,&pStm);        //在堆中创建流对象
              m_pBmp=Bitmap::FromStream(pStm);                  //从流中获取位图对象
              GlobalUnlock(m_hMem);                             //解锁内存空间
              if(m_pBmp!=NULL)                                  //判断位图对象是否为空
              {
                    m_pBmp->GetRawFormat(&ImageID);             //获取位图的GUID,判断图像类型
                    if(ImageID==ImageFormatGIF)                 //是否为GIF图像
                    {
                        m_ImageType=IT_DYNAMIC;                 //设置图像类型
                    }
                    else if (ImageID==ImageFormatBMP || ImageID==ImageFormatIcon ||
                        ImageID==ImageFormatJPEG)               //是否为静态图像
                    {
                        m_ImageType=IT_STATIC;                  //设置图像类型
                    }
                    if(m_ImageType==IT_DYNAMIC)                 //如果为GIF图像
                    {
                        //获取GIF图像维数
                        m_Count = m_pBmp->GetFrameDimensionsCount();
                        GUID*pGuids=new GUID[m_Count];           //定义一个GUID数组
                        //获取帧列表
                        m_pBmp->GetFrameDimensionsList(pGuids,m_Count);
                        //获取图像帧数
                        m_FrameCount = m_pBmp->GetFrameCount(pGuids);
                        UINT size = 0;
                        delay = PropertyTagFrameDelay;
                        m_Count = 0;
                        //获取图像属性
                        m_pBmp->GetPropertySize(&size,&delay);
                        PropertyItem*pItem=NULL;                //定义属性对象
                        pItem=(PropertyItem*)malloc(size);      //为属性对象分配空间
                        //获取所有属性
                        Status status= m_pBmp->GetAllPropertyItems(size,delay,pItem);
                        delay=((long*)pItem->value)[1];         //获取第一帧的延时
                        free(pItem);                            //释放属性对象
                        delete[]pGuids;                         //释放GUID列表
                        fcount = 0;
                    }
                    if(m_ImageType==IT_UNKNOWN)                 //图像格式不正确
                        return S_OK;                             //不加载图像m_bLoaded为FALSE
                    m_rcPos.left=1;                             //设置图像的显示区域
                    m_rcPos.top = 1;
                    m_rcPos.right = m_pBmp->GetWidth()+1;
                    m_rcPos.bottom = m_pBmp->GetHeight()+1;
                    CSize pixelsize,newsize;
                    pixelsize.cx = m_pBmp->GetWidth()+2;
                    pixelsize.cy = m_pBmp->GetHeight()+2;
                    AtlPixelToHiMetric(&pixelsize,&newsize);
                    m_sizeExtent.cx = newsize.cx;
                    m_sizeExtent.cy = newsize.cy;
                    SendOnViewChange(DVASPECT_CONTENT);         //重新修改显示区域,m_sizeExtent成员生效
                    m_bLoaded = TRUE;
                    m_bNewInsert = TRUE;
                    m_ImageNum++;
                    m_ImageInfo[m_ImageNum-1] = this;
                    hTmp = NULL;
              }
              return S_OK;
        }

(13)向ATL控件接口中添加SaveToFile方法,用于保存图像到磁盘文件中。因为在客户端当用户接收或发送图像时,可以在编辑框中用鼠标右键单击图像,在弹出的快捷菜单中有一个“收藏图片”菜单,单击该菜单将调用SaveToFile方法保存图像到文件中。代码如下:

          STDMETHODIMP CCGif::SaveToFile(BSTR FileName)
          {
              AFX_MANAGE_STATE(AfxGetStaticModuleState())
              CLSID clsid;
              int nRet = -1;
              if(ImageID==ImageFormatGIF)                                //是否为GIF图像
              {
                  nRet=GetCodecClsid(L"image/gif",&clsid);               //获取GUID
              }
              else if(ImageID==ImageFormatBMP)                            //是否为位图图像
              {
                  nRet=GetCodecClsid(L"image/bmp",&clsid);               //获取GUID
              }
              else if(ImageID==ImageFormatIcon)                           //是否为图标
              {
                  nRet=GetCodecClsid(L"image/bmp",&clsid);               //获取GUID
              }
              else if(ImageID==ImageFormatJPEG)                           //是否为JPEG
              {
                  nRet=GetCodecClsid(L"image/jpeg",&clsid);              //获取GUID
              }
              if (nRet== -1)
                  return S_FALSE;
              IStream*pstm=NULL;                                         //定义流接口指针
              CreateStreamOnHGlobal(m_hMem,TRUE,&pstm);                  //在堆中创建流
              //获取宽字节转换为多字节字符的长度
              int nLen = WideCharToMultiByte(CP_ACP,0,(unsigned short*)FileName,-1,NULL,0,NULL,NULL);
              char*pdata=new  char[nLen];                                //定义字符缓冲区
              //将宽字节字符转换为多字节字符
              WideCharToMultiByte(CP_ACP,0,(unsigned short*)FileName,-1,pdata,nLen,NULL,NULL);
              CFile file;                                                 //定义文件对象
              file.Open(pdata,CFile::modeCreate|CFile::modeReadWrite);   //创建文件
              BYTE*pData=(BYTE*)GlobalLock(m_hMem);                      //获取图像数据
              DWORD dwSize=GlobalSize(m_hMem);                            //获取图像数据的大小
              file.WriteHuge(pData,dwSize);                              //写入图像数据到文件中
              file.Close();                                              //关闭文件
              delete[]pdata;                                             //释放字符缓冲区
              GlobalUnlock(m_hMem);                                      //解锁堆空间
              return S_OK;
          }

(14)向ATL控件接口中添加FileName属性。其实现代码如下:

          STDMETHODIMP CCGif::get_FileName(BSTR *pVal)
          {
              AFX_MANAGE_STATE(AfxGetStaticModuleState())
              *pVal=m_FileName                                  //获取图像的文件名称
              return S_OK;
          }
          STDMETHODIMP CCGif::put_FileName(BSTR newVal)
          {
              AFX_MANAGE_STATE(AfxGetStaticModuleState())
              m_FileName=newVal;                                //设置图像的文件名称
              LoadFromFile((LPCTSTR)m_FileName);                //加载图像
              return S_OK;
           }

至此,就完成了ATL控件的设计。

2.将ATL控件插入到RichEdit控件中

CRichEditCtrl控件插入ATL控件的主要思路是通过IRichEditOle接口的InsertObject方法实现的。用户可以使用CRichEditCtrl控件的GetIRichEditOle方法获取IRichEditOle接口指针。所有的插入操作都是围绕InsertObject方法的参数进行的。

实现过程

(1)新建一个基于对话框的应用程序。

(2)向窗体中添加一个RichEdit控件,并为其添加成员变量m_RichEdit。

(3)主要程序代码如下:

        void CTestGifDlg::OnOK()
        {
            CFileDialog flDlg(TRUE,"","",OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
              "图片文件|*.bmp;*.gif;*.jpg;*.jpeg;*.ico;||",this);           //构造打开对话框
            if (flDlg.DoModal()==IDOK)
            {
              CString csFile=flDlg.GetPathName();                            //获得GIF动画路径
              IRichEditOle*lpRichOle=m_RichEdit.GetIRichEditOle();          //获得IRichEditOle接口指针
              if (lpRichOle != NULL)
              {
                    InsertImage(lpRichOle,csFile);                          //插入ATL控件
                    lpRichOle->Release();
                    lpRichOle = NULL;
              }
            }
        }
        BOOL CTestGifDlg::InsertImage(IRichEditOle *lpRichEditOle, CString &csFileName)
        {
            IStorage*lpStorage=NULL;                            //存储接口
            IOleObject*lpOleObject=NULL;                        //定义Ole对象指针
            LPLOCKBYTES lpLockBytes=NULL;                        //定义LOCKBYTES指针,用于创建存储对象
            IOleClientSite*lpOleClientSite=NULL;                //定义IOleClientSite接口指针
            GIFLib::ICGifPtr lpAnimator;                         //定义ATL控件接口指针
            CLSID clsid;                                         //定义类ID对象
            REOBJECT reobject;                                   //定义InsertOjbect方法的参数
            HRESULT hr;
            if (lpRichEditOle == NULL)
            {
              return FALSE;
            }
            hr=::CoInitialize(NULL);                            //初始化Com
            if (FAILED(hr))
            {
              _com_issue_error(hr);
            }
            hr=lpAnimator.CreateInstance(GIFLib::CLSID_CGif);                  //创建ATL控件实例
            if (FAILED(hr))
            {
              _com_issue_error(hr);
            }
            lpRichEditOle->GetClientSite(&lpOleClientSite);                    //获取IOleClientSite
            try
            {
              //获取OLE对象接口
              hr = lpAnimator->QueryInterface(IID_IOleObject,(void**)&lpOleObject);
              if (FAILED(hr))
              {
                    AfxMessageBox("Error QueryInterface");
              }
              hr=lpOleObject->GetUserClassID(&clsid);                          //获取类ID
              if (FAILED(hr))
              {
                    AfxMessageBox("Error GetUserClassID");
              }
              lpOleObject->SetClientSite(NULL);                                //防止出现错误提示
              lpOleObject->SetClientSite(lpOleClientSite);                     //设置ATL控件的OleClientSite
              hr=::CreateILockBytesOnHGlobal(NULL,TRUE,&lpLockBytes);          //创建LOCKBYTE对象
              if (FAILED(hr))
              {
                    AfxThrowOleException(hr);
              }
              ASSERT(lpLockBytes != NULL);
              hr = ::StgCreateDocfileOnILockBytes(lpLockBytes, STGM_SHARE_EXCLUSIVE | STGM_CREATE |
                    STGM_READWRITE,0,&lpStorage);                              //创建根存储对象
              if (FAILED(hr))
              {
                    VERIFY(lpLockBytes->Release() == 0);
                    lpLockBytes = NULL;
                    AfxThrowOleException(hr);
              }
              ZeroMemory(&reobject,sizeof(REOBJECT));                          //初始化参数对象
              reobject.cbStruct=sizeof(REOBJECT);                              //设置结构的大小
              reobject.clsid=clsid;                                            //设置类ID
                  reobject.cp = REO_CP_SELECTION;
                  reobject.dvaspect = DVASPECT_CONTENT;
                  reobject.dwFlags = REO_BLANK;
                  reobject.poleobj=lpOleObject;                                   //设置Ole对象
                  reobject.polesite=lpOleClientSite;                              //设置OleeClientSite
                  reobject.pstg=lpStorage;                                        //设置根存储
                    hr=lpRichEditOle->InsertObject(&reobject);                    //插入对象
                    hr=lpAnimator->LoadFromFile(csFileName.AllocSysString());     //加载文件
                if (FAILED(hr))
                    {
                      AfxThrowOleException(hr);
                    }
                    RedrawWindow();                                               //刷新窗体
                  lpOleClientSite->SaveObject();                                  //保存Ole对象
                    OleSetContainedObject(lpOleObject,TRUE);                      //设置容器对象
              }
              catch (CException* e)
              {
                    e->Delete();
              }
              lpAnimator->Release();                                              //释放ATL接口指针
              lpStorage->Release();                                               //释放存储接口指针
              return TRUE;
            }

举一反三

根据本实例,读者可以:

利用RichEdit控件制作画图工具。