With Events question

I'm having difficulty understanding the events procedure.
I have a from "frmcust" with many textboxes, comboboxes, listboxes, etc. and I want to know the easiest way to check if any thing on the form has been changed fdirty issue and I'm having a hard time understanding anything I read on this subject - could someone show me how and where to put the least amount of code possible.
MBHEY131Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

AndyAinscowFreelance programmer / ConsultantCommented:
>>I want to know the easiest way to check if any thing on the form has been changed fdirty issue

The simplest is probably to have one event - that fired when you want to check (eg. Saving) and just compare the contents/selections to that of the original data.

You could have loads of 'changed' events but then you have to cope with it being changed then being changed back (ie. not actually changed)
0
MBHEY131Author Commented:
The simplest is probably to have one event - that fired when you want to check (eg. Saving) and just compare the contents/selections to that of the original data.

 You could have loads of 'changed' events but then you have to cope with it being changed then being changed back (ie. not actually changed)

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

I'm not sure how to setup the EVENT in question OR even where to put the code - could you give me a very short example of how and where.

I just want to have a variable say "fDirty as boolean" declared PROBABLY AT FORM LEVEL' and then when any textbox, combobox, listbox receives any USER INPUT (KEYDOWN MAYBE) then the code gets triggered to set the variable "fDirty"
to true and I will check the variable at close out time.
0
AndyAinscowFreelance programmer / ConsultantCommented:
>>I just want to have a variable say "fDirty as boolean" declared PROBABLY AT FORM LEVEL' and then when any textbox, combobox, listbox receives any USER INPUT (KEYDOWN MAYBE) then the code gets triggered to set the variable "fDirty"
to true and I will check the variable at close out time.


That is my second paragraph - and as I mentioned I don't think it is a good idea.
Consider.
User changes a radio button, your code sets fDirty to true.  So far so good.  User then realises the change was not correct and sets the radio button back to the original, your code sets fDirty to true.  Whoa - WRONG.
0
Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

MBHEY131Author Commented:
That is my second paragraph - and as I mentioned I don't think it is a good idea.
 Consider.
 User changes a radio button, your code sets fDirty to true.  So far so good.  User then realises the change was not correct and sets the radio button back to the original, your code sets fDirty to true.  Whoa - WRONG.
++++++++++++++++++
I'm not at all concerned about that scenario - that's just a double check and the customer knows he/she made a change and its just a extra click (as is my scenario) I just want to avoid a doc recall/lookup with no changes and then having to resave data.
0
MBHEY131Author Commented:
The simplest is probably to have one event - that fired when you want to check (eg. Saving) and just compare the contents/selections to that of the original data.
+++++++++++++++
your above comment seems close to what I want to do - where, how do I put this code?
0
MBHEY131Author Commented:
or is there a way "at checkout time" of looping though a collection of textboxes, etc and determining if an event such as "KEYDOWN" has been run since recall?
0
ArkCommented:
Setting Form.KeyPreview property to True allow intercept all keyboard events at form level
0
MBHEY131Author Commented:
Setting Form.KeyPreview property to True allow intercept all keyboard events at form level
++++++++++++++++++++++++++
Thanx for the info - I am going to attempt to make use of the ".tag" properties and fill them as I populate all the boxes on my form - because that solves 2 problems, "I believe" I can check against the data ".text" etc, and .tag for any discrepancies and also remove all columns from my data table  "if different" and resave the proper data.
0
ArkCommented:
If u'r using datatable just bind columns to controls and then check either context (LINQ) or dataset if they has changes
0
MBHEY131Author Commented:
If u'r using datatable just bind columns to controls and then check either context (LINQ) or dataset if they has changes
+++++++++++++
not familiar with (LINQ) I DO have all the controls bound though - I want make sure I have accounted for a textbox deletion, etc  or something like that and the reason I am liking the tag route is that it gives me a good comparison and a copy of the textboxes all with minimal code - I will look into LINQ though - THANX
0
ArkCommented:
1. How do you fill datatable?
2. How do you bind controls?
0
MBHEY131Author Commented:
SQL Query and their bound at design time in properties
0
ArkCommented:
what are you using for SQL query? Dataset with table adapter? Are you bind controls using bindingsource?
0
MBHEY131Author Commented:
yes to all I have a table adapter, dataset, and bindingsource
0
ArkCommented:
0
MBHEY131Author Commented:
AH - very interesting I have some more learning to do - I will check all this out in the AM -
this does look promising though
thanx
0
AndyAinscowFreelance programmer / ConsultantCommented:
The key.preview is overkill.
The event you are looking for?  Well, there is a form closing or the save button click (assuming you have a save button).  Both those would be suitable for testing for changes.
To just simply flag on the go then you have TextChanged for textboxs, SelectedIndexChanged for list boxs.... where you would create an event for each control on your form and set the dirty flag (your form level variable) to true in that event.
0
MBHEY131Author Commented:
I am still not sure of the EVENT procedure where and when do I put what code?
I am not interested in putting code in every Listbox, textbox, etc as I have many boxes on many forms!
0
it_saigeDeveloperCommented:
Which is why you don't actually put code on every control (not directly at least).  Instead, you would use a helper class that handles the "dirtiness" of the controls/form.

