<

Want to win a PS4? Go Premium and enter to win our High-Tech Treats giveaway. Enter to Win

x

Use CHtmlEditCtrl to Create a Simple HTML Editor

Published on
21,392 Points
11,492 Views
4 Endorsements
Last Modified:
Awarded
I needed a lightweight HTML editor to generate "rich text" emails, so I decided to explore the features and capabilities of the MFC CHtmlEditCtrl control.  I had looked into using the DHTML Edit Control (an Active X control) in the past, but this new (MFC 7) class supports that functionality, so I decided to work from that direction.
Just a dialog box and a controlI found a few quirks with the control.  There is (at least) one significant bug in the implementation and some "rough edges" that need to be smoothed out.  But with a little bit of finesse, it is possible to create a usable HTML editor with very few lines of code.

If you want a full-fledged Document/View system, then the sample code provided by Microsoft and elsewhere (see References) is your best bet.  I wanted a simple control that I could plop down into a dialog box so my user can start typing text and make it bold, italics, etc.  This turns out to be easy once you get over a few hurdles, and it is also possible to read-in HTML source text from a file or URL.

The Good
The underlying DHTML editing support is powerful and comprehensive.  For instance, the control automatically implements nearly all of the important keyboard command functions:

   Ctrl+B      Bold
   Ctrl+I        Italics
   Ctrl+U      Underline
   Ctrl+K      Create a Hyperlink
   Ctrl+spc   Remove formatting
   Ctrl+bksp Undo (last text edit or format change)

   Ctrl+X      Cut
   Ctrl+C      Copy
   Ctrl+V      Insert from clipboard

   Ctrl+F      Find (the "new style" that highlights all occurrences!)
   Ctrl+A      Select All
   Ctrl+L      Open new local HTML file
   Ctrl+N      New browser window on current URL
   Ctrl+P      Print...

It is capable of nearly any desired HTML edit action, including "Convert lines to a bullet list," "Set the font: size, color, face, background, etc.", "Insert a horizontal line", "Insert a button," and so forth.

You have complete access to the browser DOM -- you can get an IHTMLDocument2* and use it to iterate through the elements.  So you can programmatically do basically anything you want to do -- either in the HTML DOM or (as we shall see in the continuation of this article) by modifying the HTML text directly.

The Bad
As capable as it is, the DHTML editor seems partially unfinished.  For instance, it has no support for creating a <TABLE> element.  If a table already exists in the HTML, you can select it and delete it, or manually drag its handles to change the size, but that's about it.  

The built-in context menu shows a "Properties" command, but nothing happens when you select it.  It appears that Microsoft leaves implementation of this (complex) functionality up to the programmer -- but I think they should have disabled menu items that do nothing.

When you load an existing HTML page in design mode, you may be in for something of a shock... In edit mode, the scripts are not executed, so lots of hidden stuff is shown, and the page layout looks wonky.  It is possible to switch to "Browser View" but there are some cases where that fails for no apparent reason.

There are a few other "quirks" that we'll encounter as we explore this subject.

The Ugly
The thing that might prevent many programmers from using this as a simple control is that 1) The Toolbox does not provide a CHtmlEditCtrl tool, and 2) the MCF Class Wizard support does not provide a wizard for deriving from CHtmlEditCtrl -- you must do it manually.  (Note: You can derive from CHtmlEditView, though there are some peculiarities about putting a View into a dialog control).

Let's Get Started!
In this article, I'll provide a simple class object that will make it easy to put an HTML editor into a dialog box.  And I'll demonstrate how to use it.   In future articles, we'll look at some more advanced techniques.

First, we'll derive from CHtmlEditCtrl to (initially) provide just a few functions:
A means to instantiate the control on a dialog box.
Overrides to get rid of "Byte Order Marks" in the source text.
An option to disable (or replace) the built-in context menu.

The latter function is needed to avoid letting the user do things like navigate to a different page, or other things that don't make sense for some situations.

Here's the header file (HtmlEditCtrlEx.h):
#pragma once
#include <afxhtml.h>
// CHtmlEditCtrlEx control

class CHtmlEditCtrlEx : public CHtmlEditCtrl
{
	DECLARE_DYNCREATE(CHtmlEditCtrlEx)
protected:
public:
	CHtmlEditCtrlEx() { m_fEnableContextMenu=true; };    
	virtual ~CHtmlEditCtrlEx()  {};

