2009-02-28 131 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; 
     }; 
    } 
}