Cómo implementar una buena y eficiente funcionalidad de deshacer / rehacer para un cuadro de texto

StackOverflow https://stackoverflow.com/questions/597792

  •  11-09-2019
  •  | 
  •  

Pregunta

Tengo un cuadro de texto que me gustaría implementar la funcionalidad de deshacer / rehacer para. Me han leído que podría tener algunas funciones de deshacer ligero ya, pero que está libre de errores? De todas formas, me gustaría poner en práctica tanto deshacer y rehacer funcionalidad también sólo para aprender cómo va a ir adelante y hacerlo.

He leído acerca de la Memento Patrón y se veía un poco en un ejemplo Genérico deshacer / rehacer en CodeProject. Y el patrón de kiiind tiene sentido. Me parece que no puede envolver mi cabeza en torno a la manera de ponerla en práctica. Y la forma de hacerlo de manera eficiente de los contenidos de un TextBox.

Por supuesto que podría simplemente tienda textbox.Text cuando TextChanges, pero eso sería abrazar a un buen montón de memoria muy rápido, especialmente si el TextBox contenía una gran cantidad de texto.

Así que de todos modos, estoy buscando algunos consejos sobre cómo poner en práctica una forma buena, clara y eficiente de implementar esta funcionalidad. Tanto en general y especialmente para un cuadro de texto c",)

¿Fue útil?

Solución

El espacio de nombres de .NET System.ComponentModel viene con una interfaz IEditableObject, también se puede utilizar INotifyPropertyChanging y INotifyPropertyChanged. Patrón MVC también haría que su interfaz responde a los cambios en el modelo a través de eventos y así actualizar o restaurar el valor de su cuadro de texto.

Efectivamente, el Memento Patrón .

¿Ha tenido una mirada a estos? Aquí es una forma de.

Una versión simple y más rápida sería la de almacenar el estado de la OnTextChanged cuadro de texto. Cada deshacer devolvería el último evento de una matriz. El tipo C # Pila sería útil aquí. Se podría cancelar el estado una vez que esté fuera de la interfaz también o después Apply.

Otros consejos

Esta es una forma de lograrlo con el código mínimo: (Este es el código detrás de un formulario victoria con un único cuadro de texto en él)

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; };
    }
}

Mediante la implementación de un método de extensión para otros tipos de entrada del undoStack puede dar servicio a la totalidad de su interfaz de usuario, la interfaz de usuario deshacer todas las acciones en orden.

Una buena solución se puede encontrar aquí:

Añadir deshacer / rehacer o Atrás / Adelante funcionalidad a su aplicación

Deshacer / rehacer TextBox Capaz (winforms)

El código es en VB.NET, pero se puede convertir fácilmente en C # y sin mucho esfuerzo. convertidores de línea también están disponibles.

Esta es la página más útil que he encontrado en el tema, más genérico, adecuados para diferentes tipos de objetos en la pila de deshacer / rehacer.

Comando Patrón

Cuando llegué a su aplicación, me sorprendió lo sencillo y elegante que terminó siendo. Eso hace que sea una victoria para mí.

necesito para restablecer la selección, también, en sus posiciones originales al deshacer / rehacer. Ver "Extensiones de clase", en la parte inferior de mi código sólo los de base y que funciona bien, para una forma con un solo cuadro de texto "textBox1" intentar:

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;
        };
    }
}

Me gustaría escuchar un evento de cambio, y cuando ocurre empujar el diff del estado anterior y el estado actual en una pila. Los diferenciales deben ser mucho más pequeño que el almacenamiento de todo el texto. Además, puede que no desee para impulsar un nuevo estado de deshacer en la pila en cada edición ... Me agrupar a todos a escribir juntos hasta que el usuario cambia la posición del cursor, por ejemplo.

La forma más inteligente es con objetos persistentes inmutables. Nunca haga un cambio en un objeto sólo hacen nuevos objetos que cambian ligeramente de la versión antigua. Esto se puede hacer algo de manera eficiente por clonación solamente las partes del árbol en el camino caliente.

Tengo un ejemplo de una pila de deshacer escrito con 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 como 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 completo aquí https://gist.github.com/bradphelan/5395652

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top