2009-02-28 126 views
24

我有一个TextBox,我想实现撤销/重做功能。我have read,它可能已经有一些轻微的撤消功能,但它是越野车?无论如何,我想实现撤销和重做功能,也只是为了学习如何继续前进和做到这一点。如何实现TextBox的高效撤销/重做功能

我已阅读关于Memento Pattern,并在CodeProject上看到了一些Generic Undo/Redo示例。而模式kiiind是有道理的。我似乎无法围绕如何实施它。以及如何有效地为TextBox的内容做到这一点。

当然,我可以在TextChanges的时候存储textbox.Text,但是这样会很快占用很多内存,特别是如果TextBox包含大量文本。

因此,无论如何,我正在寻找一些建议,关于如何实现一个很好,清晰和高效的方式来实现这个功能。无论是在一般的,尤其是对于一个TextBox C”)

回答

15

的.NET System.ComponentModel命名空间配备了一个IEditableObject接口,您也可以使用INotifyPropertyChangingINotifyPropertyChanged。MVC模式也将使你的界面响应模型中的更改通过这样的事件更新或恢复您的文本框的值。

有效的备忘录模式

你有过一个研究这些?Here是如何。

一个简单而快捷的版本将用于存储文本框OnTextChanged的状态。每个撤消操作都会返回数组中的最后一个事件。 C#堆栈类型在这里很方便。您也可以在离开界面后或者在Apply之后清除状态。

+0

所以,你会做一个文本框的扩展版呢?实现该接口? – Svish 2009-02-28 12:19:47

+0

需要多少级别的撤销才能需要n级或1级?撤消行为是否在对象上或仅在文本框的数据上? – 2009-02-28 13:57:02

3

这里是一种用最少的代码来实现它: (这是一个双赢的形式背后的代码上有一个单一的文本框)

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可以为整个用户界面提供服务,并依次撤消所有用户界面操作。

+1

为什么使用一堆函数而不是一堆字符串? – Jack 2012-09-10 03:07:11

+2

@Jack再次读最后一句。此代码可以扩展为允许您在相反的顺序中撤消* any *控件中的任何*更改。 – Dan 2016-03-17 14:50:06

0

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

1

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

Command Pattern

当我到了实现它,我很惊讶它是如何简单而优雅的结束了。 这对我来说是一个胜利。

1

最聪明的方法是使用不可变的持久对象。不要对对象进行更改,只会使新对象与旧版本稍有变化。这可以通过在热路径上克隆部分树来有效地完成。

我用最少的代码编写的撤消堆栈的例子

[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

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