Click here to Skip to main content
15,880,967 members
Articles / Desktop Programming / Windows Forms

Undo/Redo Framework

Rate me:
Please Sign up or sign in to vote.
4.89/5 (21 votes)
16 Oct 2013CPOL8 min read 92.8K   4.2K   78  
Undo/Redo framework for editing controls in a Windows application.
'******************************************************************************************************************
' Undo/Redo framework (c) Copyright 2009 Etienne Nijboer
'******************************************************************************************************************

Public MustInherit Class BaseUndoRedoMonitor

    Public Sub New(ByVal AUndoRedoManager As UndoRedoManager)
        _UndoRedoManager = AUndoRedoManager
    End Sub

    Private _UndoRedoManager As UndoRedoManager
    Public Property UndoRedoManager() As UndoRedoManager
        Get
            Return _UndoRedoManager
        End Get
        Set(ByVal value As UndoRedoManager)
            _UndoRedoManager = value
        End Set
    End Property

    Public ReadOnly Property isUndoing() As Boolean
        Get
            Return UndoRedoManager.isUndoing
        End Get
    End Property
    Public ReadOnly Property isRedoing() As Boolean
        Get
            Return UndoRedoManager.isRedoing
        End Get
    End Property

    Public ReadOnly Property isPerformingUndoRedo() As Boolean
        Get
            Return UndoRedoManager.isPerformingUndoRedo
        End Get
    End Property

    Public Sub AddCommand(ByVal UndoRedoCommandType As UndoRedoCommandType, ByVal UndoRedoCommand As BaseUndoRedoCommand)
        UndoRedoManager.AddCommand(UndoRedoCommandType, UndoRedoCommand)
    End Sub

    Public MustOverride Function Monitor(ByVal AControl As Control) As Boolean

End Class

