Click here to Skip to main content
15,890,579 members
Articles / Programming Languages / C#

Solving Undo/Redo Problems with the MSHTML Editor

Rate me:
Please Sign up or sign in to vote.
4.76/5 (9 votes)
10 Jun 2014CPOL3 min read 20.2K   11   5
This article presents solutions to problems relating to Undo/Redo when using the MSHTML editor.

Introduction 

Recently, I made the fateful decision to add a simple HTML editor to an application.  I found some code that uses the MSHTML control to achieve this and began to work to make it work right.  This article presents a few of the problems I encountered related to Undo/Redo, and the solutions I found with the hope it can save some time for others.   

Background  

MSHTML is a Microsoft library that provides very powerful capabilities for creating and editing HTML documents.  An introduction to the use of it as an editor can be found here.  You can find quite a bit of sample code online that demonstrates how to create an HTML editor using this component.  For example, see this article by Carl Nolan and this archived code sample from Nikhil Kothari. 

While I found the available references on the web very useful, they didn't solve some problems related to Undo and Redo.  In particular,

  1. I needed to be able to group compound operations into a single Undo/Redo unit, and
  2. I needed to be able to clear the Undo buffer, so that a user couldn't undo the initial assignment of raw HTML to the editor control. 

This article outlines the solutions I found for these problems. 

Grouping Compound Operations   

The granularity of the operations you can perform in code is sometimes smaller than the user initiated action.  For example, when you are adding a column to a table,  you need to iterate through each row in the table, inserting a cell in the same location in each row, like this:   

C#
// find the existing row the user is on and perform the insertion
int index = cell.cellIndex;
foreach (mshtml.IHTMLTableRow row in table.rows)
{
    row.insertCell(index);
}   

Although the user has requested a single operation (insert a column), the MSHTML interface treats each programatic insertion as an operation, so the user would have to hit ^Z table.rows.count times to undo the operation. 

The solution I found is to use the IMarkupServices interface provided by Microsoft like this: 

C#
var markupServices = (mshtml.IMarkupServices)document;
markupServices.BeginUndoUnit(0);
...
//Multiple operations
...
markupServices.EndUndoUnit();

This groups all operations into a single undo/redo unit. 

Clearing the Undo Buffer 

 If you initialize your MSHTML control with some HTML content, that assignment will be part of the undo buffer, so, unless you do something about it, the end user can undo the assignment with ^Z (or clicking an undo control.)  Avoiding this turned out to be a bit more painful than I expected.  If you find an easier way, please help us all out by posting it. 

To clear the Undo buffer, you need to gain access to the  IOleUndoManager for the MSHTML control.  Thanks to Jon David for his post here for most of the code.  First, you need to define the IOleUndoManager interface as follows: 

C#
/// <summary>
/// These interface definitions are required  for obtaining the IOldUndoManager, which 
/// is needed for clearing the undo buffer.
/// </summary>
[ComVisible(true),
    ComImport(), 
    Guid("6d5140c1-7436-11ce-8034-00aa006009fa"),
    InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]

public interface UCOMIServiceProvider
{
    IntPtr QueryService(ref Guid guidService, ref Guid riid);
}
public interface IOleParentUndoUnit { }
public interface IOleUndoUnit { }
public interface IEnumOleUndoUnits { }

[ComVisible(true), ComImport(), Guid("D001F200-EF97-11CE-9BC9-00AA00608E01"),
     InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleUndoManager
{
   void Open(IOleParentUndoUnit ParentUndoUnit);
   void Close(IOleParentUndoUnit ParentUndoUnit, bool Commit);
   void Add(IOleUndoUnit UndoUnit);
   void GetOpenParentState(ref int State);
   void DiscardFrom(IOleUndoUnit UndoUnit);
   void UndoTo(IOleUndoUnit UndoUnit);
   void RedoTo(IOleUndoUnit UndoUnit);
   void EnumUndoable(ref IEnumOleUndoUnits ppEnum);
   void EnumRedoable(ref IEnumOleUndoUnits ppEnum);
   void GetLastUndoDescription(ref string Description);
   void GetLastRedoDescription(ref string Description);
   void Enable(bool Enable);
}

 Now define the Guids you'll use for identifying it as follows in your code: 

C#
private Guid SID_SOleUndoManager = new Guid("D001F200-EF97-11CE-9BC9-00AA00608E01");
private Guid IID_IOleUndoManager = new Guid("D001F200-EF97-11CE-9BC9-00AA00608E01"); 

Write a function that returns the UndoManager: 

C#
private IOleUndoManager GetUndoManager() 
{
   IOleUndoManager oUndoManager;
   UCOMIServiceProvider isp =(UCOMIServiceProvider)document;
   IntPtr ip = isp.QueryService( ref SID_SOleUndoManager, ref IID_IOleUndoManager);
   oUndoManager = (IOleUndoManager)Marshal.GetObjectForIUnknown(ip);
   return oUndoManager;
}  

 And use it as follows to clear the buffer: 

C#
/// <summary>
/// Clear the undo buffer so that the user can't accidentally "undo"
/// the assignment of the HTML to the control
/// </summary>
public void ClearUndoBuffer()
{
   IOleUndoManager oUndoMgr = GetUndoManager();
   oUndoMgr.Enable(false);
   oUndoMgr.Enable(true);
}   

 UPDATE:  A comment below says that the enabling and disabling of the undo manager shown above does not work in some circumstances. He found that replacing:

C#
oUndoMgr.Enable(false);
oUndoMgr.Enable(true);

with

C#
oUndoMgr.DiscardFrom(null);

in the ClearUndoBuffer() method solved the problem for him.

Points of Interest   

I hope this is of some help to you if you're working with the MSHTML control.  The intent of this article is to share some of the research I did to find these solutions and group them together in a place others can reference them.  Much of what is here came initially from the referenced authors.

History  

June 4, 2013 - Initial Version  
June 10, 2014 - Added alternative way of clearing the undo/redo buffer based on comment below.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Product Manager
United States United States
I've been programming in C, C++, Visual Basic and C# for over 35 years. I've worked at Sierra Systems, ViewStar, Mosaix, Lucent, Avaya, Avinon, Apptero, Serena and now Guidewire Software in various roles over my career.

Comments and Discussions

 
QuestionSimpler method Pin
UKAndrewC7-Feb-15 11:39
UKAndrewC7-Feb-15 11:39 
Questioninserting column in table Pin
Ravi_Chaudhary22-Dec-14 2:54
Ravi_Chaudhary22-Dec-14 2:54 
QuestionClearing undo stack doesn't (always) work Pin
Member 12639409-Mar-14 23:42
Member 12639409-Mar-14 23:42 
AnswerRe: Clearing undo stack doesn't (always) work Pin
Tom Clement10-Jun-14 9:56
professionalTom Clement10-Jun-14 9:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.