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

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

Я читал о шаблоне сувенира и немного посмотрел на примере Generic Undo / Redo на CodeProject. И картина имеет смысл. Кажется, я просто не могу понять, как это реализовать. И как это сделать эффективно для содержимого TextBox.

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

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

Ответы [ 7 ]

15 голосов
/ 28 февраля 2009

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

Эффективно Шаблон Памяти .

Вы смотрели на это? Здесь - это как.

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

6 голосов
/ 14 сентября 2010

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

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

2 голосов
/ 20 апреля 2012

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

Добавление функций отмены / возврата или возврата / пересылки в ваше приложение

Возможность отменить / повторить TextBox (winforms)

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

1 голос
/ 25 августа 2016

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

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

Шаблон команды

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

0 голосов
/ 16 апреля 2013

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

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

 [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

0 голосов
/ 14 сентября 2010

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

...