'****************************************************************************************************************
' SimpleControl 
' Controls: TextBox, ComboBox, DateTimePicker, NumericUpDown, MaskedTextBox
'****************************************************************************************************************
Public Class SimpleControlMonitor : Inherits BaseUndoRedoMonitor

    Private Data As String

    Public Sub New(ByVal AUndoRedoManager As UndoRedoManager)
        MyBase.New(AUndoRedoManager)
    End Sub

    Public Overrides Function Monitor(ByVal AControl As System.Windows.Forms.Control) As Boolean
        If TypeOf AControl Is TextBox Or _
           TypeOf AControl Is ComboBox Or _
           TypeOf AControl Is DateTimePicker Or _
           TypeOf AControl Is NumericUpDown Or _
           TypeOf AControl Is MaskedTextBox Then
            AddHandler AControl.Enter, AddressOf Control_Enter
            AddHandler AControl.Leave, AddressOf Control_Leave
            Return True
        End If
        Return False
    End Function

    Private Sub Control_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Data = CType(sender, Control).Text
    End Sub

    Private Sub Control_Leave(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Dim CurrentData As String = CType(sender, Control).Text
        If Not String.Equals(CurrentData, Data) Then
            AddCommand(UndoRedoCommandType.ctUndo, New SimpleControlUndoRedoCommand(Me, sender, Data))
        End If
    End Sub
End Class


'****************************************************************************************************************
' ListBox
'****************************************************************************************************************
Public Class ListBoxMonitor : Inherits BaseUndoRedoMonitor

    Private Data As Object

    Public Sub New(ByVal AUndoRedoManager As UndoRedoManager)
        MyBase.New(AUndoRedoManager)
    End Sub

    Public Overrides Function Monitor(ByVal AControl As System.Windows.Forms.Control) As Boolean
        If TypeOf AControl Is ListBox Then
            AddHandler AControl.Enter, AddressOf Control_Enter
            AddHandler CType(AControl, ListBox).SelectedIndexChanged, AddressOf Control_Changed
            Return True
        End If
        Return False
    End Function

    Public Function GetSelected(ByVal AListBox As Object) As String
        Dim Indices As List(Of String) = New List(Of String)
        For Each itemIndex As Integer In CType(AListBox, ListBox).SelectedIndices
            Indices.Add(CStr(itemIndex + 1))
        Next
        Return String.Join(",", Indices.ToArray)
    End Function

    Public Sub RestoreSelected(ByVal AListBox As Object, ByVal ASelection As String)
        If Not String.IsNullOrEmpty(ASelection) Then
            Dim Indices As List(Of Integer) = New List(Of Integer)(Array.ConvertAll(ASelection.Split(","), New Converter(Of String, Integer)(AddressOf Integer.Parse)))
            Dim Control As ListBox = CType(AListBox, ListBox)
            Select Case Control.SelectionMode
                Case SelectionMode.None
                Case SelectionMode.One
                    Control.SetSelected(Indices(0) - 1, True)
                Case SelectionMode.MultiSimple, SelectionMode.MultiExtended
                    For index As Integer = 0 To Control.Items.Count - 1
                        Control.SetSelected(index, Indices.IndexOf(index + 1) >= 0)
                    Next
            End Select
        Else
            CType(AListBox, ListBox).ClearSelected()
        End If
    End Sub

    Private Sub Control_Changed(ByVal sender As System.Object, ByVal e As System.EventArgs)
        ' Events that are also fired when the undo/redo value is changed by code, like change events,
        ' it is important to make sure that no undo/redo command is added when performing a undo/redo action. 
        If Not isPerformingUndoRedo Then
            Dim CurrentData As String = GetSelected(sender)
            If Not String.Equals(Data, CurrentData) Then
                AddCommand(UndoRedoCommandType.ctUndo, New ListBoxUndoRedoCommand(Me, sender, Data))
                Data = CurrentData
            End If
        End If
    End Sub

    Private Sub Control_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Data = GetSelected(sender)
    End Sub

End Class


'****************************************************************************************************************
' CheckBox
'****************************************************************************************************************
Public Class CheckBoxMonitor : Inherits BaseUndoRedoMonitor
    Private Data As CheckState

    Public Sub New(ByVal AUndoRedoManager As UndoRedoManager)
        MyBase.New(AUndoRedoManager)
    End Sub

    Public Overrides Function Monitor(ByVal AControl As System.Windows.Forms.Control) As Boolean
        If TypeOf AControl Is CheckBox Then
            AddHandler AControl.Enter, AddressOf Control_Enter
            AddHandler AControl.Leave, AddressOf Control_Leave
            Return True
        End If
        Return False
    End Function

    Private Sub Control_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Data = CType(sender, CheckBox).CheckState
    End Sub

    Private Sub Control_Leave(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Dim CurrentData As CheckState = CType(sender, CheckBox).CheckState
        If Data <> CurrentData Then
            AddCommand(UndoRedoCommandType.ctUndo, New CheckBoxUndoRedoCommand(Me, sender, Data))
        End If
    End Sub
End Class

'****************************************************************************************************************
' RadioButton
'****************************************************************************************************************
Public Class RadioButtonMonitor : Inherits BaseUndoRedoMonitor
    Private Data As RadioButton

    Public Sub New(ByVal AUndoRedoManager As UndoRedoManager)
        MyBase.New(AUndoRedoManager)
    End Sub

    Public Overrides Function Monitor(ByVal AControl As System.Windows.Forms.Control) As Boolean
        If TypeOf AControl Is RadioButton Then
            AddHandler CType(AControl, RadioButton).CheckedChanged, AddressOf Control_CheckedChanged
            Return True
        End If
        Return False
    End Function

    Private Sub Control_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
        ' Events that are also fired when the undo/redo value is changed by code, like change events,
        ' it is important to make sure that no undo/redo command is added when performing a undo/redo action.  
        If Not isPerformingUndoRedo Then
            If CType(sender, RadioButton).Checked Then
                AddCommand(UndoRedoCommandType.ctUndo, New RadioButtonUndoRedoCommand(Me, sender, Data))
            Else
                Data = sender
            End If
        End If
    End Sub
End Class

'****************************************************************************************************************
' MonthCalendar
'****************************************************************************************************************
Public Class MonthCalendarMonitor : Inherits BaseUndoRedoMonitor
    Private Data As SelectionRange

    Public Sub New(ByVal AUndoRedoManager As UndoRedoManager)
        MyBase.New(AUndoRedoManager)
    End Sub

    Public Overrides Function Monitor(ByVal AControl As System.Windows.Forms.Control) As Boolean
        If TypeOf AControl Is MonthCalendar Then
            AddHandler AControl.Enter, AddressOf Control_Enter
            AddHandler CType(AControl, MonthCalendar).DateSelected, AddressOf Control_DateSelected
            Return True
        End If
        Return False
    End Function

    Private Sub Control_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Data = CType(sender, MonthCalendar).SelectionRange
    End Sub

    Private Sub Control_DateSelected(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DateRangeEventArgs)
        ' Events that are also fired when the undo/redo value is changed by code, like selected events,
        ' it is important to make sure that no undo/redo command is added when performing a undo/redo action. 
        If Not isPerformingUndoRedo Then
            Dim CurrentData As SelectionRange = CType(sender, MonthCalendar).SelectionRange
            If Not SelectionRange.Equals(Data, CurrentData) Then
                AddCommand(UndoRedoCommandType.ctUndo, New MonthCalendarUndoRedoCommand(Me, sender, Data))
                Data = CurrentData
            End If
        End If
    End Sub

End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
Currently working as a Software Developer on several projects.

Comments and Discussions