How to start using the .NET Framework UndoEngine Class?
Asked Answered
V

3

16

Today I've discovered that the FW 4.5 has their own undoredo manager (if I understood good) http://msdn.microsoft.com/en-us/library/System.ComponentModel.Design.UndoEngine%28v=vs.110%29.aspx

Well, I can't find any example about how to start using this class just to make a simple undo/redo of a text based control, I know other alternatives to do undoable things, but just I want to learn how to use this.

When I try to use the constructor it has any parameter to be passed, and also the Intellisense does not shows me any method for the System.ComponentModel.Design.UndoEngine class, really I don't know how I could use it.

Someone could illustrate ours with an example for C# or VBNET? (I preffer VBNET documentation if possibly, please)

Vaulting answered 22/11, 2013 at 15:56 Comment(6)
some of the methods like AddUndoUnit make it sound like it is literally a framework (ie you still have to add lots of stuff like detecting changes - the hard part) vs Etienne's. If you thought Etienne's was complex, look at the docs for the UndoUnit: msdn.microsoft.com/en-us/library/…Counterattack
You need to make your own implementation. UndoEngine is abstract and you need to derive from it. Also is it only usefule during DesignTime when you build your own components and want to provide undo/redo functionality (not runtime), cause it relies on services that are only available during DesignTime. Also is the class there since FW 2.0Mcclendon
UndoEngine ad-hoc usage is quite unusual as standard designer hosts (Visual Studio, namely) support most undo operations natively even for custom controls. What's your scenario?Hhd
@Simon Mourier My scenario is any. As I said in my question and also I remarked in the bounty comment I just would like to learn the usage for example with a TextBox. thanks for commentVaulting
Your scenario doesn't explain what you want to do with the undo engine. Undo/redo a text control - even one that derives from the base one - already works without anything fancy.Hhd
This class is meant to be used by the design-time support of controls, not at runtime.Baksheesh
R
12

UndoEngine is an abstract class, Visual Studio and Designers have implemented UndoEngine in their own way, and those must be private or not available for redistribution. You will not be able to use it, in fact abstract class is just an interface with little implementation, it is not at all an undo framework.

You still have to write your own undo management, however benefit of deriving your undo engine from UndoEngine class is, it can be easily hosted/integrated with VS and other MS based editors.

  1. If you want to provide an editing experience inside Visual Studio document editor, then you have to derive your Undo framework class from UndoEngine, VS will automatically highlight disable undo/redo buttons and will call undo/redo methods on your class.
  2. If you want to use UndoEngine inside your own application, UndoEngine will not help you for anything, you will have to write every functionality by yourself. UndoEngine just manages stack of Undo/Redo, real work is inside UndoUnit. It is based on Unit of Work concept, where your every action should actually represent a work that can be undone.

The simplest UndoEngine Implementation

Let us say you are changing a global variable,

// following code uses big UndoUnit

public void SetGlobalVariable(object v){
    var oldValue = GlobalVariable;

    GlobalVariable = v;

    var action = new UndoUnit{
        UndoAction = ()=>{
            GlobalVariable = oldValue;
        },
        RedoAction = ()=>{
            GlobalVariable = v;
        }
    };
    UndoManager.Add(action);
}


/// <summary>
/// Used to indicates the designer's status 
/// </summary>
public enum UndoUnitState
{
    Undoing,
    Redoing,
}

/// <summary>
/// A UndoUnitBase can be used as a IOleUndoUnit or just a undo step in 
/// a transaction  
/// </summary>
public class UndoUnitBase : IOleUndoUnit
{
    public Action UndoAction {get;set;}
    public Action RedoAction {get;set;}

    private string name = null;
    private Guid clsid = Guid.Empty;

    private bool inDoAction = false;
    private bool isStillAtTop = true;
    private UndoUnitState unitState = UndoUnitState.Undoing;

    protected UndoUnit UnitState
    {
        get { return unitState; }
        set { unitState = value; }
    }

    /// <summary>
    /// </summary>
    /// <param name="undoManager"></param>
    /// <param name="name"></param>
    internal UndoUnitBase(string name)
    {
        this.name = name;
    }

    ~UndoUnitBase()
    {
    }

