CRichEditControl50W - 一个 VC++ 富文本编辑 4.1 MFC 控件






4.72/5 (13投票s)
2005 年 3 月 11 日
5分钟阅读

245655

5099
一个简单的 MFC 富文本控件,使用 msftedit.dll 中的 4.1 版本。
引言
CRichEditControl50W
是一个 CWnd
派生的富文本编辑控件 v. 4.1,它使用了随 Windows XP 一起发布的、文档少得可怜的新版 msftedit.dll (MSFTEDIT_CLASS
,或称 "RichEdit50W
" 类名)。VC++.NET 中提供的 CRichEditCtrl
只使用了旧版 v. 3.0 的富文本编辑控件 (RICHEDIT_CLASS
,或称 "RichEdit20W
")。我在网上找不到任何关于在 MFC 中使用新控件的示例。因此,在查看了 VS 中的 CRichEditCtrl
和 CRichEditView
类之后,我决定创建自己的。这可能是一项琐碎的工作,但由于文档的缺乏,我花了大量时间研究才意识到 msftedit.dll 并未得到 VS.NET 的支持,CRichEditView
类也同样如此。我想着或许可以为其他人省去解决这个麻烦的精力。
背景
我曾试图更新我的 ping/traceroute 应用程序的 CRichEditCtrl
以使用新的 "RichEdit50W
" 窗口类。你不能简单地在 richedit.h 中用 MSFTEDIT_CLASS
替换 RICHEDIT_CLASS
。微软在 Wordpad XP 中使用了 4.1 版本,并开发了新的、未公开的类 CRichEdit2View
、CRichEdit2Ctrl
、CRichEdit2Doc
、CRichEdit2Cntr
(我通过使用 Hex Editor 查看 Wordpad.exe 发现了这些),但它们并未出现在 Visual Studio .NET 2003 甚至 VS.NET 2005 BETA1 (Whidbey) 中。他们仍然保留了使用 riched20.dll 的旧 WORDPAD 示例。 "RichEdit50W
" (或 "MSFTEDIT_CLASS
") 类无法与 VS 中当前的 CRichEditView
一起使用;CRichEditView
是硬编码为加载 riched20.dll 的。
我没有创建像微软那样的新基准文档/视图架构类,所以你无法使用文档/视图架构来嵌入对象,除非你自己开发并重新编译 VS.NET。但如果你不需要嵌入对象,这个控件就能很好地工作,并提供了 RichEdit v. 4.1 的高级文本格式化功能。
使用代码
步骤
- 在 Visual Studio 中创建一个新的 MFC 应用程序。在 "应用程序类型" 选项卡中,选择 "单文档",然后 **取消选中** "文档/视图体系结构" 选项...你不需要这个。我给我的应用程序起了个名字叫 RE50W。完成后,删除所有对
CChildView
类的引用,这是CMainFrame
默认创建的视图;你也不需要这个。 - 将文件 RichEditControl50W.cpp 和 RichEditControl50W.h 添加到你的项目中。这就是我创建的
CRichEditControl50W
控件。 - 在应用程序的 re50w.cpp 中,添加
#include "RichEditControl50W.h" // before #include "MainFrm.h"
- 在 MainFrame.cpp 中,添加
#include "RichEditControl50W.h" // before #include "MainFrm.h"
- 在 MainFrame.f 中,将
CRichEditControl50W
m_REControl50W
声明为受保护的成员etc.......................... protected: // control bar embedded members CStatusBar m_wndStatusBar; CToolBar m_wndToolBar; CRichEditControl50W m_REControl50W; etc..................
- 在 MainFrame.cpp 的
OnCreate
函数下,更改视图的创建方式int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; //Set Options for the Rich Edit Control DWORD w_RichEd = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_VSCROLL | ES_AUTOVSCROLL | WS_HSCROLL | ES_AUTOHSCROLL | ES_MULTILINE; // create a rich edit control to occupy the client area of the frame if (!m_REControl50W.Create(w_RichEd, CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST)) { TRACE0("Failed to create view window\n"); return -1; } // Send Initialization messages to the window m_REControl50W.LimitText50W(-1); //Set the control to accept the maximum amount of text //Other options for the control DWORD REOptions = (ECO_AUTOVSCROLL | ECO_AUTOHSCROLL | ECO_NOHIDESEL | ECO_SAVESEL | ECO_SELECTIONBAR); //Set other options m_REControl50W.SetOptions50W( ECOOP_OR, //The type of operation REOptions ); //Options //Set the contol to automatically detect URLs m_REControl50W.SendMessage( EM_AUTOURLDETECT, TRUE, 0); //Set the event masks for the rich edit control m_REControl50W.SetEventMask50W( ENM_SELCHANGE | ENM_LINK //New event mask for the rich edit control ); //Set the default character formatting... //see RichEditControl50W.cpp for function definition m_REControl50W.SetDefaultCharFormat50W( CFM_COLOR | CFM_BOLD | CFM_SIZE | CFM_FACE | CFM_BACKCOLOR, //Mask options RGB(0,0,0), //Text Color !CFE_BOLD, //Text Effects "Trebuchet MS", //Font name 200, //Font yHeight RGB(255,255,255)); //Font background color //Text for RE Control Example, has to be in RTF format m_csMessage = "{\\rtf1 RE50W by Jim Dunne Copyright" " (C) 2005\\par http://www.topjimmy.net/tjs \\par" "{\\field{\\*\\fldinst{HYPERLINK mailto:jim@dunnes.net" " }}{\\fldrslt{\\cf1\\ul jim@dunnes.net}}}}"; //Set the caret selection to the end of any current text m_REControl50W.SetSel50W(-1, -1); //Write to the control m_REControl50W.SetTextTo50WControl( m_csMessage, //Write the text in m_csMessage to the RE Control ST_SELECTION, // SETTEXT flags value 1200); // SETTEXT codepage value etc..........................
- 向
CMainFrame
添加OnContextMenu
函数,以处理富文本控件的自定义弹出菜单。创建一个名为IDR_REPOPUP
的自定义弹出菜单 (更多细节请参阅源代码)void CMainFrame::OnContextMenu(CWnd* pWnd, CPoint point) { CMenu menu; if (menu.LoadMenu(IDR_REPOPUP)) { CMenu* pPopup = menu.GetSubMenu(0); ASSERT(pPopup != NULL); pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x , point.y, AfxGetMainWnd()); } }
- 为你的弹出菜单添加命令和更新处理程序及函数。例如 "复制" 功能
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_CREATE() ON_WM_SETFOCUS() ON_WM_CONTEXTMENU() ON_COMMAND(ID_REPOPUP_COPY, OnPopupCopy) ON_UPDATE_COMMAND_UI(ID_REPOPUP_COPY, OnUpdatePopupCopy) etc................. void CMainFrame::OnPopupCopy() { m_REControl50W.SendMessage(WM_COPY, 0, 0); } void CMainFrame::OnUpdatePopupCopy(CCmdUI* pCmdUI) { m_REControl50W.SendMessage(EM_EXGETSEL, 0, (LPARAM)&m_crStatus); // Activate the "Copy" menu item if anything is selected pCmdUI->Enable(m_crStatus.cpMin != m_crStatus.cpMax); // CHARRANGE m_crStatus declared in MainFrame.h }
- 在 stdafx.h 的底部添加
#include <richedit.h>
。(这其实不是必需的,但为了保险起见...)
处理超链接
这很有趣,因为文档严重不足。WordPad XP 不处理 RTF 超链接,但 Word 2003 可以,所以我知道这是可以做到的。问题在于 GetTextRange()
函数,或发送给控件以获取超链接文本的 EM_GETTEXTRANGE
消息。如果你按照 Visual Studio 中的说明使用它,TEXTRANGE lpstrText LPSTR
缓冲区只会返回范围的第一个字符。我在网上找到了其他人称之为 Rich Edit 控件的 Bug。问题其实是 Unicode 与 ANSI 的区别。如果你在你的 richedit 控件中使用 Unicode,并且使用了 VS 中的默认说明,那么 EM_GETTEXTRANGE
将无法正常工作。
你需要使用 TEXTRANGEW
结构,该结构定义在 richedit.h 中,而不是 TEXTRANGE
。该结构中的 lpstrText
缓冲区是 LPWSTR
(宽字符串),用于处理 Unicode。但是,当你从 EM_GETTEXTRANGE
返回 lpstrText
后,你必须将其转换回 ANSI,以便与 MFC 一起使用!为此,你必须使用 WideCharToMultiByte()
函数,将 LPWSTR
缓冲区的内容填充到 LPSTR
缓冲区中。然后,你可以使用 LPSTR
缓冲区的内容在 ShellExecute()
中打开你的默认电子邮件、网页浏览器等应用程序,具体取决于超链接 (例如,http:// 会打开你的默认网页浏览器)。
在 CMainFrame
的 OnCreate
函数中设置控件的 ENM_LINK
事件掩码,这会导致每当控件中的超链接被操作时,控件就会向父窗口 CMainFrame
发送一个 WM_NOTIFY
消息。要捕获并使用此消息;使用 Visual Studio 中的类视图,向 CMainFrame
添加 OnNotify
函数,然后修改它
BOOL CMainFrame::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) { //Need ENLINK structure in order to mine the contents of lParam ENLINK *p_enRE50W; //Put contents of lParam into ENLINK structure p_enRE50W = (ENLINK *)lParam; //If ENLINK exists and the message sent was left mouse button up.. if(p_enRE50W && p_enRE50W->msg == WM_LBUTTONUP) { m_REControl50W.GetTextRange50W(p_enRE50W->chrg.cpMin, p_enRE50W->chrg.cpMax); //Call ShellExecute to perform default //action based on the type of hyperlink ShellExecute(m_hWnd, "Open", m_REControl50W.m_lpszChar, NULL, NULL, SW_MAXIMIZE); } return CFrameWnd::OnNotify(wParam, lParam, pResult); }
GetTextRange50W()
(见下文) 将 Unicode LPWSTR lpszWChar
转换为 LPSTR
成员变量 m_lpszChar
,ShellExecute()
会使用它来打开适用于你的超链接的相应应用程序。
如果你打开演示应用程序,然后点击网页或电子邮件链接,你的默认应用程序应该会处理它们。
CRichEditControl50W 概览
- 在 "
Create
" 时,它会加载 msftedit.dll 并将控件创建为一个CWnd
。BOOL CRichEditControl50W::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID) { //Load the MSFTEDIT.DLL library. //HINSTANCE m_hInstRichEdit50W declared in RichEditControl50W.h m_hInstRichEdit50W = LoadLibrary("msftedit.dll"); if (!m_hInstRichEdit50W) { AfxMessageBox("MSFTEDIT.DLL Didn't Load"); return(0); } CWnd* pWnd = this; return pWnd->Create("RichEdit50W", NULL, dwStyle, rect, pParentWnd, nID); }
- 它实现了
CRichEditCtrl
的部分函数,我对其进行了修改,使其更易于使用。例如,我的SetTextTo50WControl()
函数void CRichEditControl50W::SetTextTo50WControl(CString csText, int nSTFlags, int nSTCodepage) { //Set the options. SETTEXTEX m_st50W declared in RichEditControl50W.h m_st50W.codepage = nSTCodepage; m_st50W.flags = nSTFlags; SendMessage(EM_SETTEXTEX, (WPARAM)&m_st50W, (LPARAM)(LPCTSTR)csText); }
指定了要写入控件的文本以及
SETTEXT
结构m_st50W
的标志,该结构是声明在 RichEditControl50W.h 中的成员变量。 GetTextRange50W()
以 Unicode 格式检索指定范围内的文本,并将其转换为 ANSI 字符串,以便 MFC 可以使用它。void CRichEditControl50W::GetTextRange50W(int ncharrMin, int ncharrMax) { //Set the CHARRANGE for the trRE50W = the characters sent by ENLINK m_trRE50W.chrg.cpMin = ncharrMin; m_trRE50W.chrg.cpMax = ncharrMax; //Set the size of the character buffers, + 1 for null character int nLength = int((m_trRE50W.chrg.cpMax - m_trRE50W.chrg.cpMin +1)); //create an ANSI buffer and a Unicode (Wide Character) buffer m_lpszChar = new CHAR[nLength]; LPWSTR lpszWChar = new WCHAR[nLength]; //Set the trRE50W LPWSTR character buffer = Unicode buffer m_trRE50W.lpstrText = lpszWChar; //Get the Unicode text SendMessage(EM_GETTEXTRANGE, 0, (LPARAM) &m_trRE50W); // Convert the Unicode RTF text to ANSI. WideCharToMultiByte(CP_ACP, 0, lpszWChar, -1, m_lpszChar, nLength, NULL, NULL); //Release buffer memory delete lpszWChar; return; }
- 析构函数释放 msftedit.dll。
CRichEditControl50W::~CRichEditControl50W() { //Free the MSFTEDIT.DLL library if(m_hInstRichEdit50W) FreeLibrary(m_hInstRichEdit50W); }
- 你可以编写自己的函数来模拟和/或改进
CRichEditCtrl
函数。只需查看 Visual Studio 中的 WINCTRL4.CPP、AFXCMN.H、AFXCMN.INL 和 VIEWRICH.CPP,你就可以大致了解如何实现它们。Msftedit.dll 和 Riched20.dll 非常相似...所有的CRichEditCtrl
消息都应该适用于CRichEditControl50W
。通过使用 Visual Studio 中的 OLE/COM 对象查看器查看两者,我发现的唯一重大区别是 Msftedit.dll 实现了一些我找不到文档的ITextDocument2
和ITextMsgFilter
接口。 - 正如你从下面我的更新应用程序的截图所看到的,你可以创建漂亮的表格并进行各种酷炫的文本格式化,这是 RichEdit 3.0 无法实现的 (尝试使用 riched20.dll 创建一个没有内部边框的表格,你就会明白我的意思了)
请参阅 MSDN 文档,了解富文本控件的更多功能。有关 RTF 代码的信息,请参阅 Microsoft Office Word 2003 Rich Text Format (RTF) Specification White Paper。
历史
- 2005年3月11日
- 添加了超链接处理功能。
- 为控件的上下文弹出菜单添加了更多文本编辑选项。