Click here to Skip to main content
15,896,201 members
Articles / Desktop Programming / Windows Forms

Form Changed Control

Rate me:
Please Sign up or sign in to vote.
4.79/5 (13 votes)
26 May 2008CPOL2 min read 91.7K   1.9K   80  
A component that allows you to monitor all the controls on the form and list any that have changed (for dirty checking)
Imports System.ComponentModel

<ProvideProperty("MonitorForChanges", GetType(Control))> _
<ProvideProperty("ChangeEventToMonitor", GetType(Control))> _
<ProvideProperty("ValueNameToMonitor", GetType(Control))> _
Public Class FormChangedComponent
    Implements System.ComponentModel.IExtenderProvider

#Region "Private members"
    Private WithEvents _ControlChanged As New ChangeEventMonitoringCollection
#End Region

#Region "Events"
    Public Event FormControlChanged As EventHandler(Of FormChangedEventArgs)
    Private Sub _ControlChanged_MonitoredControlChanged(ByVal sender As Object, ByVal e As FormChangedEventArgs) Handles _ControlChanged.MonitoredControlChanged
        RaiseEvent FormControlChanged(Me, e)
    End Sub
#End Region

#Region "Public interface"
    ''' <summary>
    ''' Resets all the changed flags for the controls on the form
    ''' </summary>
    ''' <remarks>
    ''' You should reset the dirty flags when a record is saved, for example
    ''' </remarks>
    Public Sub ResetDirtyFlags()
        For Each f As ChangeEventMonitoring In _ControlChanged
            f.Changed = False
        Next
    End Sub


    Private Function GetChangeEventName(ByVal ctl As Control) As String
        If TypeOf (ctl) Is TextBox Then
            Return "TextChanged"
        ElseIf TypeOf (ctl) Is CheckBox Then
            Return "CheckedChanged"
        ElseIf TypeOf (ctl) Is ComboBox Then
            Return "SelectedValueChanged"
        Else
            'Todo : add a thing here for every type of control to monitor
            Return ""
        End If
    End Function

    ''' <summary>
    ''' Returns the collection of controls that have changed since the last reset
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ControlsThatHaveChanged() As Generic.List(Of Control)
        Get
            Dim cRet As New Generic.List(Of Control)
            For Each f As ChangeEventMonitoring In _ControlChanged
                If f.Changed Then
                    cRet.Add(f.ControlToMonitor)
                End If
            Next
            Return cRet
        End Get
    End Property


#Region "MonitorForChanges"
    <Category("Change Monitoring")> _
    <Description("Whether or not to monitor this control for changes")> _
    Public Function GetMonitorForChanges(ByVal ctl As Control) As Boolean
        Return _ControlChanged.Item(ctl).Monitor
    End Function

    <Category("Change Monitoring")> _
    <Description("Whether or not to monitor this control for changes")> _
    Public Sub SetMonitorForChanges(ByVal ctl As Control, ByVal value As Boolean)
        _ControlChanged.Item(ctl).Monitor = value
    End Sub
#End Region

#Region "ChangeEventToMonitor"
    <Category("Change Monitoring")> _
    <Description("Which event to monitor this control for changes")> _
    Public Function GetChangeEventToMonitor(ByVal ctl As Control) As String
        Return _ControlChanged.Item(ctl).ChangeEventToMonitor
    End Function

    <Category("Change Monitoring")> _
    <Description("Which event to monitor this control for changes")> _
    Public Sub SetChangeEventToMonitor(ByVal ctl As Control, ByVal ChangeEvent As String)
        If ChangeEvent <> "" Then
            '\\ validate that it is a property of the control
            If ctl.GetType.GetEvent(ChangeEvent) Is Nothing Then
                Throw New ArgumentException(ChangeEvent & " is not an event of " & ctl.GetType.ToString)
            End If
        End If
        _ControlChanged.Item(ctl).ChangeEventToMonitor = ChangeEvent
    End Sub
#End Region

