代码之家  ›  专栏  ›  技术社区  ›  ElektroStudios

基于堆栈的编辑控件的撤消/重做实现

  •  3
  • ElektroStudios  · 技术社区  · 9 年前

    我正在尝试为文本框的某些事件实现一个简单的撤消/重做机制(基于堆栈)。

    在提出这个问题之前,我已经看到了很多撤销/重做实现,如 these ,但它们或多或少都是不完整的,显示了我已经知道的东西(另一方面,使用稀有接口的专业方式超出了我的理解,所以我想遵循这种基于堆栈的方式),因为这些示例不仅仅是编辑控件的撤消/重做示例,而是堆栈的推/弹出示例,但是撤销/重做比编写一个方法来弹出“ 撤销栈 “和另一种弹出” 重做堆栈 “,因为在用户与控件交互的某个时刻,堆栈应该被清除/重置。

    我的意思是,在编辑控件的真正撤消/重做机制中,“ 重做堆栈 “当用户撤消并且用户在控件中进行文本修改时,应清除” 撤销栈 “仍然包含项,因此在这一点上没有什么可重做的,因为撤消时发生了更改。 考虑到控件中发生更改时必须如何操作撤消/重做堆栈,我还没有看到任何以这种方式执行撤消/重作机制的完整示例。

    我需要帮助来正确地实现我的撤销/重做堆栈的逻辑,我开始自己尝试了几天,尝试了几次,但总是忽略了一些细节,因为当我让一个(撤销或重做)堆栈正常工作时,另一个堆栈会停止按预期工作,撤销它不应该撤销的或重做它不应该重做的, 所以我(再次)放弃了 all the conditional logic that I written 因为我的逻辑总是错误的,所以我应该用一个合适的条件算法从零开始,我的意思是在合适的时候用合适的条件推送或弹出堆栈项。

    然后,不仅仅是文字或建议,我需要一个可以用我的算法解决问题的工作代码,我需要完成 AddUndoRedoItem 在下面的代码中,这是一个具体的问题。

    如果我缺少遵循相同原则的更简单的解决方案(撤销和重做堆栈),我也会接受这个解决方案。

    不管是在C#还是Vb.Net中。

    零件编号: 如果因为我的英语不好,我没有正确地解释一些事情,而你也不完全确定我要的是什么样的撤销/重做,只是我要求的是一个听起来很像的撤销/重新做,那就测试一下 Ctrl+Z组合键 (撤销)和 Ctrl+Y组合键 当在记事本中执行撤消或重做的文本更改时,请查看它的行为,这是一个真正的撤消/重做实现,这是我试图用堆栈复制的。


    这是当前代码:

    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
    
    1 回复  |  直到 8 年前
        1
  •  0
  •   ElektroStudios    9 年前

    感谢@ 普鲁托尼克斯 我解决了这个问题,我真的不相信一个简单的评论可以帮助我解决逻辑问题,但是的,我让事情变得比实际情况更复杂。

    我仍然需要考虑如何管理一次性对象,但这个想法的基础或多或少已经完成,下面的代码正在按预期工作(至少我预期的那样)。

    这些是控件的基本撤消/重做类的部分:

    Public Enum UndoRedoCommand As Integer
        Undo
        Redo
    End Enum
    
    Public Class UndoRedoItem
        Public Property [Event] As Integer
        Public Property LastValue As Object
        Public Property CurrentValue As Object
    End Class
    
    Public MustInherit Class UndoRedo(Of T As Control)
    
    #Region " Private Fields "
    
        Private ReadOnly undoStack As Stack(Of UndoRedoItem)
        Private ReadOnly redoStack As Stack(Of UndoRedoItem)
    
    #End Region
    
    #Region " Properties "
    
        Public ReadOnly Property Control As T
            Get
                Return Me.controlB
            End Get
        End Property
        Protected WithEvents controlB As T
    
        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
    
    #End Region
    
    #Region " Constructors "
    
        Private Sub New()
        End Sub
    
        Public Sub New(ByVal ctrl As T)
    
            Me.undoStack = New Stack(Of UndoRedoItem)
            Me.redoStack = New Stack(Of UndoRedoItem)
    
            Me.controlB = ctrl
    
        End Sub
    
    #End Region
    
    #Region " Public Methods "
    
        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
    
    #End Region
    
    #Region " Private Methods "
    
        Private Sub InternalUndoRedo(ByVal command As UndoRedoCommand)
    
            Dim undoRedoItem As UndoRedoItem = Nothing
    
            Select Case command
    
                Case UndoRedoCommand.Undo
                    Me.isUndoingB = True
                    undoRedoItem = Me.undoStack.Pop
                    Me.AddUndoRedoItem(UndoRedoCommand.Redo, undoRedoItem.Event, undoRedoItem.LastValue, undoRedoItem.CurrentValue)
    
                Case UndoRedoCommand.Redo
                    Me.isRedoingB = True
                    undoRedoItem = Me.redoStack.Pop
    
            End Select
    
            Me.DoUndo(undoRedoItem.Event, undoRedoItem.CurrentValue)
    
            Me.isUndoingB = False
            Me.isRedoingB = False
    
        End Sub
    
        Protected MustOverride Sub DoUndo(ByVal [event] As Integer, ByVal data As Object)
    
        Protected Sub AddUndoRedoItem(ByVal command As UndoRedoCommand,
                                      ByVal [event] As Integer,
                                      ByVal currentData As Object,
                                      ByVal lastData As Object)
    
            Dim undoRedoItem As New UndoRedoItem
            undoRedoItem.Event = [event]
    
            Select Case command
    
                Case UndoRedoCommand.Undo
    
                    If (Me.isUndoingB) Then
                        Exit Select
                    End If
    
                    If (Me.CanUndo) AndAlso (Me.CanRedo) AndAlso Not (Me.IsRedoing) Then
                        Me.redoStack.Clear()
                    End If
    
                    undoRedoItem.CurrentValue = lastData
                    undoRedoItem.LastValue = currentData
                    Me.undoStack.Push(undoRedoItem)
    
                Case UndoRedoCommand.Redo
    
                    If (Me.isRedoingB) Then
                        Exit Select
                    End If
    
                    undoRedoItem.CurrentValue = currentData
                    undoRedoItem.LastValue = lastData
                    Me.redoStack.Push(undoRedoItem)
    
            End Select
    
        End Sub
    
    #End Region
    
    End Class
    

    这是对文本框进行撤消/重做的实现:

    Public Enum UndoRedoTextBoxEvent As Integer
    
        TextChanged
        FontChanged
        BackColorChanged
        ForeColorChanged
    
    End Enum
    
    Public NotInheritable Class UndoRedoTextBox : Inherits UndoRedo(Of TextBox)
    
        Private lastText As String
        Private lastFont As Font
        Private lastBackColor As Color
        Private lastForeColor As Color
    
        Public Sub New(ByVal tb As TextBox)
            MyBase.New(tb)
        End Sub
    
        Protected Overrides Sub DoUndo([event] As Integer, data As Object)
    
            Select Case DirectCast([event], UndoRedoTextBoxEvent)
    
                Case UndoRedoTextBoxEvent.TextChanged
                    MyBase.controlB.Text = CStr(data)
    
                Case UndoRedoTextBoxEvent.FontChanged
                    MyBase.controlB.Font = DirectCast(data, Font)
    
                Case UndoRedoTextBoxEvent.BackColorChanged
                    MyBase.controlB.BackColor = DirectCast(data, Color)
    
                Case UndoRedoTextBoxEvent.ForeColorChanged
                    MyBase.controlB.ForeColor = DirectCast(data, Color)
    
            End Select
    
        End Sub
    
        Private Sub TextBox_TextChanged(ByVal sender As Object, ByVal e As EventArgs) _
        Handles controlB.TextChanged
    
            Dim currentText As String = MyBase.controlB.Text
    
            If Not String.Equals(Me.lastText, currentText, StringComparison.Ordinal) Then
    
                MyBase.AddUndoRedoItem(UndoRedoCommand.Undo, UndoRedoTextBoxEvent.TextChanged, currentText, Me.lastText)
                Me.lastText = currentText
    
            End If
    
        End Sub
    
        Private Sub TextBox_FontChanged(sender As Object, e As EventArgs) _
        Handles controlB.FontChanged
    
            Dim currentFont As Font = MyBase.controlB.Font
    
            If (Me.lastFont IsNot currentFont) Then
                MyBase.AddUndoRedoItem(UndoRedoCommand.Undo, UndoRedoTextBoxEvent.FontChanged, currentFont, Me.lastFont)
                Me.lastFont = currentFont
            End If
    
        End Sub
    
        Private Sub TextBox_BackColorChanged(sender As Object, e As EventArgs) _
        Handles controlB.BackColorChanged
    
            Dim currentBackColor As Color = MyBase.controlB.BackColor
    
            If (Me.lastBackColor <> currentBackColor) Then
                MyBase.AddUndoRedoItem(UndoRedoCommand.Undo, UndoRedoTextBoxEvent.BackColorChanged, currentBackColor, Me.lastBackColor)
                Me.lastBackColor = currentBackColor
            End If
    
        End Sub
    
        Private Sub TextBox_ForeColorChanged(sender As Object, e As EventArgs) _
        Handles controlB.ForeColorChanged
    
            Dim currentForeColor As Color = MyBase.controlB.ForeColor
    
            If (Me.lastForeColor <> currentForeColor) Then
                MyBase.AddUndoRedoItem(UndoRedoCommand.Undo, UndoRedoTextBoxEvent.ForeColorChanged, currentForeColor, Me.lastForeColor)
                Me.lastForeColor = currentForeColor
            End If
    
        End Sub
    
    End Class