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