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:
-
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.
-
Add the files
RichEditControl50W.cpp
and
RichEditControl50W.h
to your project. This is the
CRichEditControl50W
control I created.
-
In the application's
re50w.cpp
, add:
#include "RichEditControl50W.h" // before
#include "MainFrm.h"
-
In
MainFrame.cpp
, add:
#include "RichEditControl50W.h" // before
#include "MainFrm.h"
-
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..................
-
In
MainFrame.cpp
, under
OnCreate
, change the
way the
view is created:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
DWORD w_RichEd = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_VSCROLL |
ES_AUTOVSCROLL | WS_HSCROLL | ES_AUTOHSCROLL | ES_MULTILINE;
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;
}
m_REControl50W.LimitText50W(-1);
//Set the control to accept the maximum amount of text
DWORD REOptions = (ECO_AUTOVSCROLL | ECO_AUTOHSCROLL | ECO_NOHIDESEL | ECO_SAVESEL | ECO_SELECTIONBAR);
m_REControl50W.SetOptions50W(
ECOOP_OR, //The type of operation
REOptions ); //Options
m_REControl50W.SendMessage( EM_AUTOURLDETECT, TRUE, 0);
m_REControl50W.SetEventMask50W(
ENM_SELCHANGE | ENM_LINK //New event mask for the rich edit control
);
m_REControl50W.SetDefaultCharFormat50W(
CFM_COLOR | CFM_BOLD | CFM_SIZE |
CFM_FACE | CFM_BACKCOLOR,
RGB(0,0,0),
!CFE_BOLD,
"Trebuchet MS",
200,
RGB(255,255,255));
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}}}}";
m_REControl50W.SetSel50W(-1, -1);
m_REControl50W.SetTextTo50WControl(
m_csMessage,
ST_SELECTION,
1200);
etc..........................
-
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());
}
}
-
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);
pCmdUI->Enable(m_crStatus.cpMin != m_crStatus.cpMax);
}
-
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)
{
ENLINK *p_enRE50W;
p_enRE50W = (ENLINK *)lParam;
if(p_enRE50W && p_enRE50W->msg == WM_LBUTTONUP)
{
m_REControl50W.GetTextRange50W(p_enRE50W->chrg.cpMin, p_enRE50W->chrg.cpMax);
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:
- 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)
{
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);
}
- 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)
{
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
.
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)
{
m_trRE50W.chrg.cpMin = ncharrMin;
m_trRE50W.chrg.cpMax = ncharrMax;
int nLength = int((m_trRE50W.chrg.cpMax - m_trRE50W.chrg.cpMin +1));
m_lpszChar = new CHAR[nLength];
LPWSTR lpszWChar = new WCHAR[nLength];
m_trRE50W.lpstrText = lpszWChar;
SendMessage(EM_GETTEXTRANGE, 0, (LPARAM) &m_trRE50W);
WideCharToMultiByte(CP_ACP, 0, lpszWChar, -1, m_lpszChar, nLength, NULL,
NULL);
delete lpszWChar;
return;
}
- The destructor releases
msftedit.dll
:
CRichEditControl50W::~CRichEditControl50W()
{
if(m_hInstRichEdit50W)
FreeLibrary(m_hInstRichEdit50W);
}
- 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.
- 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