如何为文本框实现良好且高效的撤消/重做功能

发布于 2024-07-14 06:28:37 字数 697 浏览 8 评论 0原文

我有一个文本框,我想为其实现撤消/重做功能。 我已经读到它可能已经有一些轻微的撤消功能,但它有问题吗? 无论如何,我想实现撤消和重做功能,也只是为了了解您将如何继续执行此操作。

我读过有关 Memento Pattern 的内容,并在 CodeProject 上的通用撤消/重做示例。 这种模式是有道理的。 我似乎不知道如何实现它。 以及如何有效地处理 TextBox 的内容。

当然,我可以在 TextChanges 时存储 textbox.Text,但这会很快占用大量内存,特别是如果 TextBox包含大量文字。

所以无论如何,我正在寻找一些关于如何以良好、清晰和有效的方式来实现此功能的建议。 一般而言,特别是对于文本框 c",)

I have a TextBox which I would like to implement undo/redo functionality for. I have read that it might have some slight undo functionality already, but that it is buggy? Anyways, I would like to implement both undo and redo functionality also just to learn how you would go ahead and do that.

I have read about the Memento Pattern and looked some on a Generic Undo/Redo example on CodeProject. And the pattern kiiind of makes sense. I just can't seem to wrap my head around how to implement it. And how to do it effeciently for the contents of a TextBox.

Of course I could just store textbox.Text when TextChanges, but that would hug up quite a lot of memory pretty fast, especially if the TextBox contained a lot of text.

So anyways, I'm looking for some advice on how to implement a good, clear and efficient way of implementing this functionality. Both in general and especially for a TextBox c",)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

清风无影 2024-07-21 06:28:37

.NET System.ComponentModel 命名空间附带一个 IEditableObject 接口,您还可以使用 INotifyPropertyChangingINotifyPropertyChanged。 MVC 模式还将使您的界面通过事件响应模型中的更改,从而更新或恢复文本框的值。

实际上是纪念品模式

你看过这些吗? 此处介绍了操作方法。

一个简单且更快的版本是存储文本框OnTextChanged的状态。 每个撤消都会返回数组中的最后一个事件。 C# 堆栈类型在这里会很方便。 您也可以在离开界面后或Apply之后清除该状态。

The .NET System.ComponentModel namespace comes with an IEditableObject interface, you could also use INotifyPropertyChanging and INotifyPropertyChanged. MVC Pattern would also make it that your interface responds to changes in the model through events thus updating or restoring the value of your textbox.

Effectively the Memento Pattern.

Have you had a look into these? Here is a how to.

A simple and quicker version would be to store the state of the textbox OnTextChanged. Each undo would return the last event in an Array. The C# Stack Type would be handy here. You could clear the state once you are off the interface also or after Apply.

七月上 2024-07-21 06:28:37

这是一种用最少的代码实现它的方法:
(这是带有单个文本框的 win 表单的后台代码)

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 可以为整个 UI 提供服务,按顺序撤消所有 UI 操作。

Here's a way to achieve it with minimal code:
(This is the code behind of a win form with a single textbox on it)

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

By implementing an extension method for other input types the undoStack can service the whole of your UI, undoing all UI actions in order.

像你 2024-07-21 06:28:37

撤消/重做时,我也需要将选择重置到原始位置。 观看“类扩展”,位于我的基本且运行良好的代码的底部,对于只有一个文本框“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;
        };
    }
}

I need to reset the selection, too, into its original positions when undoing / redoing. Watch "class Extensions", at the bottom of my just basic and well working code, for a form with just one textbox "textBox1" to try:

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;
        };
    }
}
围归者 2024-07-21 06:28:37

可以在这里找到一个很好的解决方案:

向您的应用程序添加撤消/重做或后退/前进功能

Undo/Redo Capable TextBox (winforms)

代码位于 VB.NET 中,但您可以轻松地将其转换为 C#,无需太多努力。 还提供在线转换器。

A good solution can be found here:

Add Undo/Redo or Back/Forward Functionality to your Application