	BOOL CreateFromStatic( UINT nID, CWnd* pParent );
	void EnableContextMenu( BOOL fEnab ) { m_fEnableContextMenu= fEnab; };
	HRESULT GetDocumentHTML( CString& sHtml ); // override discards BOM
	HRESULT SetDocumentHTML( CString& sHtml ); // override discards BOM

protected:
	BOOL m_fEnableContextMenu;
	DECLARE_MESSAGE_MAP()
public:
	virtual BOOL PreTranslateMessage(MSG* pMsg); // context menu suppression
};

Open in new window

And here's the source code  (HtmlEditCtrlEx.cpp):
#include "stdafx.h"
#include "HtmlEditCtrlEx.h"
// CHtmlEditCtrlEx

IMPLEMENT_DYNCREATE(CHtmlEditCtrlEx, CHtmlEditCtrl)
BEGIN_MESSAGE_MAP(CHtmlEditCtrlEx, CHtmlEditCtrl)
END_MESSAGE_MAP()

BOOL CHtmlEditCtrlEx::CreateFromStatic( UINT nID, CWnd* pParent ) {
	CStatic wndStatic;
	if ( !wndStatic.SubclassDlgItem(nID, pParent)) {
		return( FALSE );
	}
	CRect rc;
	wndStatic.GetWindowRect( &rc );
	pParent->ScreenToClient( &rc );
	return Create( 0, (WS_CHILD | WS_VISIBLE), rc, pParent, nID, 0 );
};

BOOL CHtmlEditCtrlEx::PreTranslateMessage(MSG* pMsg)
{
	if ( !m_fEnableContextMenu ) {
		switch (pMsg->message) {
		case WM_CONTEXTMENU:
		case WM_RBUTTONUP:
		case WM_RBUTTONDOWN:
		case WM_RBUTTONDBLCLK:
			if (pMsg->message==WM_RBUTTONUP) {
				// let parent handle context menu
				GetParent()->SendMessage(WM_CONTEXTMENU, pMsg->wParam, pMsg->lParam);
			}
			return TRUE; // eat it
		}
	}
	return CHtmlEditCtrl::PreTranslateMessage(pMsg);
}
// overide to discard BOM
HRESULT CHtmlEditCtrlEx::GetDocumentHTML( CString& sHtml )
{
	HRESULT hr= CHtmlEditCtrl::GetDocumentHTML( sHtml );
	if ( sHtml[0] != '<' ) {
		sHtml= sHtml.Mid(3);
	}
	return hr;
}
HRESULT CHtmlEditCtrlEx::SetDocumentHTML( CString& sHtml )
{
	HRESULT hr;
	int nOffset=0;
	if ( sHtml[0]==0xef && sHtml[1]==0xbb && sHtml[2]==0xbf ) { // BOM may be there
		nOffset= 3;
	}
	hr= CHtmlEditCtrl::SetDocumentHTML( sHtml.Mid(nOffset) );
	return hr;
}

Open in new window


The PreTranslateMessage override allows you to prevent the build-in context menu, while at the same time, process any WM_CONTEXT menu handler that you provide in your parent dialog box.  

The key function is CreateFromStatic.  This function looks for a STATIC control in the parent window and uses it to determine the size and placement for the HTML editor window.  It calls the CHtmlEditCtrl::Create() function, which ends up setting the browser control into design mode and navigating to about:blank -- to provide an empty slate.

Note the code in the SetDocumentHTML override where it checks the first few bytes for a special sequence,
    0xef 0xbb 0xbf
The CHtmlEditCtrl's SaveAs function uses a CStream-based technique that stores these at the beginning of the file.  It's called the "Byte Order Mark" (BOM).  In this case, I don't need the bytes and they look goofy, so I get rid of them.  In the continuation of this article, I'm going to add an Edit Source option and I want to show a clean text file.

To use this control:
1) Use the Visual Studio AppWizard to create an MFC dialog-based program.
2) In the Dialog Editor, use the toolbox to add a Static Text box at the location where you want the editor.  Set its ID to, for instance, IDST_HtmlBox
3) Add this to your OnInitDialog function:
	m_ctlEditHtml.CreateFromStatic( IDST_HtmlBox, this );
	// m_ctlEditHtml.EnableContextMenu( false ); // -- uncomment later

Open in new window

and add...
     #include "HtmlEditCtrlEx.h"

Open in new window

...at the top of the dialog's header file.

