65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (13投票s)

2005 年 3 月 11 日

5分钟阅读

viewsIcon

245655

downloadIcon

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 中的 CRichEditCtrlCRichEditView 类之后,我决定创建自己的。这可能是一项琐碎的工作,但由于文档的缺乏,我花了大量时间研究才意识到 msftedit.dll 并未得到 VS.NET 的支持,CRichEditView 类也同样如此。我想着或许可以为其他人省去解决这个麻烦的精力。

背景

我曾试图更新我的 ping/traceroute 应用程序的 CRichEditCtrl 以使用新的 "RichEdit50W" 窗口类。你不能简单地在 richedit.h 中用 MSFTEDIT_CLASS 替换 RICHEDIT_CLASS。微软在 Wordpad XP 中使用了 4.1 版本,并开发了新的、未公开的类 CRichEdit2ViewCRichEdit2CtrlCRichEdit2DocCRichEdit2Cntr (我通过使用 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 的高级文本格式化功能。

使用代码

步骤

  1. 在 Visual Studio 中创建一个新的 MFC 应用程序。在 "应用程序类型" 选项卡中,选择 "单文档",然后 **取消选中** "文档/视图体系结构" 选项...你不需要这个。我给我的应用程序起了个名字叫 RE50W。完成后,删除所有对 CChildView 类的引用,这是 CMainFrame 默认创建的视图;你也不需要这个。
  2. 将文件 RichEditControl50W.cppRichEditControl50W.h 添加到你的项目中。这就是我创建的 CRichEditControl50W 控件。
  3. 在应用程序的 re50w.cpp 中,添加
    #include "RichEditControl50W.h"    // before
    #include "MainFrm.h"
  4. MainFrame.cpp 中,添加
    #include "RichEditControl50W.h"     // before
    #include "MainFrm.h"
  5. MainFrame.f 中,将 CRichEditControl50W m_REControl50W 声明为受保护的成员
    etc..........................
    protected:        // control bar embedded members
    CStatusBar m_wndStatusBar;
    CToolBar m_wndToolBar;
    CRichEditControl50W m_REControl50W;
    etc..................
  6. MainFrame.cppOnCreate 函数下,更改视图的创建方式
    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..........................
  7. 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());
        }
    }
  8. 为你的弹出菜单添加命令和更新处理程序及函数。例如 "复制" 功能
    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
    }
  9. 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:// 会打开你的默认网页浏览器)。

CMainFrameOnCreate 函数中设置控件的 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_lpszCharShellExecute() 会使用它来打开适用于你的超链接的相应应用程序。

如果你打开演示应用程序,然后点击网页或电子邮件链接,你的默认应用程序应该会处理它们。

CRichEditControl50W 概览

  1. 在 "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);
    }
  2. 它实现了 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 中的成员变量。

  3. 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;
    }
  4. 析构函数释放 msftedit.dll
    CRichEditControl50W::~CRichEditControl50W()
    {
        //Free the MSFTEDIT.DLL library
        if(m_hInstRichEdit50W)
            FreeLibrary(m_hInstRichEdit50W);
    }
  5. 你可以编写自己的函数来模拟和/或改进 CRichEditCtrl 函数。只需查看 Visual Studio 中的 WINCTRL4.CPPAFXCMN.HAFXCMN.INLVIEWRICH.CPP,你就可以大致了解如何实现它们。Msftedit.dllRiched20.dll 非常相似...所有的 CRichEditCtrl 消息都应该适用于 CRichEditControl50W。通过使用 Visual Studio 中的 OLE/COM 对象查看器查看两者,我发现的唯一重大区别是 Msftedit.dll 实现了一些我找不到文档的 ITextDocument2ITextMsgFilter 接口。
  6. 正如你从下面我的更新应用程序的截图所看到的,你可以创建漂亮的表格并进行各种酷炫的文本格式化,这是 RichEdit 3.0 无法实现的 (尝试使用 riched20.dll 创建一个没有内部边框的表格,你就会明白我的意思了)

请参阅 MSDN 文档,了解富文本控件的更多功能。有关 RTF 代码的信息,请参阅 Microsoft Office Word 2003 Rich Text Format (RTF) Specification White Paper

历史

  • 2005年3月11日
    • 添加了超链接处理功能。
    • 为控件的上下文弹出菜单添加了更多文本编辑选项。
© . All rights reserved.