Developing an MFC Rich Edit 4.1 Control Application Using MSFTEDIT.DLL
(Published at CodeProject.com and CodeGuru.com, Mar 2005)


Download Source - 49 Kb, for VS .NET 2003
Download Demo App - 18 Kb

I've developed the first known Microsoft Foundation Classes (MFC) source code of a rich edit 4.1 control on the 'Net.  It even handles hyperlinks!

Introduction:

CRichEditControl50W is a CWnd-derived Rich Text Edit control v. 4.1 using the new, poorly documented msftedit.dll (MSFTEDIT_CLASS, or "RichEdit50W" classname) that ships with Windows XP. The CRichEditCtrl provided in VC++.NET only uses the old v. 3.0 rich edit control (RICHEDIT_CLASS, or "RichEdit20W"). There are no examples of using the new control in MFC that I could find anywhere on the net. So I decided to create my own after looking at the CRichEditCtrl and CRichEditView classes in VS. The lack of documentation made me do a whole lot of research to realize that msftedit.dll was not supported by VS.NET, or the CRichEditView class. I figured I'd save somebody else the headache of sorting this mess out.

Background:

I tried to update my ping/traceroute application's CRichEditCtrl with the new "RichEdit50W" window class. You can't simply substitute MSFTEDIT_CLASS for RICHEDIT_CLASS in richedit.h. Microsoft uses the 4.1 version in Wordpad XP, and has developed new, undocumented classes CRichEdit2View, CRichEdit2Ctrl, CRichEdit2Doc, CRichEdit2Cntr (which I found by looking at Wordpad.exe with a Hex Editor)... but they don't appear in Visual Studio .NET 2003 or 2005. They left the old WORDPAD example using riched20.dll. The "RichEdit50W" (or "MSFTEDIT_CLASS") class does not work with the current CRichEditView in VS; CRichEditView is hard-coded to load riched20.dll.

So I've done the work for the control...just download the demo application or source code and adapt for your uses.  Note that I've not created any new base classes like MS did, so you won't be able to use the document/view architecture to embed objects, unless you develop your own and recompile VS .NET...the RichEdit50W (or MSFTEDIT_CLASS) class does not work with the current CRichEditView in VS.  No matter what you do, CRichEditView is hard-coded to load riched20.dll.

Rich Edit 50W Control Application developed with Visual Studio .NET 2003

Using the code:

Steps:

  1. Create a new MFC application in Visual Studio. In the "Application Type" tab, select "Single Document", and deselect the Document/View Architecture option...you don't need this. I named my application RE50W. Once complete, remove all references to the CChildView class, which is the default view constructed by CMainFrame; you don't need this, either.

  2. Add the files RichEditControl50W.cpp and RichEditControl50W.h to your project. This is the CRichEditControl50W control I created.

  3. In the application's re50w.cpp, add:

    #include "RichEditControl50W.h"    // before
    #include "MainFrm.h"
  4. In MainFrame.cpp, add:

    #include "RichEditControl50W.h"     // before
    #include "MainFrm.h"
  5. In MainFrame.f, declare CRichEditControl50W m_REControl50W as a protected member:

    etc..........................
    protected:        // control bar embedded members
    CStatusBar m_wndStatusBar;
    CToolBar m_wndToolBar;
    CRichEditControl50W m_REControl50W;
    etc..................
  6. In MainFrame.cpp, under OnCreate, change the way the view is created:

    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. Add the OnContextMenu function to CMainFrame to handle your custom popup menu for the rich edit control. Create a custom popup menu named IDR_REPOPUP (see source code for more details):

    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. Add command and update handlers and functions for your popup menu. An example for the Copy function:

    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. Add #include <richedit.h> to the bottom of stdafx.h. (This isn't actually necessary, but just to be safe...).

Handling Hyperlinks:

