eliminar - crear un panel en tiempo de ejecucion c#
Cómo implementar una buena y eficiente funcionalidad de deshacer/rehacer para un TextBox (7)
Tengo un TextBox para el que me gustaría implementar la funcionalidad de deshacer / rehacer. He leído que puede que ya tenga alguna funcionalidad de deshacer, pero que tiene errores? De todos modos, me gustaría implementar la funcionalidad de deshacer y rehacer también para aprender cómo seguir adelante y hacer eso.
He leído sobre el patrón Memento y miré un ejemplo en un ejemplo de Deshacer / Rehacer genérico en CodeProject. Y el patrón de sentido tiene sentido. Parece que no puedo entender cómo implementarlo. Y cómo hacerlo de manera eficiente para los contenidos de un TextBox
.
Por supuesto, podría almacenar textbox.Text
cuando TextChanges
, pero eso abarcaría bastante memoria bastante rápido, especialmente si el TextBox
contenía una gran cantidad de texto.
De todos modos, estoy buscando algunos consejos sobre cómo implementar una forma buena, clara y eficiente de implementar esta funcionalidad. Tanto en general como especialmente para un TextBox c ",)
Aquí hay una forma de lograrlo con un código mínimo: (Este es el código que se encuentra detrás de un formulario de ganancia con un solo cuadro de texto)
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; };
}
}
Al implementar un método de extensión para otros tipos de entrada, undoStack puede atender la totalidad de su IU, deshaciendo todas las acciones de UI en orden.
El espacio de nombres .NET System.ComponentModel
viene con una interfaz IEditableObject
, también puede usar INotifyPropertyChanging
y INotifyPropertyChanged
. MVC Pattern también haría que su interfaz responda a los cambios en el modelo a través de eventos, actualizando o restaurando el valor de su cuadro de texto.
Efectivamente el Patrón Memento .
¿Has echado un vistazo a estos? Here hay una forma de
Una versión simple y más rápida sería almacenar el estado del cuadro de texto OnTextChanged
. Cada deshacer devolvería el último evento en un Array. El tipo de pila C # sería útil aquí. También puede borrar el estado una vez que esté fuera de la interfaz o después de Apply
.
Escucharía un evento de cambio, y cuando ocurra, empuje la diff
del estado anterior y el estado actual en una pila. Las diferencias deben ser mucho más pequeñas que almacenar todo el texto. Además, es posible que no desee insertar un nuevo estado de deshacer en la pila en cada edición ... Yo agruparía todos los tipos hasta que el usuario cambia la posición del cursor, por ejemplo.
Esta es la página más útil que encontré en el tema, más genérica, adecuada para diferentes tipos de objetos en la pila de deshacer / rehacer.
Cuando llegué a implementarlo, me sorprendió lo simple y elegante que terminó siendo. Eso hace que sea una victoria para mí.
La forma más inteligente es con objetos persistentes inmutables. Nunca haga un cambio en un objeto, solo haga nuevos objetos que cambien ligeramente de la versión anterior. Esto se puede hacer de manera un tanto eficiente solo clonando partes del árbol en la ruta activa.
Tengo un ejemplo de una pila de deshacer escrita con un código mínimo
[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);
}
donde A y B son clases con private setters
en todas las propiedades, es decir, 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;
}
}
Puede encontrar la fuente completa aquí https://gist.github.com/bradphelan/5395652
También necesito restablecer la selección a sus posiciones originales al deshacer / rehacer. Mire "class Extensions", en la parte inferior de mi código básico y que funciona bien, para obtener un formulario con un solo cuadro de texto "textBox1" para probar:
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;
};
}
}
Una buena solución se puede encontrar aquí:
Agregue Deshacer / Rehacer o Atrás / Adelante Funcionalidad a su aplicación
Deshacer / Rehacer Cuadro de texto con capacidad (winforms)
El código está en VB.NET, pero puede convertirlo fácilmente a C # sin mucho esfuerzo. Convertidores en línea también están disponibles.