You now have an HTML editor that will handle all of the expected keyboard commands.   As you may have noticed in the earlier screenshot, I've added two buttons for Load and Save.  Here's the code for those functions:
void CEditHtmlDlg::OnBnClickedLoadfile() {
	wchar_t szFilter[] = L"HTML files (*.htm)|*.htm;*.html|"
		            L"All Files (*.*)|*.*||";
	CFileDialog dlg(TRUE,0,0,6,szFilter );
	if ( dlg.DoModal()!=IDOK ) {
		return;
	}
	//-------------------------- read the HTML file into memory
	CString sFile= dlg.GetPathName();
	CFile cf( sFile,CFile::modeRead );
	int nLen= (int)cf.GetLength();
	BYTE *p= new BYTE[nLen+2];
	int n= cf.Read( p,nLen );  // read it all
	p[nLen]=0;                 // Add null terminator for CString
	m_sHtml= p;                // save the HTML string as dialog member variable
	delete p;
	m_ctlEditHtml.SetDocumentHTML( m_sHtml ); // stuff it into the document
	m_ctHtmlSrcText.SetWindowTextW( m_sHtml );// and the source view
}

void CEditHtmlDlg::OnBnClickedSavefile() {
	m_ctlEditHtml.SaveAs(); // this one is easier :-)
}

Open in new window


Summary:
In this article, we created an object derived from CHtmlEditCtrl and placed it onto a dialog box.  For now, we can read an HTML file from disk, make changes (using keyboard commands only) and save it back out.  It's just a start, but because of the control's built-in functionality, it's a good distance toward a useful little utility for creating and modifying HTML files.

In future articles, we'll add an HTML source editor and explore how to access some editing functionality that is not directly available from the keyboard.

References:
Paul DiLascia's MSDN articles about using CHtmlView provided inspiration and guidance on setting up the control and overriding the context menu:

   MSJ aug2003   http://msdn.microsoft.com/en-us/magazine/cc164109.aspx
   MSJ sep2001   http://msdn.microsoft.com/en-us/magazine/cc301445.aspx
   MSJ jan2000   (apparently, no longer available!)

CHtmlEditCtrl Class
http://msdn.microsoft.com/en-us/library/h14ht0dh.aspx

CHtmlEditCtrlBase Class
http://msdn.microsoft.com/en-us/library/54542c1c.aspx

MSHTML Editing Overviews and Tutorials
http://msdn.microsoft.com/en-us/library/aa770039(VS.85).aspx

Document/View architecture examples:
    HTMLEdit Sample: Wraps the Internet Explorer MSHTML Editing Control
    http://msdn.microsoft.com/en-us/library/ea8hhwb6.aspx
    Using the new HTML Editing classes in MFC7
    http://www.codeproject.com/KB/MFC/mfchtmledit.aspx
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
If you liked this article and want to see more from this author,  please click the Yes button near the:
      Was this article helpful?
label that is just below and to the right of this text.   Thanks!
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
4
Comment
Author:DanRollins
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
2 Comments
 

Expert Comment

by:Articles101
Dan,

Aside from the technical reasons, the following items made this a top article that got my vote among the EE Editors and my yes vote above.

(a) It's a good example for giving a clear purpose of the article.
(b) It's a good example of putting itself into a proper technical context:  The Good;  The Bad; The Ugly.
(c) It has very good flow.  For example, a longer article like this needed a "Let's get started!"   Also, having a separate how-to-use heading is (I think) essential in a longer article (the "To use this control" heading).
(d) It has a summary that gives the user another chance to understand if the article would be helpful.
(e) it gives references for those who want or need more information
0
 
LVL 49

Author Comment

by:DanRollins
Thank you!  I hope that many readers find this article helpful.
0

Featured Post

Free learning courses: Active Directory Deep Dive

Get a firm grasp on your IT environment when you learn Active Directory best practices with Veeam! Watch all, or choose any amount, of this three-part webinar series to improve your skills. From the basics to virtualization and backup, we got you covered.

Join & Write a Comment

This is Part 3 in a 3-part series on Experts Exchange to discuss error handling in VBA code written for Excel. Part 1 of this series discussed basic error handling code using VBA. http://www.experts-exchange.com/videos/1478/Excel-Error-Handlin…
We’ve all felt that sense of false security before—locking down external access to a database or component and feeling like we’ve done all we need to do to secure company data. But that feeling is fleeting. Attacks these days can happen in many w…

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month