C# 如何为 TextBox 实现良好而高效的撤消/重做功能
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/597792/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to implement good and efficient undo/redo functionality for a TextBox
提问by Svish
I have a TextBox which I would like to implement undo/redo functionality for. I have readthat 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 Patternand looked some on a Generic Undo/Redoexample 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
.
我已经阅读了有关纪念品模式的内容,并在 CodeProject上的通用撤消/重做示例中查看了一些内容。和模式kiiiind 是有道理的。我似乎无法理解如何实现它。以及如何有效地为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.
当然,我可以只存储textbox.Text
when TextChanges
,但这会非常快地占用大量内存,特别是如果其中TextBox
包含大量文本。
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",)
所以无论如何,我正在寻找一些关于如何实现一个好的、清晰和有效的方式来实现这个功能的建议。一般情况下,尤其是对于 TextBox c",)
采纳答案by REA_ANDREW
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.
.NETSystem.ComponentModel
命名空间带有一个IEditableObject
接口,您也可以使用INotifyPropertyChanging
和INotifyPropertyChanged
。MVC 模式还可以使您的界面通过事件响应模型中的更改,从而更新或恢复文本框的值。
Effectively the Memento Pattern.
有效的纪念品模式。
Have you had a look into these? Hereis 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
.
一个简单快捷的版本是存储文本框的状态OnTextChanged
。每次撤消将返回数组中的最后一个事件。C# 堆栈类型在这里会很方便。您也可以在离开界面后或之后清除状态Apply
。
回答by jdoig
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)
这是一种用最少的代码实现它的方法:(这是带有单个文本框的 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; };
}
}
By implementing an extension method for other input types the undoStack can service the whole of your UI, undoing all UI actions in order.
通过为其他输入类型实现扩展方法,undoStack 可以为整个 UI 提供服务,按顺序撤消所有 UI 操作。
回答by mpen
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.
我会监听一个更改事件,当它发生时diff
,将前一个状态和当前状态推入堆栈。差异应该比存储整个文本小得多。此外,您可能不想在每次编辑时将新的撤消状态推送到堆栈中……例如,我会将所有输入混为一谈,直到用户更改光标位置。
回答by Pradeep Kumar
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.
代码在 VB.NET 中,但您可以轻松地将其转换为 C#。也可以使用在线转换器。
回答by radsdau
This is the most helpful page I found on the topic, more generic, suitable for different object types on the undo/redo stack.
这是我在该主题上找到的最有用的页面,更通用,适用于撤消/重做堆栈上的不同对象类型。
When I got to implementing it, I was surprised how simple and elegant it ended up being. That makes it a win for me.
当我开始实施它时,我惊讶于它最终变得如此简单和优雅。这对我来说是一场胜利。
回答by bradgonesurfing
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
其中 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;
}
}
you can find the full source here https://gist.github.com/bradphelan/5395652
你可以在这里找到完整的源代码https://gist.github.com/bradphelan/5395652
回答by Josh
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:
撤消/重做时,我也需要将选择重置为其原始位置。观看“类扩展”,在我的基本且运行良好的代码底部,对于只有一个文本框“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;
};
}
}