web-dev-qa-db-fra.com

Comment implémenter des fonctionnalités d'annulation et de rétablissement correctes et efficaces pour un TextBox

J'ai un TextBox pour lequel j'aimerais implémenter la fonctionnalité d'annulation/de rétablissement. Je ai l qu'il pourrait déjà avoir une légère fonctionnalité d'annulation, mais qu'il est bogué? Quoi qu'il en soit, je voudrais également implémenter les fonctionnalités d'annulation et de restauration pour apprendre comment vous pouvez procéder et le faire.

J'ai lu sur le Memento Pattern et en ai regardé un exemple Generic Undo/Redo sur CodeProject. Et le motif kiiind est logique. Je n'arrive pas à comprendre comment l'implémenter. Et comment le faire efficacement pour le contenu d'un TextBox.

Bien sûr, je pouvais simplement stocker textbox.Text lorsque TextChanges, mais cela accaparerait pas mal de mémoire assez rapidement, surtout si le TextBox contenait beaucoup de texte.

Donc, de toute façon, je suis à la recherche de conseils sur la façon de mettre en œuvre une bonne façon claire et efficace de mettre en œuvre cette fonctionnalité. A la fois en général et surtout pour un TextBox c ",)

27
Svish

Le net System.ComponentModel l'espace de noms est livré avec une interface IEditableObject, vous pouvez également utiliser INotifyPropertyChanging et INotifyPropertyChanged. MVC Pattern ferait également en sorte que votre interface réponde aux changements du modèle par le biais d'événements, mettant ainsi à jour ou rétablissant la valeur de votre zone de texte.

Effectivement, le modèle de souvenir .

Les avez-vous examinés? Ici est un guide pratique.

Une version simple et plus rapide consisterait à stocker l'état de la zone de texte OnTextChanged. Chaque annulation retournerait le dernier événement d'un tableau. Le type de pile C # serait pratique ici. Vous pouvez également effacer l'état une fois que vous êtes hors de l'interface ou après Apply.

17
REA_ANDREW

Voici un moyen de le réaliser avec un code minimal: (Il s'agit du code derrière un formulaire gagnant avec une seule zone de texte dessus)

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

En implémentant une méthode d'extension pour d'autres types d'entrée, undoStack peut desservir l'ensemble de votre interface utilisateur, annulant toutes les actions de l'interface utilisateur dans l'ordre.

7
jdoig

Une bonne solution peut être trouvée ici:

Ajoutez les fonctionnalités Annuler/Rétablir ou Précédent/Suivant à votre application

Annuler/Rétablir TextBox Capable (winforms)

Le code est en VB.NET, mais vous pouvez facilement le convertir en C # sans trop d'efforts. Des convertisseurs en ligne sont également disponibles.

3
Pradeep Kumar

C'est la page la plus utile que j'ai trouvée sur le sujet, plus générique, adaptée à différents types d'objets sur la pile annuler/rétablir.

Modèle de commande

Quand j'ai commencé à le mettre en œuvre, j'ai été surpris de voir à quel point il était simple et élégant. Cela en fait une victoire pour moi.

2
radsdau

J'ai également besoin de réinitialiser la sélection dans ses positions d'origine lors de l'annulation/du rétablissement. Regardez "Extensions de classe", au bas de mon code juste basique et qui fonctionne bien, pour un formulaire avec une seule zone de texte "textBox1" à essayer:

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

La manière la plus intelligente consiste à utiliser des objets persistants immuables. Ne modifiez jamais un objet, ne faites que de nouveaux objets qui changent légèrement par rapport à l'ancienne version. Cela peut être fait de manière assez efficace en ne clonant que des parties de l'arbre sur le chemin chaud.

J'ai un exemple d'une pile d'annulation écrite avec un code minimal

 [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);

}

où A et B en tant que classes avec private setters sur toutes les propriétés, c'est-à-dire 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;
    }
}

vous pouvez trouver la source complète ici https://Gist.github.com/bradphelan/5395652

1
bradgonesurfing

J'écouterais un événement de changement, et quand il se produit Poussez le diff de l'état précédent et de l'état actuel sur une pile. Les différences doivent être beaucoup plus petites que le stockage de tout le texte. De plus, vous ne voudrez peut-être pas pousser un nouvel état d'annulation sur la pile à chaque édition ... Je regrouperais tous les caractères jusqu'à ce que l'utilisateur change la position du curseur, par exemple.

1
mpen