Link to home
Start Free TrialLog in
Avatar of MBHEY131
MBHEY131

asked on

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.
Avatar of AndyAinscow
AndyAinscow
Flag of Switzerland image

>>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)
Avatar of MBHEY131
MBHEY131

ASKER

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.
>>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.
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.
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?
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?
Setting Form.KeyPreview property to True allow intercept all keyboard events at form level
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.
If u'r using datatable just bind columns to controls and then check either context (LINQ) or dataset if they has changes
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
1. How do you fill datatable?
2. How do you bind controls?
SQL Query and their bound at design time in properties
what are you using for SQL query? Dataset with table adapter? Are you bind controls using bindingsource?
yes to all I have a table adapter, dataset, and bindingsource
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
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.
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!
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 -User generated imageUser generated imageUser generated imageFrom 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 -User generated imageUser generated image
This is really just the tip of the ice berg.

-saige-
got a lot to learn here -
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.)
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

>>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.
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
===================================
ASKER CERTIFIED SOLUTION
Avatar of Ark
Ark
Flag of Russian Federation image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Yeah - I ran across that one now too - I'm reevaluating now!
After much TRIAL and ERROR I have accepted and (INCORPORATED) your answer
THANX