#Region "ValueNameToMonitor"
    <Category("Change Monitoring")> _
    <Description("Which property to test on this control for changes")> _
    Public Function GetValueNameToMonitor(ByVal ctl As Control) As String
        Return _ControlChanged.Item(ctl).ValueName
    End Function

    <Category("Change Monitoring")> _
    <Description("Which property to test on this control for changes")> _
    Public Sub SetValueNameToMonitor(ByVal ctl As Control, ByVal ValueName As String)
        If ValueName <> "" Then
            '\\ validate that it is a property of the control
            If ctl.GetType.GetProperty(ValueName) Is Nothing Then
                Throw New ArgumentException(ValueName & " is not a property of " & ctl.GetType.ToString)
            End If
        End If
        _ControlChanged.Item(ctl).ValueName = ValueName
    End Sub
#End Region

#End Region

#Region "IExtenderProvider implementation"
    ''' <summary>
    ''' Returns true if the given control can be extended by this component - i.e. if it can sensibly be printed
    ''' </summary>
    ''' <param name="extendee">The control being queried for extensibility</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CanExtend(ByVal extendee As Object) As Boolean Implements System.ComponentModel.IExtenderProvider.CanExtend

        If TypeOf (extendee) Is Form Then
            Return False
        ElseIf TypeOf (extendee) Is Control Then
            Return True
        End If

    End Function
#End Region


    Private Class ChangeEventMonitoring

#Region "Private members"
        Private _ctlToMonitor As Control
        Private _Monitor As Boolean
        Private _ChangeEventName As String
        Private _ValueName As String
        Private _CheckSum As Integer
        Private _Changed As Boolean
        Private _MonitoringEvent As Boolean
        Private _EventHandler As EventHandler
#End Region

#Region "Events"
        Public Event MonitoredControlChanged As EventHandler(Of FormChangedEventArgs)
#End Region

