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

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

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

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

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


person Svish    schedule 28.02.2009    source источник


Ответы (7)


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

Фактически Образец сувенира.

Вы это видели? Вот как это сделать.

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

person REA_ANDREW    schedule 28.02.2009
comment
Итак, не могли бы вы сделать расширенную версию текстового поля? реализовать этот интерфейс? - person Svish; 28.02.2009
comment
Сколько уровней отмены вам потребуется: n уровней или 1? Также будет ли поведение отмены относиться к объекту или исключительно к данным текстового поля? - person REA_ANDREW; 28.02.2009

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

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

person jdoig    schedule 14.09.2010
comment
Зачем использовать стек функций вместо стека строк? - person Jack; 10.09.2012
comment
@Jack Прочтите последнее предложение еще раз. Этот код можно расширить, чтобы вы могли отменить любое изменение в любом элементе управления по всей форме в обратном порядке. - person Dan Bechard; 17.03.2016
comment
@jdoig - Разве вы не сохраняете каждый раз весь текст в стеке, что делает это ужасно неэффективным? - person waqasahmed; 04.05.2019

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

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

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

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

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

person Pradeep Kumar    schedule 20.04.2012
comment
Спасибо, это простое решение мне очень помогло - person Zoran Basic; 20.04.2017

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

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

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

person radsdau    schedule 13.11.2012
comment
Мне это нравится. Я только что реализовал с нуля Undo / Redo с Command Pattern. Удивительно или нет, но в итоге я пришел к очень похожему подходу, который был реализован в предоставленной ссылке. - person stephanmg; 22.01.2021

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

person mpen    schedule 14.09.2010

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

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

 [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

person bradgonesurfing    schedule 16.04.2013
comment
Это запутанная версия? Возможно, вы могли бы обновить свой ответ, чтобы привести примеры с чем-то более подробным, чем однобуквенные имена переменных? - person Dan Bechard; 17.03.2016
comment
Имена переменных значения не имеют. Они не имеют никакого значения, кроме того, что они устанавливаются, а затем вызывается отмена и показано, что они не установлены. Реализация заложена в сути и дает весь исходный код, необходимый для полного понимания. - person bradgonesurfing; 17.03.2016
comment
Единственные имена временных переменных не имеют значения, когда сами переменные не имеют значения, и в этом случае их следует полностью удалить. Если переменные служат какой-либо цели, даже если эта цель ограничена произвольным примером класса, они должны быть названы таким образом, чтобы представлять что-то осязаемое, чтобы облегчить потребление читателем реальных идей, представленных кодом. Код примера причины так часто содержит, например, Класс Car или класс BankAccount не потому, что эти вещи важны, а потому, что они помогают читателю понять лежащие в основе концепции. - person Dan Bechard; 18.03.2016