This was a lot of fun, due to the serious lack of documentation.  WordPad XP doesn't handle RTF hyperlinks, but Word 2003 does, so I knew it could be done. The problem is the GetTextRange() function, or EM_GETTEXTRANGE message, which you send to the control to get the hyperlink's text.  If you use this per the instructions in Visual Studio, the TEXTRANGE lpstrText LPSTR buffer only returns the first character of the range.  I found others on the 'Net who have called this a bug in the Rich Edit Control.  The problem is really Unicode vs. ANSI.  If you use Unicode in your richedit control, EM_GETTEXTRANGE won't work right if you use the default instructions in VS.

Instead of TEXTRANGE , you have to use the TEXTRANGEW structure, defined in richedit.h.  The lpstrText buffer in this structure is LPWSTR (wide-string) to handle Unicode.  However, after you get the lpstrText back from EM_GETTEXTRANGE, you have to convert it back to ANSI, so you can use it with MFC!  To do this, you have to fill an LPSTR buffer with the LPWSTR buffer contents using WideCharToMultiByte().  Then you can use the LPSTR buffer contents in a ShellExecute() to open your default email, web browser, etc. app, based on the hyperlink (i.e., http:// opens your default web browser). 

Setting the ENM_LINK event mask for the control in the CMainFrame OnCreate function causes the control to send a WM_NOTIFY message to the parent window, CMainFrame, whenever a hyperlink in the control is acted on. To catch and use this message; using the Class View in Visual Studio, add the OnNotify function to CMainFrame, then modify it:

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() (see below) converts the Unicode LPWSTR lpszWChar to the LPSTR  member variable m_lpszChar, which ShellExecute() uses to open the appropriate application for your hyperlink.

If you open the demo app, and click on the webpage or email links, your default application(s) should handle them.

A look at CRichEditControl50W:

  1. On Create, it loads msftedit.dll and creates the control as a 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. It implements some of the functions of CRichEditCtrl, which I've modified to make easier to use. For example, my SetTextTo50WControl() function:
    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);
    }

    specifies both the text to be written to the control and the flags for the SETTEXT structure m_st50W, which is a member variable declared in RichEditControl50W.h.
     

  3. GetTextRange50W() retrieves the text in a given range in Unicode and converts it into an ANSI string so MFC can use it:
    void CRichEditControl50W::GetTextRange50W(int ncharrMin, int ncharrMax)
    {
    //Set the RRANGE 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. The destructor releases msftedit.dll:
    CRichEditControl50W::~CRichEditControl50W()
    {
        //Free the MSFTEDIT.DLL library
        if(m_hInstRichEdit50W)
            FreeLibrary(m_hInstRichEdit50W);
    }
  5. You can write your own functions to emulate and/or improve upon the CRichEditCtrl functions. Just have a look at WINCTRL4.CPP, AFXCMN.H, AFXCMN.INL, and VIEWRICH.CPP in Visual Studio and you can get an idea of how to implement them. Msftedit.dll and riched20.dll are very similar... all of the CRichEditCtrl messages should work for CRichEditControl50W. The only big differences I could find by looking at both with the OLE/COM Object Viewer in Visual Studio is that msftedit.dll implements the ITextDocument2 and ITextMsgFilter interfaces, which I can find no documentation on.
     
  6. As you can see from the screenshot below of my updated app, you can create nice tables and do all kinds of cool text formatting with the new rich edit control, which you can't do with RichEdit 3.0 (try creating a table with no interior borders with riched20.dll, and you'll see what I mean):

Check the MSDN documentation for many more things you can do with rich edit controls. For information on RTF codes, see the Microsoft Office Word 2003 Rich Text Format (RTF) Specification White Paper.

 Download: 

 MSFTEDIT.DLL, v. 5.41.15.1509, Rich Text Edit Control, v4.1, Zip format, 311 Kb

History:

11 Mar 05: 
- Added hyperlink handling capability
- Added more text-editing options to the control's context popup menu

Back

Updated July 12, 2006