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控件制作画图工具。