Using MSHTML Editing in VC6 Doc/View Applications






4.82/5 (19 votes)
How to enable MSHTML editing in a view in VC6
Introduction
I've recently published a couple of articles on CodeProject covering customisation of the MSHTML editor. Those articles are VC7 and MFC7 specific. Naturally there have been some posts asking how one can do the same things using VC6. This article will show you how you can use the MSHTML editor in a VC6 MFC document/view application.
Background
The version of MFC that ships with VC6 includes support for displaying HTML documents in a view. You derive your view class from the CHtmlView
class, override a few virtual
methods and voila, there's an embedded HTML display in your app.
MFC7, as shipped with VC7, extends this to include a CHtmlEditView
class. See
this [^]
article for a quick overview of the class.
Scope of this article
I didn't set out to completely rewrite the CHtmlEditView
class. Instead, I'll show you how to put the MSHTML control into edit mode and show
how to implement the various commands it supports. You'll have to do most of the work yourself.
Structural differences between this implemenation and the MFC one
MFC adds HTML editing capability toCHtmlView
by bolting on a class called CHtmlEditCtrlBase
which actually implements the low
level details. They did it that way so they could reuse the code in the CHtmlEditCtrl
class (also new to MFC7). Being a lazy sod I chose to
add the edit support into a direct derivative of CHtmlView
called (so as not to conflict with MFC's naming) CHtmlEditorView
.
Putting MSHTML into edit mode
is trivial. All you do is get anIHTMLDocument2
interface for the document being displayed and call the put_designMode(()
function
passing a case sensitive BSTR
specifying edit mode as either On
or Off
. You can only do this once a document has been
loaded into MSHTML and each time you navigate to a new document you need to repeat the process. I do it in the OnNavigateComplete2()
event
handler. In code it looks like this.
void CHTMLEditorView::OnNavigateComplete2(LPCTSTR strURL)
{
BSTR bs = SysAllocString(L"On");
CHtmlView::OnNavigateComplete2(strURL);
// Get a pointer to our document
m_pHTMLDoc = (IHTMLDocument2 *) GetHtmlDocument();
if (m_pHTMLDoc != (IHTMLDocument2 *) NULL)
m_pHTMLDoc->put_designMode(bs);
SysFreeString(bs);
}
which will always force the document into edit mode. Once that's done you can click around on the document to your hearts content changing things, moving them
around and generally messing up the page.
What about formatting the content?
That's the next step. TheIHTMLDocument2
interface has an execCommand()
method that lets you execute commands on the current
selection in the MSHTML editor. For the purpose of this article I've only implemented the Bold
command, which looks like this.
void CHTMLEditorView::Bold(BOOL bState)
{
BSTR bCmd = SysAllocString(L"Bold");
VARIANT_BOOL vb;
VARIANT vValue;
vValue.vt = VT_BOOL;
vValue.boolVal = bState;
if (m_pHTMLDoc != (IHTMLDocument2 *) NULL)
m_pHTMLDoc->execCommand(bCmd, VARIANT_FALSE, vValue, &vb);
SysFreeString(bCmd);
}
we set up a BSTR
containing our command verb and a VARIANT
containing the desired state. Then we call
IHTMLDocument2::execCommand
to actually execute the command. The second parameter in the call indicates that we don't want any user interface
shown. In the case of the Bold
command there isn't a user interface required but some other commands may support one. That second parameter gives
us the chance to suppress the user interface if we wish. The fourth parameter is a pointer to a VARIANT_BOOL
which recieves a value indicating
the success of the operation.
All of the MSHTML commands follow this pattern however it should be clear that the variant type you assign to the variant depends on the datatype which in turn depends on the command. Setting the font face requires a string, setting the text colour requires a specially formatted string and so forth.
Querying the current state of a selection
is done using theIHTMLDocument2::QueryCommandState()
or IHTMLDocument2::QueryCommandValue()
methods. In each case you pass the
name of the command you're querying (for example, is the selection bolded?) and a location to return the value. The demo project has a
button on the toolbar to let you toggle Bold
on selected text. Naturally it's expected that if the selection is already bold the button will
be depressed indicating that fact. So whenever we change the selection we need to query the Bold
state. The code to do this looks like
BOOL CHTMLEditorView::Bold()
{
BSTR bCmd = SysAllocString(L"Bold");
VARIANT_BOOL vb = FALSE;
if (m_pHTMLDoc != (IHTMLDocument2 *) NULL)
m_pHTMLDoc->queryCommandState(bCmd, &vb);
SysFreeString(bCmd);
return vb;
}
which queries the document for the Bold
state. This is returned in the VARIANT_BOOL
which we simply return to the caller. The
IHTMLDocument2::queryCommandState()
method works only for BOOL
properties. If we wanted to query the font name we'd do it like
this
CString CHTMLEditorView::FontName()
{
BSTR bCmd = SysAllocString(L"FontName");
VARIANT vb;
CString csValue;
if (m_pHTMLDoc != (IHTMLDocument2 *) NULL)
m_pHTMLDoc->queryCommandValue(bCmd, &vb);
SysFreeString(bCmd);
if (vb.vt == VT_BSTR)
csValue = CW2CT(vb.bstrVal);
return csValue;
}
which receives the return value in a VARANT
. Then it checks that the return value is indeed a string; if so it converts it from the
BSTR
returned in the VARIANT
to a CString
and returns the CString
. If the return value isn't a string
the conversion attempt doesn't take place and the empty CString
is returned instead. Naturally the caller needs to allow for the case where an
empty CString
is returned, which might happen if one were to execute a font name query on an image, which leads to the next topic.
Determing if a command can be executed
This is done by calling theIHTMLDocument2::queryCommandEnabled()
method. This takes the command name and a pointer to a VARIANT_BOOL
which receives the return value. If the command is valid for the current selection you get back TRUE
, otherwise you get back FALSE
.
The code that determines if the Bold
command is valid for the current selection looks like this.
BOOL CHTMLEditorView::CanBold()
{
BSTR bCmd = SysAllocString(L"Bold");
VARIANT_BOOL vb = FALSE;
if (m_pHTMLDoc != (IHTMLDocument2 *) NULL)
m_pHTMLDoc->queryCommandEnabled(bCmd, &vb);
SysFreeString(bCmd);
return vb;
}
which is almost identical to querying the current value.
Putting it all together
So now we have the code in place to setBold
on the current selection, determine if the current selection is Bold
or not and
determine if the current selection can be Bold
ed. We'd use that code in our toolbar Bold
button command handler and UI state
update handler like this.
void CHTMLEditorView::OnBold()
{
Bold(Bold() ? 1 : 0);
}
void CHTMLEditorView::OnUpdateBold(CCmdUI* pCmdUI)
{
pCmdUI->SetCheck(Bold() ? 1 : 0);
pCmdUI->Enable(CanBold());
}
which is trivial in the extreme.
What commands are available?
I refer you to theIHTMLDocument2::execCommand()
documentation available online at MSDN for the exhaustive list. I'm not providing a URL because
MSDN sometimes change things around on their site and invalidate links.
Wich versions of Microsoft Internet Explorer will this work with?
According to the MSDN documentation theIHTMLDocument2
interface was added in version 4.0 of Microsoft Internet Explorer. It's probably safe to
assume that almost every Windows machine you install your code on will have at least that version. You may run across the occasional command that requires a
later version of Internet Explorer - if so the MSDN documentation will list version requirements.
Using the code
The source file download contains a copy of theCHtmlEditorView
header and implementation files. As aforesaid I've only implemented the
Bold
command - it's up to you to add handlers for any other commands you're interested in. The class does include an override of
OnNavigateComplete2()
which puts the document into edit mode.
You derive your own class from this, or use the class directly if you wish.
History
26 April 2004 - Initial version.