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.
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.
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.
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.
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.
ASKER
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.
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.
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.
+++++++++++++++
your above comment seems close to what I want to do - where, how do I put this code?
+++++++++++++++
your above comment seems close to what I want to do - where, how do I put this code?
ASKER
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
ASKER
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.
++++++++++++++++++++++++++
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
ASKER
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
+++++++++++++
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?
2. How do you bind controls?
ASKER
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?
ASKER
yes to all I have a table adapter, dataset, and bindingsource
ASKER
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
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.
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.
ASKER
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!
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 -
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:
This is really just the tip of the ice berg.
-saige-
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
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
Produces the following output -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)
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 -This is really just the tip of the ice berg.
-saige-
ASKER
got a lot to learn here -
ASKER
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.)
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
>>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.
ASKER
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
========================== =========
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Yeah - I ran across that one now too - I'm reevaluating now!
ASKER
After much TRIAL and ERROR I have accepted and (INCORPORATED) your answer
THANX
THANX
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)