Undo/Redo Capable TextBox (winforms)

The code is in VB.NET, but you can easily convert it to C# without much efforts. Online converters are also available.

似最初 2024-07-21 06:28:37

这是我在该主题上找到的最有帮助的页面,更通用,适合撤消/重做堆栈上的不同对象类型。

命令模式

当我得到在实现它的过程中,我很惊讶它最终变得如此简单和优雅。
这对我来说是一场胜利。

This is the most helpful page I found on the topic, more generic, suitable for different object types on the undo/redo stack.

Command Pattern

When I got to implementing it, I was surprised how simple and elegant it ended up being.
That makes it a win for me.

夏末的微笑 2024-07-21 06:28:37

我会监听更改事件,当它发生时,将前一个状态和当前状态的 diff 推送到堆栈上。 差异应该比存储整个文本小得多。 另外,您可能不想在每次编辑时将新的撤消状态推送到堆栈上...例如,我会将所有输入集中在一起,直到用户更改光标位置。

I would listen for a change event, and when it occurs push the diff of the previous state and present state onto a stack. The diffs should be much smaller than storing the entire text. Also, you might not want to push a new undo state onto the stack at every edit... I'd lump all typing together until the user changes the cursor position, for example.

一向肩并 2024-07-21 06:28:37

最聪明的方法是使用不可变的持久对象。 切勿对对象进行更改,仅创建与旧版本略有不同的新对象。 通过仅克隆热路径上树的部分内容,可以稍微有效地完成此操作。

我有一个用最少代码编写的撤消堆栈的示例,

 [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/布拉德费伦/5395652

The smartest way is with immutable persistent objects. Never make a change to an object only make new objects that change slightly from the old version. This can be done somewhat efficiently by only cloning parts of the tree on the hot path.

I have an example of an undo stack written with minimal code

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

}

where A and B as classes with private setters on all properties 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;
    }
}

you can find the full source here https://gist.github.com/bradphelan/5395652

皇甫轩 2024-07-21 06:28:37

这是遵循 KISS 原则的撤消/重做实现。
您需要一个带有指向当前操作的光标的操作列表。 每次用户执行某些操作时,您都会将操作添加到列表中,增加光标并删除列表中光标之后的操作,如果用户按“撤消”,则减少光标,如果按“重做”,则增加光标。
这是伪代码。

public class UndoFeature
{
    public interface IRecoverableAction
    {
        void Execute();
        void Rollback();
    }

    private List<IRecoverableAction> actions;
    private int cursor { get; set; }

    void DoAction(IRecoverableAction action)
    {
        actions.Add(action);
        cursor++;
        action.Execute();
        //Removes items after cursor
        actions.RemoveRange(cursor, actions.Count - cursor);
    }

    void Undo()
    {
        actions[cursor].Rollback();
        cursor--;
    }

    void Redo()
    {
        actions[cursor].Execute();
        cursor++;
    }
}

注意:代码中未包含范围检查和其他考虑因素,以使其简单易懂;

This is an Undo/Redo implementation following the KISS principle.
You need a List of Actions with a cursor that points to the current action. You add actions to the list every time user do something, increase the cursor and remove actions that are after the cursor in the list, if the user press Undo, decrease the cursor, and if pressed Redo increase the cursor.
Here's the pseudo-code.

public class UndoFeature
{
    public interface IRecoverableAction
    {
        void Execute();
        void Rollback();
    }

    private List<IRecoverableAction> actions;
    private int cursor { get; set; }

    void DoAction(IRecoverableAction action)
    {
        actions.Add(action);
        cursor++;
        action.Execute();
        //Removes items after cursor
        actions.RemoveRange(cursor, actions.Count - cursor);
    }

    void Undo()
    {
        actions[cursor].Rollback();
        cursor--;
    }

    void Redo()
    {
        actions[cursor].Execute();
        cursor++;
    }
}

Note: Range checking and other consideration not included in the code to keep it simple and easy to understand for everyone;

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文