Как реализовать хорошие и эффективные функции отмены/повтора для TextBox
У меня есть TextBox, который я бы хотел реализовать для функции отмены/повтора. Я прочитал, что у него могут быть некоторые незначительные функции отмены, но что это глючит? В любом случае, я хотел бы реализовать как функцию отмены и повтора, так и просто узнать, как вы это сделаете.
Я прочитал о Memento Pattern и посмотрел на Общий вариант отмены/повтора в CodeProject. И образец имеет смысл. Я просто не могу представить себе, как его реализовать. И как сделать это эффективно для содержимого TextBox
.
Конечно, я мог бы просто хранить textbox.Text
, когда TextChanges
, но это довольно быстро обернуло бы довольно много памяти, особенно если TextBox
содержало много текста.
Так или иначе, я ищу несколько советов о том, как реализовать хороший, понятный и эффективный способ реализации этой функциональности. И вообще, и особенно для TextBox c ",)
Ответы
Ответ 1
Пространство имен .NET System.ComponentModel
поставляется с интерфейсом IEditableObject
, вы также можете использовать INotifyPropertyChanging
и INotifyPropertyChanged
. MVC Pattern также сделает так, чтобы ваш интерфейс реагировал на изменения в модели посредством событий, таким образом обновляя или восстанавливая значение вашего текстового поля.
Эффектно шаблон заклинания.
Вы изучали их? Здесь - это как.
Простая и быстрая версия - сохранить состояние текстового поля OnTextChanged
. Каждое отклонение возвращает последнее событие в массиве. Здесь был бы полезен тип стека С#. Вы можете очистить состояние, как только вы покинете интерфейс, или после Apply
.
Ответ 2
Здесь можно достичь минимального кода:
(Это код позади формы выигрыша с одним текстовым полем на нем)
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 может обслуживать весь ваш пользовательский интерфейс, отменяя все действия пользовательского интерфейса в порядке.
Ответ 3
Хорошее решение можно найти здесь:
Добавить функцию Отменить/Повторить или Назад/Вперед к вашему приложению
Undo/Redo Capable TextBox (winforms)
Код находится в VB.NET, но вы можете легко преобразовать его в С# без особых усилий. Также доступны онлайн-конвертеры.
Ответ 4
Это самая полезная страница, которую я нашел по этой теме, более общий, подходящий для разных типов объектов в стеке отмены/повтора.
Командная строка
Когда я добрался до его реализации, я был удивлен тем, насколько простым и элегантным он оказался.
Это делает его победой для меня.
Ответ 5
Самый умный способ - с неизменяемыми постоянными объектами. Никогда не изменяйте объект, только создавайте новые объекты, которые немного меняются от старой версии. Это можно сделать несколько эффективно, только клонируя части дерева на горячем пути.
У меня есть пример стека отмены, написанного с минимальным кодом
[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
Ответ 6
Мне нужно reset выбрать также его исходные позиции при отмене/повторении. Смотрите "Расширения класса", в нижней части моего простого базового и хорошо работающего кода, для формы с одним текстовым полем "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;
};
}
}
Ответ 7
Я бы прослушал событие изменения, и когда это произойдет, нажмите diff
предыдущего состояния и текущего состояния на стек. Разница должна быть намного меньше, чем хранение всего текста. Кроме того, вы можете не захотеть перетащить новое состояние отмены в стек при каждом редактировании... Я бы разделил все набрав вместе, пока пользователь не изменит позицию курсора, например.