2009-02-28 14 views
24

私は、元に戻す/やり直し機能を実装したいTextBoxを持っています。私はhave readは、いくつかのわずかな元に戻す機能が既にあるかもしれないが、それはバグですか?とにかく、やり直しとやり直しの両方の機能を実装したいと思っています。TextBoxの優れた効率的なUndo/Redo機能の実装方法

私はMemento Patternについて読んでおり、CodeProjectのGeneric Undo/Redoの例を見てきました。そして、そのパターンは理にかなっています。私はそれを実装する方法について私の頭を包んでいるようには思えません。そして、それをTextBoxの内容に対して効果的に行う方法。

もちろん、textbox.TextTextChangesに保存することもできますが、それはかなりのメモリをかなり高速に抱き込みます。特に、多くのテキストが含まれている場合は特にです。

とにかく、この機能を実装するうえで、明確で効率的な方法を実装する方法に関するアドバイスを探しています。どちら.NET

答えて

15

)、「一般的に、特にテキストボックスcに対してSystem.ComponentModel名前空間がIEditableObjectインターフェースが付属して、あなたもINotifyPropertyChangingINotifyPropertyChangedを使用することができます。MVCパターンはまた、あなたのインターフェースがモデルの変化に応答することをそれを作るだろうそのため、あなたのテキストボックスの値を更新または復元。

効果的Mementoパターン

をイベントを通じて、あなたはこれらの?Hereに見てどのようにある。

持っていたことがあります

シンプルで速いバージョンは、テキストボックスOnTextChangedの状態を保存することです。それぞれの取り消しは、配列内の最後のイベントを返します。ここではC#のスタック型が便利でしょう。あなたはまた、インターフェイスをオフにしたり、Applyの後に状態をクリアすることができます。他の入力タイプundoStackのための拡張メソッドを実装することで、 (これはその上に、単一のテキストボックスと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; }; 
    } 
} 

+0

だから、拡張版のテキストボックスを作成しますか?そのインタフェースを実装していますか? – Svish

+0

あなたは何レベルのアンドゥを必要としますか?アンドゥの振る舞いは、オブジェクト上にあるのか、それともテキストボックスのデータだけになるのでしょうか? –

3

ここでは最小限のコードでそれを達成するための方法ですすべてのUIアクションを元に戻すことができます。

+1

なぜ文字列のスタックの代わりに関数のスタックを使用するのですか? – Jack

+2

@Jack最後の文をもう一度読みます。このコードを拡張して、フォーム全体の*コントロールの変更を逆順に取り消すことができます。 – Dan

0

私は変更イベントを待ち受け、発生したときに前の状態と現在の状態のdiffをスタックにプッシュします。 diffは、テキスト全体を格納するよりはるかに小さくなければなりません。また、すべての編集時に新しいUNDO状態をスタックにプッシュしたくないかもしれません。たとえば、ユーザーがカーソル位置を変更するまで、すべての入力を一緒にしていました。

1

これは、元に戻す/やり直しスタック上のさまざまなオブジェクトタイプに適した、より一般的なトピックで見つかった最も役立つページです。

Command Pattern

私はそれを実装するに着いたとき、私はそれがされてしまった方法をシンプルかつエレガントな驚きました。 それは私のために勝つ。

1

最もスマートな方法は、不変の永続オブジェクトです。オブジェクトに変更を加えないでください。古いバージョンからわずかに変更される新しいオブジェクトのみを作成します。これは、ホットパス上のツリーの一部をクローンするだけで、やや効率的に行うことができます。

は、私はあなたが完全なソースを見つけることができるすべてのプロパティでprivate settersを持つクラスとしてAとBが 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; 
    } 
} 

、すなわち

[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); 

} 

最小限のコードで書かれたアンドゥスタックの例を持っていますここをクリックhttps://gist.github.com/bradphelan/5395652

+0

これは難読版ですか?おそらく、一文字の変数名より少し冗長な例を提供するために、あなたの答えを更新することができますか? – Dan

+1

変数名は無関係です。彼らは設定されている以外の意味を持たず、元に戻すと呼び出され、未設定であることが示されます。実装は要点であり、完全に理解するために必要なすべてのソースコードを提供します。 – bradgonesurfing

+3

変数名が無関係な唯一の時間は、変数そのものが無関係な場合です。その場合、それらは完全に削除する必要があります。変数が目的を果たしている場合、たとえその目的が任意のサンプルクラスに限定されていても、コードによって提示される実際のアイデアの読者の消費を容易にするような形で名前を付けるべきです。理由の例のコードは、しばしば、例えば、 "クラスカー"または "クラスBankAccount"は重要なものではなく、基本的な概念を理解するのに役立つからです。 – Dan

1

私も選択を元の位置にリセットする必要があります元に戻す/やり直すときに繰り返します。テキストボックス "textBox1"を1つだけ試してみると、基本的でうまく動作するコードの一番下にある「クラス拡張機能」を見てみましょう。

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; 
     }; 
    } 
} 
関連する問題