Using http://www.codeproject.com/Articles/38756/Two-Simple-Approaches-to-WinForms-Dirty-Tracking as a basis; Consider the following:

Form1.vb -
Imports System.Text

Public Class Form1
	Private tracker As DirtyFormTracker

	Private Sub OnLoad(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
		tbFirstName.Text = "Paul"
		tbLastName.Text = "Wilson"
		dtpBirthday.Text = "01/01/2015"
		cmbGender.Text = "Male"
		cbAdministrator.Checked = True
		rbGreen.Checked = True

		tracker = New DirtyFormTracker(Me)
		tracker.MarkAllControlsAsClean()
	End Sub

	Private Sub OnClick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSave.Click, btnListDirtyControls.Click, btnClose.Click
		If TypeOf sender Is Button Then
			Dim btn As Button = DirectCast(sender, Button)
			If btn.Equals(btnClose) Then
				If tracker IsNot Nothing Then
					If tracker.IsDirty() Then
						If MessageBox.Show("Would you like to save changes before closing?", "Changes detected", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question) = DialogResult.Yes Then
							For Each ctl In tracker.GetListOfDirtyControls()
								tracker.MarkControlAsClean(ctl)
							Next
						Else
							MessageBox.Show("The form is clean, so the window closes without prompting to save", "No changed detected", MessageBoxButtons.OK, MessageBoxIcon.Information)
						End If
					End If
				End If
			ElseIf btn.Equals(btnListDirtyControls) Then
				If tracker IsNot Nothing Then
					Dim builder As New StringBuilder()
					For Each ctl In tracker.GetListOfDirtyControls()
						builder.AppendLine(String.Format("{0}; Clean Value = {1}; Current Value = {2}", ctl.Control.Name, ctl.CleanValue, ctl.GetCurrentValue()))
					Next

					If builder.Length > 0 Then
						builder.Insert(0, String.Format("The following controls are dirty:{0}", Environment.NewLine))
					Else
						builder.AppendLine("None of the controls are currently 'dirty'")
					End If

					MessageBox.Show(builder.ToString(), "Dirty Controls")
				End If
			ElseIf btn.Equals(btnSave) Then
				If tracker IsNot Nothing Then
					For Each ctl In tracker.GetListOfDirtyControls()
						tracker.MarkControlAsClean(ctl)
					Next
					btn.Enabled = False
				End If
			End If
		End If
	End Sub
End Class

Public Class DirtyFormTracker
	Private fTrackedForm As Form
	Private fTrackedControls As List(Of DirtyControlTracker)

	Public ReadOnly Property IsDirty() As Boolean
		Get
			Return GetListOfDirtyControls().Count > 0
		End Get
	End Property

	Public Sub New(ByVal parent As Form)
		fTrackedForm = parent
		fTrackedControls = New List(Of DirtyControlTracker)
		AddControlsFromForm(parent)
	End Sub

	' utility method to add the controls from a Form to this collection
	Public Sub AddControlsFromForm(ByVal frm As Form)
		AddControlsFromCollection(frm.Controls)
	End Sub

	' recursive routine to inspect each control and add to the collection accordingly
	Public Sub AddControlsFromCollection(ByVal coll As Control.ControlCollection)
		For Each c As Control In coll
			' if the control is supported for dirty tracking, add it
			Try
				If DirtyControlTracker.IsControlTypeSupported(c) Then
					fTrackedControls.Add(New DirtyControlTracker(c))
				End If
			Catch ex As Exception
				Console.WriteLine(ex.Message)
			End Try

			' recurively apply to inner collections
			If c.HasChildren Then
				AddControlsFromCollection(c.Controls)
			End If
		Next
	End Sub

	' loop through all controls and return a list of those that are dirty
	Public Function GetListOfDirtyControls() As IEnumerable(Of DirtyControlTracker)
		Return (From [control] As DirtyControlTracker In fTrackedControls Where [control].IsDirty() Select [control])
	End Function

	Public Sub MarkControlAsClean(ByVal tracker As DirtyControlTracker)
		tracker.EstablishValueAsClean()
	End Sub

	' mark all the tracked controls as clean
	Public Sub MarkAllControlsAsClean()
		For Each c As DirtyControlTracker In fTrackedControls
			c.EstablishValueAsClean()
		Next
	End Sub
End Class

Public Class DirtyControlTracker
	Private fControl As Control
	Private fCleanValue As String

	' read only properties
	Public ReadOnly Property [Control]() As Control
		Get
			Return fControl
		End Get
	End Property
	Public ReadOnly Property CleanValue() As String
		Get
			Return fCleanValue
		End Get
	End Property

	' constructor establishes the control and uses its current value as "clean"
	Public Sub New(ByVal ctl As Control)
		' if the control type is not one that is supported, throw an exception
		If DirtyControlTracker.IsControlTypeSupported(ctl) Then
			fControl = ctl
		Else
			Throw New NotSupportedException(String.Format("The control type for '{0}' is not supported by the ControlDirtyTracker class.", ctl.Name))
		End If
	End Sub

	' method to establish the the current control value as "clean"
	Public Sub EstablishValueAsClean()
		fCleanValue = GetCurrentValue()
	End Sub

	' determine if the current control value is considered "dirty"; 
	' i.e. if the current control value is different than the one
	' remembered as "clean"
	Public Function IsDirty() As Boolean
		' compare the remembered "clean value" to the current value;
		' if they are the same, the control is still clean;
		' if they are different, the control is considered dirty.
		Return (String.Compare(fCleanValue, GetCurrentValue(), False) <> 0)
	End Function

	' static class utility method; return whether or not the control type 
	' of the given control is supported by this class;
	' developers may modify this to extend support for other types
	Public Shared Function IsControlTypeSupported(ByVal ctl As Control) As Boolean
		' list of types supported
		If TypeOf ctl Is TextBox Then
			Return True
		End If
		If TypeOf ctl Is CheckBox Then
			Return True
		End If
		If TypeOf ctl Is ComboBox Then
			Return True
		End If
		If TypeOf ctl Is ListBox Then
			Return True
		End If
		If TypeOf ctl Is DateTimePicker Then
			Return True
		End If
		If TypeOf ctl Is RadioButton Then
			Return True
		End If

		' ... add additional types as desired ...                       

		' not a supported type
		Return False
	End Function

	' private method to determine the current value (as a string) of the control;
	' if the control is not supported, return a NotSupported exception
	' developers may modify this to extend support for other types
	Public Function GetCurrentValue() As String
		If TypeOf fControl Is TextBox Then
			Return TryCast(fControl, TextBox).Text
		End If

		If TypeOf fControl Is CheckBox Then
			Return TryCast(fControl, CheckBox).Checked.ToString()
		End If

		If TypeOf fControl Is ComboBox Then
			Return TryCast(fControl, ComboBox).Text
		End If

		If TypeOf fControl Is ListBox Then
			' for a listbox, create a list of the selected indexes
			Dim val As New StringBuilder()
			Dim lb As ListBox = TryCast(fControl, ListBox)
			Dim coll As ListBox.SelectedIndexCollection = lb.SelectedIndices
			For i As Integer = 0 To coll.Count - 1
				val.AppendFormat("{0};", coll(i))
			Next

			Return val.ToString()
		End If

		If TypeOf fControl Is DateTimePicker Then
			Return TryCast(fControl, DateTimePicker).Text
		End If

		If TypeOf fControl Is RadioButton Then
			Return TryCast(fControl, RadioButton).Checked.ToString()
		End If

		Return ""
	End Function
End Class

Open in new window

Form1.Designer.vb -
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
    Inherits System.Windows.Forms.Form

    'Form overrides dispose to clean up the component list.
    <System.Diagnostics.DebuggerNonUserCode()> _
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        Try
            If disposing AndAlso components IsNot Nothing Then
                components.Dispose()
            End If
        Finally
            MyBase.Dispose(disposing)
        End Try
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()
		Me.Label1 = New System.Windows.Forms.Label()
		Me.tbFirstName = New System.Windows.Forms.TextBox()
		Me.tbLastName = New System.Windows.Forms.TextBox()
		Me.Label2 = New System.Windows.Forms.Label()
		Me.Label3 = New System.Windows.Forms.Label()
		Me.dtpBirthday = New System.Windows.Forms.DateTimePicker()
		Me.cmbGender = New System.Windows.Forms.ComboBox()
		Me.Label4 = New System.Windows.Forms.Label()
		Me.cbAdministrator = New System.Windows.Forms.CheckBox()
		Me.GroupBox1 = New System.Windows.Forms.GroupBox()
		Me.rbWhite = New System.Windows.Forms.RadioButton()
		Me.rbBlack = New System.Windows.Forms.RadioButton()
		Me.rbYellow = New System.Windows.Forms.RadioButton()
		Me.rbBlue = New System.Windows.Forms.RadioButton()
		Me.rbGreen = New System.Windows.Forms.RadioButton()
		Me.rbRed = New System.Windows.Forms.RadioButton()
		Me.btnSave = New System.Windows.Forms.Button()
		Me.btnClose = New System.Windows.Forms.Button()
		Me.btnListDirtyControls = New System.Windows.Forms.Button()
		Me.GroupBox1.SuspendLayout()
		Me.SuspendLayout()
		'
		'Label1
		'
		Me.Label1.AutoSize = True
		Me.Label1.Location = New System.Drawing.Point(12, 8)
		Me.Label1.Name = "Label1"
		Me.Label1.Size = New System.Drawing.Size(60, 13)
		Me.Label1.TabIndex = 0
		Me.Label1.Text = "First Name:"
		'
		'tbFirstName
		'
		Me.tbFirstName.Location = New System.Drawing.Point(78, 5)
		Me.tbFirstName.Name = "tbFirstName"
		Me.tbFirstName.Size = New System.Drawing.Size(194, 20)
		Me.tbFirstName.TabIndex = 1
		'
		'tbLastName
		'
		Me.tbLastName.Location = New System.Drawing.Point(78, 31)
		Me.tbLastName.Name = "tbLastName"
		Me.tbLastName.Size = New System.Drawing.Size(194, 20)
		Me.tbLastName.TabIndex = 3
		'
		'Label2
		'
		Me.Label2.AutoSize = True
		Me.Label2.Location = New System.Drawing.Point(11, 34)
		Me.Label2.Name = "Label2"
		Me.Label2.Size = New System.Drawing.Size(61, 13)
		Me.Label2.TabIndex = 2
		Me.Label2.Text = "Last Name:"
		'
		'Label3
		'
		Me.Label3.AutoSize = True
		Me.Label3.Location = New System.Drawing.Point(24, 61)
		Me.Label3.Name = "Label3"
		Me.Label3.Size = New System.Drawing.Size(48, 13)
		Me.Label3.TabIndex = 4
		Me.Label3.Text = "Birthday:"
		'
		'dtpBirthday
		'
		Me.dtpBirthday.Location = New System.Drawing.Point(78, 57)
		Me.dtpBirthday.Name = "dtpBirthday"
		Me.dtpBirthday.Size = New System.Drawing.Size(194, 20)
		Me.dtpBirthday.TabIndex = 5
		'
		'cmbGender
		'
		Me.cmbGender.FormattingEnabled = True
		Me.cmbGender.Items.AddRange(New Object() {"Unknown", "Male", "Female", "Undeclared"})
		Me.cmbGender.Location = New System.Drawing.Point(78, 84)
		Me.cmbGender.Name = "cmbGender"
		Me.cmbGender.Size = New System.Drawing.Size(194, 21)
		Me.cmbGender.TabIndex = 6
		'
		'Label4
		'
		Me.Label4.AutoSize = True
		Me.Label4.Location = New System.Drawing.Point(27, 87)
		Me.Label4.Name = "Label4"
		Me.Label4.Size = New System.Drawing.Size(45, 13)
		Me.Label4.TabIndex = 7
		Me.Label4.Text = "Gender:"
		'
		'cbAdministrator
		'
		Me.cbAdministrator.AutoSize = True
		Me.cbAdministrator.Location = New System.Drawing.Point(175, 111)
		Me.cbAdministrator.Name = "cbAdministrator"
		Me.cbAdministrator.Size = New System.Drawing.Size(97, 17)
		Me.cbAdministrator.TabIndex = 8
		Me.cbAdministrator.Text = "Is Administrator"
		Me.cbAdministrator.UseVisualStyleBackColor = True
		'
		'GroupBox1
		'
		Me.GroupBox1.Controls.Add(Me.rbWhite)
		Me.GroupBox1.Controls.Add(Me.rbBlack)
		Me.GroupBox1.Controls.Add(Me.rbYellow)
		Me.GroupBox1.Controls.Add(Me.rbBlue)
		Me.GroupBox1.Controls.Add(Me.rbGreen)
		Me.GroupBox1.Controls.Add(Me.rbRed)
		Me.GroupBox1.Location = New System.Drawing.Point(72, 134)
		Me.GroupBox1.Name = "GroupBox1"
		Me.GroupBox1.Size = New System.Drawing.Size(200, 85)
		Me.GroupBox1.TabIndex = 9
		Me.GroupBox1.TabStop = False
		Me.GroupBox1.Text = "Favorite Color"
		'
		'rbWhite
		'
		Me.rbWhite.AutoSize = True
		Me.rbWhite.Location = New System.Drawing.Point(103, 65)
		Me.rbWhite.Name = "rbWhite"
		Me.rbWhite.Size = New System.Drawing.Size(53, 17)
		Me.rbWhite.TabIndex = 5
		Me.rbWhite.TabStop = True
		Me.rbWhite.Text = "White"
		Me.rbWhite.UseVisualStyleBackColor = True
		'
		'rbBlack
		'
		Me.rbBlack.AutoSize = True
		Me.rbBlack.Location = New System.Drawing.Point(103, 42)
		Me.rbBlack.Name = "rbBlack"
		Me.rbBlack.Size = New System.Drawing.Size(52, 17)
		Me.rbBlack.TabIndex = 4
		Me.rbBlack.TabStop = True
		Me.rbBlack.Text = "Black"
		Me.rbBlack.UseVisualStyleBackColor = True
		'
		'rbYellow
		'
		Me.rbYellow.AutoSize = True
		Me.rbYellow.Location = New System.Drawing.Point(104, 19)
		Me.rbYellow.Name = "rbYellow"
		Me.rbYellow.Size = New System.Drawing.Size(56, 17)
		Me.rbYellow.TabIndex = 3
		Me.rbYellow.TabStop = True
		Me.rbYellow.Text = "Yellow"
		Me.rbYellow.UseVisualStyleBackColor = True
		'
		'rbBlue
		'
		Me.rbBlue.AutoSize = True
		Me.rbBlue.Location = New System.Drawing.Point(7, 66)
		Me.rbBlue.Name = "rbBlue"
		Me.rbBlue.Size = New System.Drawing.Size(46, 17)
		Me.rbBlue.TabIndex = 2
		Me.rbBlue.TabStop = True
		Me.rbBlue.Text = "Blue"
		Me.rbBlue.UseVisualStyleBackColor = True
		'
		'rbGreen
		'
		Me.rbGreen.AutoSize = True
		Me.rbGreen.Location = New System.Drawing.Point(7, 43)
		Me.rbGreen.Name = "rbGreen"
		Me.rbGreen.Size = New System.Drawing.Size(54, 17)
		Me.rbGreen.TabIndex = 1
		Me.rbGreen.TabStop = True
		Me.rbGreen.Text = "Green"
		Me.rbGreen.UseVisualStyleBackColor = True
		'
		'rbRed
		'
		Me.rbRed.AutoSize = True
		Me.rbRed.Location = New System.Drawing.Point(7, 20)
		Me.rbRed.Name = "rbRed"
		Me.rbRed.Size = New System.Drawing.Size(45, 17)
		Me.rbRed.TabIndex = 0
		Me.rbRed.TabStop = True
		Me.rbRed.Text = "Red"
		Me.rbRed.UseVisualStyleBackColor = True
		'
		'btnSave
		'
		Me.btnSave.Location = New System.Drawing.Point(116, 227)
		Me.btnSave.Name = "btnSave"
		Me.btnSave.Size = New System.Drawing.Size(75, 23)
		Me.btnSave.TabIndex = 10
		Me.btnSave.Text = "Save"
		Me.btnSave.UseVisualStyleBackColor = True
		'
		'btnClose
		'
		Me.btnClose.Location = New System.Drawing.Point(197, 227)
		Me.btnClose.Name = "btnClose"
		Me.btnClose.Size = New System.Drawing.Size(75, 23)
		Me.btnClose.TabIndex = 11
		Me.btnClose.Text = "Close"
		Me.btnClose.UseVisualStyleBackColor = True
		'
		'btnListDirtyControls
		'
		Me.btnListDirtyControls.Location = New System.Drawing.Point(12, 227)
		Me.btnListDirtyControls.Name = "btnListDirtyControls"
		Me.btnListDirtyControls.Size = New System.Drawing.Size(98, 23)
		Me.btnListDirtyControls.TabIndex = 12
		Me.btnListDirtyControls.Text = "List Dirty Controls"
		Me.btnListDirtyControls.UseVisualStyleBackColor = True
		'
		'Form1
		'
		Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
		Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
		Me.ClientSize = New System.Drawing.Size(284, 262)
		Me.Controls.Add(Me.btnListDirtyControls)
		Me.Controls.Add(Me.btnClose)
		Me.Controls.Add(Me.btnSave)
		Me.Controls.Add(Me.GroupBox1)
		Me.Controls.Add(Me.cbAdministrator)
		Me.Controls.Add(Me.Label4)
		Me.Controls.Add(Me.cmbGender)
		Me.Controls.Add(Me.dtpBirthday)
		Me.Controls.Add(Me.Label3)
		Me.Controls.Add(Me.tbLastName)
		Me.Controls.Add(Me.Label2)
		Me.Controls.Add(Me.tbFirstName)
		Me.Controls.Add(Me.Label1)
		Me.Name = "Form1"
		Me.Text = "Form1"
		Me.GroupBox1.ResumeLayout(False)
		Me.GroupBox1.PerformLayout()
		Me.ResumeLayout(False)
		Me.PerformLayout()

	End Sub
	Friend WithEvents Label1 As System.Windows.Forms.Label
	Friend WithEvents tbFirstName As System.Windows.Forms.TextBox
	Friend WithEvents tbLastName As System.Windows.Forms.TextBox
	Friend WithEvents Label2 As System.Windows.Forms.Label
	Friend WithEvents Label3 As System.Windows.Forms.Label
	Friend WithEvents dtpBirthday As System.Windows.Forms.DateTimePicker
	Friend WithEvents cmbGender As System.Windows.Forms.ComboBox
	Friend WithEvents Label4 As System.Windows.Forms.Label
	Friend WithEvents cbAdministrator As System.Windows.Forms.CheckBox
	Friend WithEvents GroupBox1 As System.Windows.Forms.GroupBox
	Friend WithEvents rbWhite As System.Windows.Forms.RadioButton
	Friend WithEvents rbBlack As System.Windows.Forms.RadioButton
	Friend WithEvents rbYellow As System.Windows.Forms.RadioButton
	Friend WithEvents rbBlue As System.Windows.Forms.RadioButton
	Friend WithEvents rbGreen As System.Windows.Forms.RadioButton
	Friend WithEvents rbRed As System.Windows.Forms.RadioButton
	Friend WithEvents btnSave As System.Windows.Forms.Button
	Friend WithEvents btnClose As System.Windows.Forms.Button
	Friend WithEvents btnListDirtyControls As System.Windows.Forms.Button

End Class

Open in new window

Produces the following output -Initial loadChanging values and then listing the dirty controlsObviously attempting to close presents the dialog box that changes were detected.From here you can do many different things depending upon how far you want to go.
For example, you can make it so that the save button is only enabled when there are dirty controls (requires hooking into an event on the controls).  You could even create a custom event on the dirty form tracker that alerts the form when it has dirty controls.  Perhaps something like:
Imports System.Text

Public Class Form1
	Private WithEvents tracker As DirtyFormTracker

	Private Sub OnLoad(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
		tbFirstName.Text = "Paul"
		tbLastName.Text = "Wilson"
		dtpBirthday.Text = "01/01/2015"
		cmbGender.Text = "Male"
		cbAdministrator.Checked = True
		rbGreen.Checked = True

		tracker = New DirtyFormTracker(Me)
		tracker.MarkAllControlsAsClean()
		btnSave.Enabled = False
	End Sub

	Private Sub OnCleanlinessChanged(ByVal sender As Object, ByVal e As EventArgs) Handles tracker.CleanlinessChanged
		If TypeOf sender Is DirtyFormTracker Then
			Dim formTracker As DirtyFormTracker = DirectCast(sender, DirtyFormTracker)
			btnSave.Enabled = formTracker.IsDirty()
		End If
	End Sub

	Private Sub OnClick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSave.Click, btnListDirtyControls.Click, btnClose.Click
		If TypeOf sender Is Button Then
			Dim btn As Button = DirectCast(sender, Button)
			If btn.Equals(btnClose) Then
				If tracker IsNot Nothing Then
					If tracker.IsDirty() Then
						If MessageBox.Show("Would you like to save changes before closing?", "Changes detected", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question) = DialogResult.Yes Then
							For Each ctl In tracker.GetListOfDirtyControls()
								tracker.MarkControlAsClean(ctl)
							Next
						Else
							MessageBox.Show("The form is clean, so the window closes without prompting to save", "No changed detected", MessageBoxButtons.OK, MessageBoxIcon.Information)
						End If
					End If
				End If
			ElseIf btn.Equals(btnListDirtyControls) Then
				If tracker IsNot Nothing Then
					Dim builder As New StringBuilder()
					For Each ctl In tracker.GetListOfDirtyControls()
						builder.AppendLine(String.Format("{0}; Clean Value = {1}; Current Value = {2}", ctl.Control.Name, ctl.CleanValue, ctl.GetCurrentValue()))
					Next

					If builder.Length > 0 Then
						builder.Insert(0, String.Format("The following controls are dirty:{0}", Environment.NewLine))
					Else
						builder.AppendLine("None of the controls are currently 'dirty'")
					End If

					MessageBox.Show(builder.ToString(), "Dirty Controls")
				End If
			ElseIf btn.Equals(btnSave) Then
				If tracker IsNot Nothing Then
					For Each ctl In tracker.GetListOfDirtyControls()
						tracker.MarkControlAsClean(ctl)
					Next
					btn.Enabled = False
				End If
			End If
		End If
	End Sub
End Class

Public Class DirtyFormTracker
	Private ReadOnly fCleanlinessChanged As New List(Of CleanlinessChangedEventHandler)
	Public Custom Event CleanlinessChanged As CleanlinessChangedEventHandler
		AddHandler(ByVal value As CleanlinessChangedEventHandler)
			fCleanlinessChanged.Add(value)
		End AddHandler

		RemoveHandler(ByVal value As CleanlinessChangedEventHandler)
			fCleanlinessChanged.Remove(value)
		End RemoveHandler

		RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
			For Each handler As CleanlinessChangedEventHandler In fCleanlinessChanged
				Try
					handler.Invoke(sender, e)
				Catch ex As Exception
					Debug.WriteLine(String.Format("Exception while invoking event handler: {0}", ex))
				End Try
			Next
		End RaiseEvent
	End Event

	Private fTrackedForm As Form
	Private fTrackedControls As List(Of DirtyControlTracker)

	Public ReadOnly Property IsDirty() As Boolean
		Get
			Return GetListOfDirtyControls().Count > 0
		End Get
	End Property

	Public Sub New(ByVal parent As Form)
		fTrackedForm = parent
		fTrackedControls = New List(Of DirtyControlTracker)
		AddControlsFromForm(parent)
	End Sub

	' utility method to add the controls from a Form to this collection
	Public Sub AddControlsFromForm(ByVal frm As Form)
		AddControlsFromCollection(frm.Controls)
	End Sub

	' recursive routine to inspect each control and add to the collection accordingly
	Public Sub AddControlsFromCollection(ByVal coll As Control.ControlCollection)
		For Each c As Control In coll
			' if the control is supported for dirty tracking, add it
			Try
				If DirtyControlTracker.IsControlTypeSupported(c) Then
					Dim controlTracker As New DirtyControlTracker(c)
					AddHandler controlTracker.CleanlinessChanged, AddressOf OnCleanLinessChanged
					fTrackedControls.Add(controlTracker)
				End If
			Catch ex As Exception
				Console.WriteLine(ex.Message)
			End Try

			' recurively apply to inner collections
			If c.HasChildren Then
				AddControlsFromCollection(c.Controls)
			End If
		Next
	End Sub

	' loop through all controls and return a list of those that are dirty
	Public Function GetListOfDirtyControls() As IEnumerable(Of DirtyControlTracker)
		Return (From [control] As DirtyControlTracker In fTrackedControls Where [control].IsDirty() Select [control])
	End Function

	Public Sub MarkControlAsClean(ByVal tracker As DirtyControlTracker)
		tracker.EstablishValueAsClean()
	End Sub

	' mark all the tracked controls as clean
	Public Sub MarkAllControlsAsClean()
		For Each c As DirtyControlTracker In fTrackedControls
			c.EstablishValueAsClean()
		Next
	End Sub

	Private Sub OnCleanLinessChanged(ByVal sender As Object, ByVal e As EventArgs)
		RaiseEvent CleanlinessChanged(Me, e)
	End Sub
End Class

Public Class DirtyControlTracker
	Private ReadOnly fCleanlinessChanged As New List(Of CleanlinessChangedEventHandler)
	Public Custom Event CleanlinessChanged As CleanlinessChangedEventHandler
		AddHandler(ByVal value As CleanlinessChangedEventHandler)
			fCleanlinessChanged.Add(value)
		End AddHandler

		RemoveHandler(ByVal value As CleanlinessChangedEventHandler)
			fCleanlinessChanged.Remove(value)
		End RemoveHandler

		RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
			For Each handler As CleanlinessChangedEventHandler In fCleanlinessChanged
				Try
					handler.Invoke(sender, e)
				Catch ex As Exception
					Debug.WriteLine(String.Format("Exception while invoking event handler: {0}", ex))
				End Try
			Next
		End RaiseEvent
	End Event

	Private fControl As Control
	Private fCleanValue As String

	' read only properties
	Public ReadOnly Property [Control]() As Control
		Get
			Return fControl
		End Get
	End Property
	Public ReadOnly Property CleanValue() As String
		Get
			Return fCleanValue
		End Get
	End Property

	' constructor establishes the control and uses its current value as "clean"
	Public Sub New(ByVal ctl As Control)
		' if the control type is not one that is supported, throw an exception
		If DirtyControlTracker.IsControlTypeSupported(ctl) Then
			fControl = ctl
			AssignHandlerForDataChanged(ctl)
		Else
			Throw New NotSupportedException(String.Format("The control type for '{0}' is not supported by the ControlDirtyTracker class.", ctl.Name))
		End If
	End Sub

	' method to establish the the current control value as "clean"
	Public Sub EstablishValueAsClean()
		fCleanValue = GetCurrentValue()
	End Sub

	' determine if the current control value is considered "dirty"; 
	' i.e. if the current control value is different than the one
	' remembered as "clean"
	Public Function IsDirty() As Boolean
		' compare the remembered "clean value" to the current value;
		' if they are the same, the control is still clean;
		' if they are different, the control is considered dirty.
		Return (String.Compare(fCleanValue, GetCurrentValue(), False) <> 0)
	End Function

	' static class utility method; return whether or not the control type 
	' of the given control is supported by this class;
	' developers may modify this to extend support for other types
	Public Shared Function IsControlTypeSupported(ByVal ctl As Control) As Boolean
		' list of types supported
		If TypeOf ctl Is TextBox Then
			Return True
		ElseIf TypeOf ctl Is CheckBox Then
			Return True
		ElseIf TypeOf ctl Is ComboBox Then
			Return True
		ElseIf TypeOf ctl Is ListBox Then
			Return True
		ElseIf TypeOf ctl Is DateTimePicker Then
			Return True
		ElseIf TypeOf ctl Is RadioButton Then
			Return True
		End If

		' ... add additional types as desired ...                       

		' not a supported type
		Return False
	End Function

	' private method to determine the current value (as a string) of the control;
	' if the control is not supported, return a NotSupported exception
	' developers may modify this to extend support for other types
	Public Function GetCurrentValue() As String
		If TypeOf fControl Is TextBox Then
			Return TryCast(fControl, TextBox).Text
		ElseIf TypeOf fControl Is CheckBox Then
			Return TryCast(fControl, CheckBox).Checked.ToString()
		ElseIf TypeOf fControl Is ComboBox Then
			Return TryCast(fControl, ComboBox).Text
		ElseIf TypeOf fControl Is ListBox Then
			' for a listbox, create a list of the selected indexes
			Dim val As New StringBuilder()
			Dim lb As ListBox = TryCast(fControl, ListBox)
			Dim coll As ListBox.SelectedIndexCollection = lb.SelectedIndices
			For i As Integer = 0 To coll.Count - 1
				val.AppendFormat("{0};", coll(i))
			Next

			Return val.ToString()
		ElseIf TypeOf fControl Is DateTimePicker Then
			Return TryCast(fControl, DateTimePicker).Text
		ElseIf TypeOf fControl Is RadioButton Then
			Return TryCast(fControl, RadioButton).Checked.ToString()
		End If

		Return ""
	End Function

	Private Sub AssignHandlerForDataChanged(ByVal ctl As Control)
		If TypeOf ctl Is TextBox Then
			AddHandler ctl.TextChanged, AddressOf OnChanged
		ElseIf TypeOf ctl Is CheckBox Then
			AddHandler DirectCast(ctl, CheckBox).CheckedChanged, AddressOf OnChanged
		ElseIf TypeOf ctl Is ComboBox Then
			AddHandler DirectCast(ctl, ComboBox).SelectedValueChanged, AddressOf OnChanged
		ElseIf TypeOf ctl Is ListBox Then
			AddHandler DirectCast(ctl, ListBox).SelectedValueChanged, AddressOf OnChanged
		ElseIf TypeOf ctl Is DateTimePicker Then
			AddHandler DirectCast(ctl, DateTimePicker).ValueChanged, AddressOf OnChanged
		ElseIf TypeOf ctl Is RadioButton Then
			AddHandler DirectCast(ctl, RadioButton).CheckedChanged, AddressOf OnChanged
		End If
	End Sub

	Private Sub OnChanged(ByVal sender As Object, ByVal e As EventArgs)
		RaiseEvent CleanlinessChanged(Me, e)
	End Sub
End Class

Public Delegate Sub CleanlinessChangedEventHandler(ByVal sender As Object, ByVal e As EventArgs)

Open in new window

Now when you leave each one of the supported controls, if it's value is dirty then the save button automatically enables.  Otherwise, the save button is disabled.  For example -Initial load.  Save button disabled.And when we change the value.  The save button enables.
This is really just the tip of the ice berg.

-saige-
0
MBHEY131Author Commented:
got a lot to learn here -
0
MBHEY131Author Commented:
I like the .HasChanges Method on the dataset combined with the ".tag" property with the boxes on the form which is what I'm mulling over now.

Is there a good reason not to investigate this if I have a bound dataset and a reason to clean out the database record because a customer could delete the data (although this might not be necessary.)
0
ArkCommented:
A brief example (dsMain is a dataset, Users - datatable, taUsers - tableAdapter, bsUsers - bindingsource, tbSave - save (toolstrip)button
    Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        'check for changes before closing
        If dsMain.HasChanges Then
            Dim ret = MsgBox("Save changes?", MsgBoxStyle.YesNoCancel + MsgBoxStyle.Question)
            If ret = MsgBoxResult.Cancel Then
                e.Cancel = True
            ElseIf ret = MsgBoxResult.Yes Then
                e.Cancel = Not SaveData()
            End If
        End If
    End Sub

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        'fill datatable
        Me.taUsers.Fill(Me.dsMain.Users)
    End Sub

    Private Sub bsUsers_ListChanged(sender As Object, e As System.ComponentModel.ListChangedEventArgs) Handles bsUsers.ListChanged
        Select Case e.ListChangedType
            Case System.ComponentModel.ListChangedType.ItemAdded, System.ComponentModel.ListChangedType.ItemChanged, System.ComponentModel.ListChangedType.ItemDeleted
                'enable save button
                tbSave.Enabled = True
            Case Else
        End Select
    End Sub

    Private Sub tbSave_Click(sender As System.Object, e As System.EventArgs) Handles tbSave.Click
        If SaveData() Then tbSave.Enabled = False
    End Sub

    Private Function SaveData() As Boolean
        If dsMain.HasChanges Then
            Try
                taUsers.Update(dsMain.Users)
                Return True
            Catch ex As Exception
                MsgBox(ex.Message, MsgBoxStyle.Critical)
                Return False
            End Try
        Else
            Return True
        End If
    End Function

Open in new window

0
AndyAinscowFreelance programmer / ConsultantCommented:
>>I am not interested in putting code in every Listbox, textbox, etc as I have many boxes on many forms

OK.  Initially I told you of two possible (general that can be used anywhere) methods.  You rejected the 'better' one as being too effective.  So that left the second, cruder, method which would involve less code but not always work correctly.  You don't want that as it is too much code.
As you want something that involves little code and may result in the user being told changes have been made even when they haven't that leaves the third method:
The data is always changed by the user, even when it hasn't been.  Even if the user has not done anything you react as if something has been changed.  No code required on your part to perform any checks.
0
MBHEY131Author Commented:
OK.  Initially I told you of two possible (general that can be used anywhere) methods.  You rejected the 'better' one as being too effective.  So that left the second, cruder, method which would involve less code but not always work correctly.  You don't want that as it is too much code.
 As you want something that involves little code and may result in the user being told changes have been made even when they haven't that leaves the third method:
The data is always changed by the user, even when it hasn't been.  Even if the user has not done anything you react as if something has been changed.  No code required on your part to perform any checks.
+++++++++++++++++++++++++++++++++
I'm in the process of matching the tag and text properties at close out time - solving 2 problems , so far so good
===================================
0
ArkCommented:
Again - you don't need to store original values for records as .Tag or other properties. DataTable store original rows as well as modified.
'Assuming dt id your datatable
For Each dr As DataRow In dt.Rows
    If dr.RowState = DataRowState.Modified Then
        For Each dc As DataColumn In dt.Columns
            If Not dr(dc, DataRowVersion.Current).Equals(dr(dc, DataRowVersion.Original)) Then
                Debug.Print(String.Format("Original: {0} => Current: {1}", dr(dc, DataRowVersion.Original).ToString, dr(dc, DataRowVersion.Current).ToString))
            End If
         Next
     End If
Next

Open in new window

'Note that if cell value (either original or current) is nothing then above code will throw an exception.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
MBHEY131Author Commented:
Yeah - I ran across that one now too - I'm reevaluating now!
0
MBHEY131Author Commented:
After much TRIAL and ERROR I have accepted and (INCORPORATED) your answer
THANX
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Visual Basic.NET

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.