【问题标题】:How to start using the .NET Framework UndoEngine Class?如何开始使用 .NET Framework UndoEngine 类?
【发布时间】:2023-08-24 16:45:01
【问题描述】:

今天我发现 FW 4.5 有自己的撤销管理器(如果我理解良好的话)http://msdn.microsoft.com/en-us/library/System.ComponentModel.Design.UndoEngine%28v=vs.110%29.aspx

好吧,我找不到任何关于如何开始使用此类的示例,只是为了对基于文本的控件进行简单的撤消/重做,我知道其他替代方法可以做可撤消的事情,但我只是想学习如何用这个。

当我尝试使用构造函数时,它有任何要传递的参数,而且 Intellisense 没有向我显示 System.ComponentModel.Design.UndoEngine 类的任何方法,我真的不知道如何使用它。

有人可以用C#VBNET 的例子来说明我们的例子吗? (如果可能,我更喜欢 VBNET 文档)

【问题讨论】:

  • AddUndoUnit 之类的一些方法听起来像是一个框架(即您仍然需要添加很多东西,例如检测更改 - 最困难的部分)与 Etienne 的方法。如果您认为 Etienne 很复杂,请查看 UndoUnit 的文档:msdn.microsoft.com/en-us/library/…
  • 你需要自己实现。 UndoEngine 是抽象的,您需要从中派生。此外,当您构建自己的组件并希望提供撤消/重做功能(而不是运行时)时,它是否仅在设计时有用,因为它依赖于仅在设计时可用的服务。也是自 FW 2.0 以来的课程
  • UndoEngine ad-hoc 使用非常不寻常,因为标准设计器主机(即 Visual Studio)本机支持大多数撤消操作,即使对于自定义控件也是如此。你的方案是什么?
  • @Simon Mourier 我的情况是任意的。正如我在我的问题中所说的并且我在赏金评论中所说的那样,我只是想了解例如使用 TextBox 的用法。感谢您的评论
  • 您的方案没有说明您想用撤消引擎做什么。撤消/重做一个文本控件——即使是从基础控件派生的一个——已经可以在没有任何花哨的情况下工作。

标签: c# .net vb.net winforms undo-redo


【解决方案1】:

UndoEngine 是一个抽象类,Visual Studio 和设计人员以自己的方式实现 UndoEngine,这些必须是私有的或不可用于重新分发。你将无法使用它,实际上抽象类只是一个几乎没有实现的接口,它根本不是一个撤消框架。

您仍然需要编写自己的撤消管理,但是从 UndoEngine 类派生撤消引擎的好处是,它可以轻松地与 VS 和其他基于 MS 的编辑器托管/集成。

  1. 如果您想在 Visual Studio 文档编辑器中提供编辑体验,那么您必须从 UndoEngine 派生您的 Undo 框架类,VS 将自动突出显示禁用撤消/重做按钮,并在您的类上调用撤消/重做方法。
  2. 如果您想在自己的应用程序中使用 UndoEngine,UndoEngine 对您没有任何帮助,您必须自己编写每个功能。 UndoEngine 只是管理 Undo/Redo 堆栈,真正的工作在 UndoUnit 内部。它基于工作单元概念,您的每一个动作实际上都应该代表一个可以撤消的工作。

最简单的 UndoEngine 实现

假设您正在更改一个全局变量,

// 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);
            }
    }

【讨论】:

    【解决方案2】:

    在这里找到 UndoEngine 的实现以及如何使用它: https://github.com/icsharpcode/SharpDevelop/search?q=ReportDesignerUndoEngine&ref=cmdform

    HTH

    【讨论】:

      【解决方案3】:

      如果您的问题是如何在运行时使用它,那么答案在MSDN

      在设计时指定通用撤消/重做功能。

      所以我怀疑它在运行时是否易于使用。
      如果您的意思是使用此类的自定义用户控件示例,我找不到,抱歉。

      【讨论】:

      • 我的意思是最后一个,我的意思是如何实现它,还是谢谢你!