#Region "Public interface"
        ''' <summary>
        ''' The control that is being monitored for change events
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property ControlToMonitor() As Control
            Get
                Return _ctlToMonitor
            End Get
        End Property

        ''' <summary>
        ''' Whether or not to monitor this control for change events
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property Monitor() As Boolean
            Get
                Return _Monitor
            End Get
            Set(ByVal value As Boolean)
                _Monitor = value
                Call ResetMonitoringState()
            End Set
        End Property

        ''' <summary>
        ''' The name of the event to monitor for changes
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>e.g. for a textbox t
        ''' </remarks>
        Public Property ChangeEventToMonitor() As String
            Get
                Return _ChangeEventName
            End Get
            Set(ByVal value As String)
                _ChangeEventName = value
            End Set
        End Property

        ''' <summary>
        ''' The name of the property that exposes the 'value' of the control
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property ValueName() As String
            Get
                Return _ValueName
            End Get
            Set(ByVal value As String)
                _ValueName = value
            End Set
        End Property

        Public Property Changed() As Boolean
            Get
                Return _Changed
            End Get
            Set(ByVal value As Boolean)
                If value = False Then
                    _CheckSum = GetCurrentChecksum()
                End If
                _Changed = value
            End Set
        End Property
#End Region

#Region "Private methods"

        Private Sub GenericChangeEventhandler(ByVal sender As Object, ByVal e As EventArgs)
            '\\ recalculate this control's checksum so we know if it has changed
            _Changed = Not (_CheckSum = GetCurrentChecksum())
            RaiseEvent MonitoredControlChanged(Me, New FormChangedEventArgs(Me.ControlToMonitor, _Changed))
        End Sub

        Private Function ChangeEventhandler() As [Delegate]
            If _EventHandler Is Nothing Then
                _EventHandler = New EventHandler(AddressOf GenericChangeEventhandler)
            End If
            Return _EventHandler
        End Function

        Private Function GetCurrentChecksum() As Integer
            Dim _pi As System.Reflection.PropertyInfo
            If _ValueName <> "" Then
                _pi = _ctlToMonitor.GetType.GetProperty(_ValueName)
                If Not _pi Is Nothing Then
                    Dim _oVal As Object = _pi.GetValue(_ctlToMonitor, Nothing)
                    If Not _oVal Is Nothing Then
                        Return _oVal.GetHashCode
                    End If
                Else
                    Return 0
                End If
            Else
                Return 0
            End If
        End Function

        Private Sub ResetMonitoringState()
            Dim evi As System.Reflection.EventInfo
            evi = _ctlToMonitor.GetType.GetEvent(_ChangeEventName)

            If _MonitoringEvent Then
                '\\ Remove the handler from the control
                If Not (evi Is Nothing) Then
                    '\\ Get the method that adds a handler to the changed event
                    Dim mi As System.Reflection.MethodInfo
                    mi = evi.GetRemoveMethod(False)
                    '\\ add a handler to that changed event
                    mi.Invoke(_ctlToMonitor, New Object() {Me.ChangeEventhandler})
                    _MonitoringEvent = True
                End If
            End If
            If _Monitor Then
                If Not (evi Is Nothing) Then
                    '\\ Get the method that adds a handler to the changed event
                    Dim mi As System.Reflection.MethodInfo
                    mi = evi.GetAddMethod(False)
                    '\\ add a handler to that changed event
                    mi.Invoke(_ctlToMonitor, New Object() {Me.ChangeEventhandler})
                    _MonitoringEvent = True
                End If
            End If
        End Sub
#End Region

#Region "Public constructors"
        Public Sub New(ByVal ctlIn As Control, ByVal MonitorIn As Boolean)
            _ctlToMonitor = ctlIn
            If TypeOf (ctlIn) Is TextBox Then
                _ChangeEventName = "TextChanged"
                _ValueName = "Text"
            ElseIf TypeOf (ctlIn) Is CheckBox Then
                _ChangeEventName = "CheckedChanged"
                _ValueName = "Checked"
            ElseIf TypeOf (ctlIn) Is ComboBox Then
                _ChangeEventName = "SelectedValueChanged"
                _ValueName = "SelectedValue"
            ElseIf TypeOf (ctlIn) Is DateTimePicker Then
                _ChangeEventName = "ValueChanged"
                _ValueName = "Value"
            Else
                'Todo : add a thing here for every type of control to monitor
                _ChangeEventName = ""
                _ValueName = ""
            End If
            _CheckSum = GetCurrentChecksum()
            Me.Monitor = MonitorIn
        End Sub
#End Region

    End Class

    Private Class ChangeEventMonitoringCollection
        Inherits Generic.List(Of ChangeEventMonitoring)


#Region "Events"
        Public Event MonitoredControlChanged As EventHandler(Of FormChangedEventArgs)
#End Region

        Public Overloads ReadOnly Property Item(ByVal ctl As Control) As ChangeEventMonitoring
            Get
                For Each f As ChangeEventMonitoring In Me
                    If f.ControlToMonitor Is ctl Then
                        Return f
                        Exit Property
                    End If
                Next
                '\\ control was not found :. add it
                Dim fret As New ChangeEventMonitoring(ctl, True)
                Me.Add(fret)
                Return fret
            End Get
        End Property

        Public Overloads Sub Add(ByVal item As ChangeEventMonitoring)
            MyBase.Add(item)
            AddHandler item.MonitoredControlChanged, AddressOf ControlChangeEventhandler
        End Sub


        Private Sub ControlChangeEventhandler(ByVal sender As Object, ByVal e As FormChangedEventArgs)
            '\\ Raise the event onwards
            RaiseEvent MonitoredControlChanged(Me, e)
        End Sub

    End Class


End Class

''' <summary>
''' Event arguments for the event raised when an item on a form is changed
''' </summary>
''' <remarks></remarks>
Public Class FormChangedEventArgs
    Inherits EventArgs


#Region "Private members"
    Private _ControlChanged As Control
    Private _Changed As Boolean
#End Region

#Region "Public interface"
    ''' <summary>
    ''' The control on the form that has changed
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ControlChanged() As Control
        Get
            Return _ControlChanged
        End Get
    End Property

    ''' <summary>
    ''' True if the control has changed, false if it has changed back to the last saved value
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Changed() As Boolean
        Get
            Return _Changed
        End Get
    End Property
#End Region

    Public Sub New(ByVal ControlChangedIn As Control, ByVal ChangedIn As Boolean)
        _ControlChanged = ControlChangedIn
        _Changed = ChangedIn
    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
Ireland Ireland
C# / SQL Server developer
Microsoft MVP (Azure) 2017
Microsoft MVP (Visual Basic) 2006, 2007

Comments and Discussions