如何为TextBox实现高效的撤消/重做功能

时间:2009-02-28 09:35:54

标签: c# undo-redo memento

我有一个TextBox,我想为其实现撤消/重做功能。我have read它可能已经有一些轻微的撤销功能,但它有错误吗?无论如何,我想实现撤消和重做功能,也只是为了了解如何继续这样做。

我已阅读有关Memento Pattern的内容,并在CodeProject的Generic Undo/Redo示例中查看了一些内容。而模式kiiind是有道理的。我似乎无法围绕如何实现它。以及如何有效地完成TextBox的内容。

当然我可以在textbox.Text时存储TextChanges,但这会非常快地占用相当多的内存,尤其是TextBox包含大量文本时。

所以无论如何,我正在寻找一些关于如何实现一种良好,清晰和有效的方法来实现这一功能的建议。一般而言,特别是对于TextBox c“,)

7 个答案:

答案 0 :(得分:15)

.NET System.ComponentModel命名空间附带IEditableObject界面,您也可以使用INotifyPropertyChangingINotifyPropertyChanged。 MVC Pattern还可以使您的界面通过事件响应模型中的更改,从而更新或恢复文本框的值。

实际上是 Memento模式

你仔细看过这些吗? Here是如何做到的。

简单快捷的版本是存储文本框OnTextChanged的状态。每个撤消都将返回Array中的最后一个事件。 C#Stack Type在这里很方便。一旦离开界面或Apply之后,您就可以清除状态。

答案 1 :(得分:6)

这是一种用最少的代码实现它的方法: (这是win表单背后的代码,上面有一个文本框)

public partial class Form1 : Form
{
    Stack<Func<object>> undoStack = new Stack<Func<object>>(); 
    public Form1()
    {
        InitializeComponent();
    }
    private void textBox_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.U && Control.ModifierKeys == Keys.Control && undoStack.Count > 0)
            undoStack.Pop()();            
    }
    private void textBox_KeyPress(object sender, KeyPressEventArgs e)
    {            
        if (e.KeyChar != 'u' || Control.ModifierKeys != Keys.Control)
        {
            var textBox = (TextBox)sender;
            undoStack.Push(textBox.Text(textBox.Text));
        }
    }
}
public static class Extensions
{
    public static Func<TextBox> Text(this TextBox textBox, string text)
    {            
        return () => { textBox.Text = text; return textBox; };
    }
}

通过为其他输入类型实现扩展方法,undoStack可以为整个UI提供服务,按顺序撤消所有UI操作。

答案 2 :(得分:2)

这里可以找到一个好的解决方案:

Add Undo/Redo or Back/Forward Functionality to your Application

Undo/Redo Capable TextBox (winforms)

代码在VB.NET中,但您可以轻松地将其转换为C#而不需要太多努力。也可以使用在线转换器。

答案 3 :(得分:1)

这是我在该主题上找到的最有用的页面,更通用,适用于撤销/重做堆栈上的不同对象类型。

Command Pattern

当我实施它时,我感到惊讶的是它最终变得如此简单和优雅。 这对我来说是一场胜利。

答案 4 :(得分:1)

我需要在撤消/重做时将选择重置为原始位置。在我的基本和良好工作代码的底部观看“类扩展”,只需一个文本框“textBox1”即可尝试:

public partial class Form1 : Form
{
    Stack<Func<object>> undoStack = new Stack<Func<object>>();
    Stack<Func<object>> redoStack = new Stack<Func<object>>();

    public Form1()
    {
        InitializeComponent();
        textBox1.KeyDown += TextBox1_KeyDown;
    }

    private void TextBox1_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.ControlKey && ModifierKeys == Keys.Control) { }
        else if (e.KeyCode == Keys.U && ModifierKeys == Keys.Control)
        {
            if(undoStack.Count > 0)
            {
                StackPush(sender, redoStack);
                undoStack.Pop()();
            }
        }
        else if (e.KeyCode == Keys.R && ModifierKeys == Keys.Control)
        {
            if(redoStack.Count > 0)
            {
                StackPush(sender, undoStack);
                redoStack.Pop()();
            }
        }
        else
        {
            redoStack.Clear();
            StackPush(sender, undoStack);
        }
    }

    private void StackPush(object sender, Stack<Func<object>> stack)
    {
        TextBox textBox = (TextBox)sender;
        var tBT = textBox.Text(textBox.Text, textBox.SelectionStart);
        stack.Push(tBT);
    }
}

public static class Extensions
{
    public static Func<TextBox> Text(this TextBox textBox, string text, int sel)
    {
        return () => 
        {
            textBox.Text = text;
            textBox.SelectionStart = sel;
            return textBox;
        };
    }
}

答案 5 :(得分:0)

我会监听一个更改事件,当它发生时,将前一个状态的diff推送到堆栈上。差异应该比存储整个文本小得多。此外,您可能不希望在每次编辑时将新的撤消状态推送到堆栈中...例如,我将所有打字组合在一起,直到用户更改光标位置为止。

答案 6 :(得分:0)

最智能的方法是使用不可变的持久对象。永远不要对对象进行更改,只创建与旧版本稍有不同的新对象。只需在热路径上克隆树的部分,就可以有效地完成这项工作。

我有一个用最少代码编写的撤销堆栈的例子

 [Fact]
public void UndoStackSpec()
{
    var stack = new UndoStack<A>(new A(10, null));

    stack.Current().B.Should().Be(null);

    stack.Set(x => x.B, new B(20, null));

    stack.Current().B.Should().NotBe(null);
    stack.Current().B.P.Should().Be(20);

    stack.Undo();

    stack.Current().B.Should().Be(null);

}

其中A和B为所有属性上具有private setters的类,即 immutable

class A : Immutable
{
    public int P { get; private set; }
    public B B { get; private set; }
    public A(int p, B b)
    {
        P = p;
        B = b;
    }
}

class B : Immutable
{
    public int P { get; private set; }
    public C C { get; private set; }
    public B(int p, C c)
    {
        P = p;
        C = c;
    }
}

class C : Immutable
{
    public int P { get; private set; }
    public C(int p)
    {
        P = p;
    }
}

您可以在此处找到完整的来源https://gist.github.com/bradphelan/5395652