Как реализовать хорошие и эффективные функции отмены / повтора для текстового поля

у меня есть текстовое поле, для которого я хотел бы реализовать функцию отмены/повтора. Я!--5-->читать что он может иметь некоторые небольшие функции отмены уже, но что он глючит? В любом случае, я хотел бы реализовать как функции отмены, так и повтора, чтобы узнать, как вы это сделаете.

Я читал о Memento Pattern и посмотрел некоторые на Универсальный Отменить / Повторить пример на CodeProject. И узор kiiind смысл. Я просто не могу понять, как это реализовать. И как это сделать эффективно для содержания a TextBox.

конечно, я мог бы просто хранить textbox.Text, когда TextChanges, но это будет обнимать довольно много памяти довольно быстро, особенно если TextBox содержит много текста.

так или иначе, я ищу некоторые советы о том, как реализовать хороший, ясный и эффективный способ реализации этой функции. Как вообще, так и специально для текстовое поле с")

7 ответов


интернет .Сети System.ComponentModel пространство имен с IEditableObject интерфейс, вы также можете использовать INotifyPropertyChanging и INotifyPropertyChanged. Шаблон MVC также сделает так, что ваш интерфейс реагирует на изменения в модели через события, обновляя или восстанавливая значение вашего текстового поля.

эффективно Memento Pattern.

вы в них заглядывали? здесь - это как.

простая и более быстрая версия будет хранить состояние поле OnTextChanged. Каждая отмена возвращает последнее событие в массиве. Тип стека C# был бы удобен здесь. Вы можете очистить состояние, как только вы выключите интерфейс или после Apply.


вот способ достичь этого с минимальными код: (Это код за победу форма с одним текстовым полем на нем)

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 может обслуживать весь ваш пользовательский интерфейс, отменяя все действия пользовательского интерфейса по порядку.


хорошее решение можно найти здесь:

добавить функцию отмены / повтора или Назад / Вперед в приложение

отменить / повторить способное текстовое поле (winforms)

код в VB.NET, но вы можете легко преобразовать его в C# без особых усилий. Онлайн конвертеры, также доступны.


Это самая полезная страница, которую я нашел по теме, более общая, подходящая для разных типов объектов в стеке отмены/повтора.

Команда

когда я добрался до его реализации, я был удивлен, насколько простым и элегантным он оказался. Для меня это победа.


самый умный способ - с неизменяемыми постоянными объектами. Никогда не вносите изменения в объект, только создавайте новые объекты, которые немного отличаются от старой версии. Это можно сделать несколько эффективно, только клонируя части дерева на горячем пути.

у меня есть пример стека отмены, написанного с минимальным кодом

 [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 по всем свойствам ie 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


мне также нужно сбросить выделение в исходные позиции при отмене / повторном выполнении. Смотрите "расширения классов", в нижней части моего основного и хорошо работающего кода, для формы с одним текстовым полем" 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;
        };
    }
}

Я бы прослушал событие изменения, и когда это произойдет, нажмите diff предыдущего состояния и текущего состояния в стек. Различия должны быть намного меньше, чем сохранение всего текста. Кроме того, при каждом редактировании может не потребоваться вводить новое состояние отмены в стек... Я бы собрал все набрав вместе, пока пользователь не изменит положение курсора, например.