【发布时间】:2016-03-31 02:57:33
【问题描述】:
我正在尝试为文本框的某些事件实现一个简单的撤消/重做机制(基于堆栈)。
在问这个问题之前,我已经看到了很多 undo/redo 实现,例如 these,但它们或多或少是不完整的,并且显示了我已经知道的东西(另一方面,使用稀有接口的专业方式不符合我的理解,所以我想遵循这种基于堆栈的方式),因为这些示例不仅仅是用于编辑控件的撤消/重做示例,而是堆栈的推送/弹出示例,但撤消/redo 不仅仅是编写一个弹出“undo stack”最后一项的方法和另一个弹出“redo stack”最后一项的方法,因为在用户与控件交互的某个时刻,堆栈应该被清除/重置。
我的意思是,在编辑控件的真正撤消/重做机制中,“重做堆栈”应该在用户撤消并且用户在控件中进行文本修改时清除,而“ 撤消堆栈”仍然包含项目,因此此时无需重做,因为撤消时发生了更改。 我没有看到任何完整的撤消重做机制示例,请记住当控件发生更改时必须如何操作撤消/重做堆栈。
我需要帮助来正确实现我的撤消/重做堆栈的逻辑,我开始自己尝试了几天,尝试了几次尝试和错误,但我总是逃避一些细节,因为当我得到一个(撤消或redo)stack 正常工作,另一个停止按预期工作,撤消不应撤消的操作或重做不应重做的操作, 所以我(再次)丢弃了 all the conditional logic that I written 因为我的逻辑总是错误的,我应该使用适当的条件算法再次从零开始,我的意思是适当的条件来推送或弹出堆栈项正确的时刻。
那么,多言或建议,我需要一个可以解决我算法问题的工作代码,我需要在下面的代码中完成AddUndoRedoItem方法的算法逻辑,这是一个关于这个的具体问题.
如果我缺少遵循相同原则(撤消和重做堆栈)的更简单的解决方案,我也会接受该解决方案。
在 C# 或 Vb.Net 中,无所谓。
PD: 如果因为我的英语不好,我没有正确解释某些事情,并且您不完全确定我要求什么样的撤消/重做,只是我要求撤消/重做听起来,只需测试 Ctrl+Z(撤消)和Ctrl+Y(重做)键,同时在文本中执行更改撤消或重做,看看它的作用,这是一个真正的撤消/redo 实现,我试图用堆栈重现。
这是当前代码:
Public Enum UndoRedoCommand As Integer
Undo
Redo
End Enum
Public Enum UndoRedoTextBoxEvent As Integer
TextChanged
End Enum
Public NotInheritable Class UndoRedoTextBox
Private ReadOnly undoStack As Stack(Of KeyValuePair(Of UndoRedoTextBoxEvent, Object))
Private ReadOnly redoStack As Stack(Of KeyValuePair(Of UndoRedoTextBoxEvent, Object))
Private lastCommand As UndoRedoCommand
Private lastText As String
Public ReadOnly Property Control As TextBox
Get
Return Me.controlB
End Get
End Property
Private WithEvents controlB As TextBox
Public ReadOnly Property CanUndo As Boolean
Get
Return (Me.undoStack.Count <> 0)
End Get
End Property
Public ReadOnly Property CanRedo As Boolean
Get
Return (Me.redoStack.Count <> 0)
End Get
End Property
Public ReadOnly Property IsUndoing As Boolean
Get
Return Me.isUndoingB
End Get
End Property
Private isUndoingB As Boolean
Public ReadOnly Property IsRedoing As Boolean
Get
Return Me.isRedoingB
End Get
End Property
Private isRedoingB As Boolean
Public Sub New(ByVal tb As TextBox)
Me.undoStack = New Stack(Of KeyValuePair(Of UndoRedoTextBoxEvent, Object))
Me.redoStack = New Stack(Of KeyValuePair(Of UndoRedoTextBoxEvent, Object))
Me.controlB = tb
Me.lastText = tb.Text
End Sub
Public Sub Undo()
If (Me.CanUndo) Then
Me.InternalUndoRedo(UndoRedoCommand.Undo)
End If
End Sub
Public Sub Redo()
If (Me.CanRedo) Then
Me.InternalUndoRedo(UndoRedoCommand.Redo)
End If
End Sub
' Undoes or redoues.
Private Sub InternalUndoRedo(ByVal command As UndoRedoCommand)
Dim undoRedoItem As KeyValuePair(Of UndoRedoTextBoxEvent, Object) = Nothing
Dim undoRedoEvent As UndoRedoTextBoxEvent
Dim undoRedoValue As Object = Nothing
Select Case command
Case UndoRedoCommand.Undo
Me.isUndoingB = True
undoRedoItem = Me.undoStack.Pop
Me.AddUndoRedoItem(UndoRedoCommand.Redo, UndoRedoTextBoxEvent.TextChanged, Me.lastText, undoRedoItem.Value)
Case UndoRedoCommand.Redo
Me.isRedoingB = True
undoRedoItem = Me.redoStack.Pop
Me.AddUndoRedoItem(UndoRedoCommand.Undo, UndoRedoTextBoxEvent.TextChanged, undoRedoItem.Value, Me.lastText)
End Select
undoRedoEvent = undoRedoItem.Key
undoRedoValue = undoRedoItem.Value
Select Case undoRedoEvent
Case UndoRedoTextBoxEvent.TextChanged
Me.controlB.Text = CStr(undoRedoValue)
End Select
Me.isUndoingB = False
Me.isRedoingB = False
End Sub
Private Sub AddUndoRedoItem(ByVal command As UndoRedoCommand, ByVal [event] As UndoRedoTextBoxEvent,
ByVal data As Object, ByVal lastData As Object)
Console.WriteLine()
Console.WriteLine("command :" & command.ToString)
Console.WriteLine("last command:" & lastCommand.ToString)
Console.WriteLine("can undo :" & Me.CanUndo)
Console.WriteLine("can redo :" & Me.CanRedo)
Console.WriteLine("is undoing :" & Me.isUndoingB)
Console.WriteLine("is redoing :" & Me.isRedoingB)
Console.WriteLine("data :" & data.ToString)
Console.WriteLine("last data :" & lastData.ToString)
Dim undoRedoData As Object = Nothing
Me.lastCommand = command
Select Case command
Case UndoRedoCommand.Undo
If (Me.isUndoingB) Then
Exit Select
End If
undoRedoData = lastData
Me.undoStack.Push(New KeyValuePair(Of UndoRedoTextBoxEvent, Object)([event], undoRedoData))
Case UndoRedoCommand.Redo
If (Me.isRedoingB) Then
Exit Select
End If
undoRedoData = lastData
Me.redoStack.Push(New KeyValuePair(Of UndoRedoTextBoxEvent, Object)([event], undoRedoData))
End Select
End Sub
Private Sub TextBox_TextChanged(ByVal sender As Object, ByVal e As EventArgs) _
Handles controlB.TextChanged
Dim currentText As String = Me.controlB.Text
If Not String.Equals(Me.lastText, currentText, StringComparison.Ordinal) Then
Select Case Me.lastCommand
Case UndoRedoCommand.Undo
Me.AddUndoRedoItem(UndoRedoCommand.Undo, UndoRedoTextBoxEvent.TextChanged, currentText, Me.lastText)
Case UndoRedoCommand.Redo
Me.AddUndoRedoItem(UndoRedoCommand.Redo, UndoRedoTextBoxEvent.TextChanged, Me.lastText, currentText)
End Select
Me.lastText = currentText
End If
End Sub
End Class
【问题讨论】:
-
如何处理多个文本框?你会为每个文本控件使用其中一个吗?
-
@Plutonix 是的,每个文本框都有一个实例,该类不打算处理多个控件,例如可以处理一种不同控件的某种“撤消/重做管理器”,不是。
-
@Plutonix 您几小时前的最后一个 cmets 很有用,请您至少重写最重要的部分吗? (我希望我不会忘记您在那些 cmets 中所说的任何内容)。我现在无法遵循您以前对我所做的建议,但是一旦我可以进入VS,我就会这样做,而且您所说的似乎很合逻辑,仅在撤消时才添加重做项目,并且不再打算对重做堆栈进行任何操作,但是我想在我明天尝试之前,我根本无法弄清楚。谢谢,圣诞快乐。
-
看起来您只是想让每个 TB 的撤消池“更深”?因为每个控件一个堆栈不会像普通文档撤消一样。您不会知道哪个堆栈具有“下一个”项目,因此 most pop/Undo 将导致文档状态从来没有或没有意义:Genie in a Bottle; 1958; (by) 平克·弗洛伊德
-
@Plutonix 我不确定是否完全理解您的最后一条评论,为什么它不应该工作?,如果一个类实例正在处理一个带有其撤消/重做堆栈的控件,那么它将按预期工作,似乎您正在谈论一个具有各种控件堆栈的类,但正如我所说,每个控件我将有一个类,而不是用于各种控件的任何类型的撤消/重做管理器。
标签: c# .net vb.net algorithm undo-redo