web-dev-qa-db-ja.com

TextBoxに適切で効率的な元に戻す/やり直し機能を実装する方法

元に戻す/やり直し機能を実装したいTextBoxがあります。私は 読んだ いくつかの元に戻す機能がすでにあるかもしれないが、バグがあるとは?とにかく、元に戻す機能とやり直し機能の両方を実装して、どうやってそれを実行するかを学びたいと思います。

Memento Pattern について読み、CodeProjectの Generic Undo/Redo の例をいくつか見ました。そして、パターンkiiindは理にかなっています。それを実装する方法に頭を抱えているようには見えません。そして、TextBoxのコンテンツに対してそれを効率的に行う方法。

もちろんtextbox.Textの場合TextChangesですが、特にTextBoxに大量のテキストが含まれている場合は、かなり高速に大量のメモリを消費します。

とにかく、私はこの機能を実装するための良い、明確で効率的な方法を実装する方法についてのアドバイスを探しています。一般的に、特にTextBoxの場合はc "、)

27
Svish

ネット System.ComponentModel名前空間にはIEditableObjectインターフェースが付属していますが、INotifyPropertyChangingおよびINotifyPropertyChangedを使用することもできます。 MVCパターンはまた、インターフェースがイベントを通じてモデルの変更に応答するようにし、テキストボックスの値を更新または復元します。

効果的にMementoパターン

これらを調べましたか? こちら の使い方です。

シンプルで高速なバージョンは、テキストボックスの状態OnTextChangedを保存することです。元に戻すたびに、配列の最後のイベントが返されます。ここではC#スタックタイプが便利です。また、インターフェイスをオフにした後、またはApplyの後に状態をクリアできます。

17
REA_ANDREW

最小限のコードでこれを実現する方法は次のとおりです(これは、単一のテキストボックスが付いた勝利フォームの背後にあるコードです)。

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アクションを順番に取り消すことができます。

7
jdoig

良い解決策はここにあります:

アプリケーションに元に戻す/やり直しまたは戻る/進む機能を追加

ndo/Redo Capable TextBox(winforms)

コードはVB.NETにありますが、簡単にC#に変換できます。オンラインコンバーターもご利用いただけます。

3
Pradeep Kumar

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

コマンドパターン

それを実装するようになったとき、私はそれがどれほどシンプルでエレガントであるかに驚いた。それは私にとって勝利です。

2
radsdau

元に戻す/やり直すときにも、選択を元の位置にリセットする必要があります。テキストボックス "textBox1"が1つしかないフォームを試してみるために、私の基本的でうまく機能するコードの最後にある "class Extensions"を見てください。

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

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

最小限のコードで書かれた取り消しスタックの例があります

 [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
bradgonesurfing

変更イベントをリッスンし、それが発生したときに、以前の状態と現在の状態のdiffをスタックにプッシュします。差分は、テキスト全体を格納するよりもはるかに小さくする必要があります。また、編集のたびに新しい元に戻す状態をスタックにプッシュしたくない場合もあります...たとえば、ユーザーがカーソル位置を変更するまで、すべての入力をまとめます。

1
mpen