    /// <summary>
    /// </summary>
    protected bool InDoAction
    {
        get
        {
            return inDoAction;
        }
    }

    public string UndoName
    {
        get
        {
            return name;
        }
        set
        {
            this.name = value;
        }
    }

    public Guid Clsid
    {
        get { return clsid; }
        set { clsid = value; }
    }

    /// <summary>
    /// This property indicates whether the undo unit is at the top (most recently added to)
    /// the undo stack. This is useful to know when deciding whether undo units for operations
    /// like typing can be coallesced together.
    /// </summary>
    public bool IsStillAtTop
    {
        get { return isStillAtTop; }
    }

    /// <summary>
    /// This function do the actual undo, and then revert the action to be a redo 
    /// </summary>
    /// <returns>objects that should be selected after DoAction</returns>
    protected abstract void DoActionInternal();

    /// <devdoc>
    ///     IOleUndoManager's "Do" action.
    /// </devdoc>
    void IOleUndoUnit.Do(IOleUndoManager oleUndoManager)
    {
        Do(oleUndoManager);
    }

    protected virtual int Do(IOleUndoManager oleUndoManager)
    {
        try
        {
            if(unitState== UndoUnitState.Undoing){
                 UndoAction();
            }else{
                 RedoAction();
            }

            unitState = (unitState == UndoUnitState.Undoing) ? UndoUnitState.Redoing : UndoUnitState.Undoing;
            if (oleUndoManager != null)
                oleUndoManager.Add(this);
            return VSConstants.S_OK;
        }
        catch (COMException e)
        {
            return e.ErrorCode;
        }
        catch
        {
            return VSConstants.E_ABORT;
        }
        finally
        {
        }
    }

    /// <summary>
    /// </summary>
    /// <returns></returns>
    void IOleUndoUnit.GetDescription(out string pBstr)
    {
        pBstr = name;
    }

    /// <summary>
    /// </summary>
    /// <param name="clsid"></param>
    /// <param name="pID"></param>
    /// <returns></returns>
    void IOleUndoUnit.GetUnitType(out Guid pClsid, out int plID)
    {
        pClsid = Clsid;
        plID = 0;
    }

    /// <summary>
    /// </summary>
    void IOleUndoUnit.OnNextAdd()
    {
        // We are no longer the top most undo unit; another one was added.
        isStillAtTop = false;
    }
}

public class MyUndoEngine : UndoEngine, IUndoHandler
    {                
            Stack<UndoEngine.UndoUnit> undoStack = new Stack<UndoEngine.UndoUnit>();
            Stack<UndoEngine.UndoUnit> redoStack = new Stack<UndoEngine.UndoUnit>();

            public ReportDesignerUndoEngine(IServiceProvider provider) : base(provider)
            {
            }

            #region IUndoHandler
            public bool EnableUndo {
                    get {
                            return undoStack.Count > 0;
                    }
            }

            public bool EnableRedo {
                    get {
                            return redoStack.Count > 0;
                    }
            }                

            public void Undo()
            {
                    if (undoStack.Count > 0) {
                            UndoEngine.UndoUnit unit = undoStack.Pop();
                            unit.Undo();
                            redoStack.Push(unit);
                    }
            }

            public void Redo()
            {
                    if (redoStack.Count > 0) {
                            UndoEngine.UndoUnit unit = redoStack.Pop();
                            unit.Undo();
                            undoStack.Push(unit);
                    }
            }
            #endregion

            protected override void AddUndoUnit(UndoEngine.UndoUnit unit)
            {
                    undoStack.Push(unit);
            }
    }
Reyreyes answered 14/12, 2013 at 15:33 Comment(0)
O
3

If your question is how to use it at runtime, then the answer is in MSDN:

Specifies generic undo/redo functionality at design time.

So I doubt that it is easily usable at runtime.
If you meant an example of custom user control utilizing this class, I can't find any, sorry.

Outpoint answered 23/11, 2013 at 22:14 Comment(1)
I meant the last, I mean how to implement it, thankyou anyways!Vaulting
A
2

Find an implementation of the UndoEngine and how to use it right here: https://github.com/icsharpcode/SharpDevelop/search?q=ReportDesignerUndoEngine&ref=cmdform

HTH

Adam answered 9/12, 2013 at 9:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.