VB.net MVC understanding and binding - vb.net

I am trying to seperate my code logic from my gui as in MVC principles, what I am trying to achieve is quite simple I believe
I have my Form1, which contains a textbox and button, once the button is clicked it loads a function in my controller class which adds a string to a database using entity and then should update the textbox with this name.
I thought what I would need to do is pass the original form through and then databind to the textbox object on the form, this is where I have come unstuck though, as my logic fails...
Public Class Form1
Private mf As New MainForm(Me)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
mf.buttonClick()
End Sub
End Class
Public Class MainForm
Private Property a As Form
Public Sub New(ByVal s As Form)
a = s
End Sub
Function buttonClick() As Boolean
Dim context As TestDBEntities2 = New TestDBEntities2
Dim newCategory As tTable = New tTable
newCategory.Name = "Test1 " & Today.DayOfWeek
context.tTables.Add(newCategory)
context.SaveChanges()
Dim current As String = newCategory.Name
a.DataBindings.Add("text", "TextBox1", current)
Return True
End Function
End Class
and my error:
Cannot bind to the property or column Test1 6 on the DataSource.
Am I looking at this the right way? Or am I so far off that there is an obvious reason this doesn't work?
Any input would be appreciated! Whats the best way to pass data back to a source without returning it in as a result of a function?

You should consider changing your code a bit, so that it reflects more the MVC structure:
use Events to exchange data and indicate action triggers, instead of using the form object
normally the controller has knowledge of the form and not the other way around, so swap this in your project. This reflects also the first point
So a possible solution for a Windows Forms application could look like this:
The form that has one button and one text field, one event to signal the button click and one WriteOnly property to fill the TextBox from outside the form:
Public Class MainForm
Public Event GenerateAndShowEvent()
' allow text box filling
Public WriteOnly Property SetTextBoxContent()
Set(ByVal value)
generatedInputTextBox.Text = value
End Set
End Property
Private Sub generateAndShowButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles generateAndShowButton.Click
' forward message: inform the subscriber that something happened
RaiseEvent GenerateAndShowEvent()
End Sub
End Class
The controller class has knowledge of the form, creates it, binds (listens) to the form's button click event and makes the form ReadOnly for the world (exposes the form):
Public Class MainFormController
' the form that the controller manages
Private dialog As MainForm = Nothing
Public Sub New()
dialog = New MainForm()
' bind to the form button click event in order to generate the text and response
AddHandler dialog.GenerateAndShowEvent, AddressOf Me.GenerateAndShowText
End Sub
' allow the world to access readonly the form - used to start the application
Public ReadOnly Property GetMainForm()
Get
Return dialog
End Get
End Property
Private Sub GenerateAndShowText()
' create the text
Dim text As String = "Test test test"
' access the Database ...
' give the generated text to the UI = MainForm dialog!
dialog.SetTextBoxContent = text
End Sub
End Class
Now what left is to create first the controller, that creates the form and use the form to show it. This can be done like this:
Create an AppStarter module with a Main method:
Module AppStarter
Sub Main()
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
' create the controller object
Dim controller As MainFormController = New MainFormController()
' use the public property to get the dialog
Application.Run(controller.GetMainForm)
End Sub
End Module
In your project settings uncheck the enable application framework setting and set the AppStarter module as the start point of your project:
Now you have a Windows Form Project using the MVC pattern.
If you still want to use DataBinding for the TextBox control, then create a Data Transfer Object or DTO that represents the fields you will transfer from your controller to the form:
Public Class DataContainer
Private t As String
Private i As Integer
Public Property Text() As String
Get
Return t
End Get
Set(ByVal value As String)
t = value
End Set
End Property
Public Property Id() As Integer
Get
Return i
End Get
Set(ByVal value As Integer)
i = value
End Set
End Property
End Class
Then add a BindingSource for your TextBox and configure it to use the DTObject:
Now bind the TextBox control to the DataBinding control:
What left is to add a public setter for the TextBox data binding control in the form:
Public Property TextBoxDataSource()
Get
Return TextBoxBindingSource.DataSource
End Get
Set(ByVal value)
TextBoxBindingSource.DataSource = value
End Set
End Property
and transfer the data from the controller:
Private Sub GenerateAndShowText()
' create the text
Dim text As String = "Test test test"
' access the Database ...
' give the generated text to the UI = MainForm dialog!
'dialog.SetTextBoxContent = text
Dim data As DataContainer = New DataContainer
data.Text = text
data.Id = 1 ' not used currently
dialog.TextBoxDataSource = data
End Sub
The binding can also be set programmatically - instead of doing this over the control property window, add the following code in the constructor of the form:
Public Sub New()
InitializeComponent()
' bind the TextBox control manually to the binding source
' first Text is the TextBox.Text property
' last Text is the DataContainer.Text property
generatedInputTextBox.DataBindings.Add(New Binding("Text", TextBoxBindingSource, "Text"))
End Sub

You seem to misunderstand the Add method.
The first argument is the name of the control's property to which you are binding. This should be "Text", not "text".
The second argument is an object that contains the data you want to bind. You have passed the name of the target control, rather than the source of the data. You are also binding to the form rather than the text box. So what you have said is that you want to bind the form's text property to data that can be extracted from the string "TextBox1".
The third argument says where to go to find the data. For example, if you passed a FileInfo object for the second argument, and you wanted to bind the file's path, you would pass the string "FullName", because that is the name of the property containing the data you want. So you have told the binding to look for a property on the string class called "Test1 6", which is why you have received the error message saying it can't be found.
I think what you want is
a.TextBox1.DataBindings.Add("Text", newCategory, "Name");

Related

Listbox breaks when setting it to a virtual instance from a class

I have a weird problem that I can't wrap my head around.
I have the following code:
Public Class Form1
Public WithEvents MyClass1 As New MyClass
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
End Sub
Private Sub MyClass_UpdateListbox() Handles MyClass1.UpdateListbox
For Each sItem as String In MyClass1.Listbox
MsgBox(sItem) 'an MsgBox shows correct items each time.
Next sItem
Me.Listbox = Me.MyClass1.Listbox 'doesn't work and breaks listbox.
Me.Listbox.Items.Clear() 'listbox is empty anyway, but has no effect.
Me.Listbox.Items.Add("event triggered") 'does nothing.
End Sub
End Class
Public Class MyClass
Public Listbox as new Listbox
Public Event UpdateListbox()
Public Sub New()
'Constructor. sub.
Me.AddItem("Populating listbox")
End Sub
Public Sub AddItem(sItem as String)
Me.Listbox.Items.Add(sItem)
RaiseEvent UpdateListbox()
End Sub
End Class
If I comment the following lines in above code, the listbox keeps adding event triggered, as expected. Of course, I don't have to remove the clear one. It will work, but then it just adds the same item. If I use a command button and call MyClass.AddItem("Something") that is correctly added too as long as the below is commented out. But if not, then once the listbox is in broken state, nothing can be added anymore.
Me.Listbox = Me.MyClass1.Listbox 'doesn't work and breaks listbox.
Me.Listbox.Items.Clear() 'listbox is empty anyway, but has no effect.
How can I use a virtual listbox and assign it to my real listbox?
Also, instead of assigning one listbox to the other, I can of course use that for each loop and add each item one by one which works, but that for each look was for debugging purpose in the first place.
EDIT:
My goal with this application is to build a Todo list with features that are not in a todolist. This is a project I build for work because there I need a tool like this. I already have a todolist that I use but I built it wrong in the past. Everything was condensed in form1, no modules no extra classes. As a result I got weird bugs that I patched with workarounds. I am now rebuilding the application from the ground up, separating tasks in its own classes so I can apply business logic and have a true OOP application. The todo list will become its own class, and managing the list etc will be handeled by this class. It interacts with controls on the form, such as buttons and listboxes. If I just use form1.listbox from the class, things break at program start. I started another question and the below code was a now deleted answer. At first I did not get it working because I did not realize the listbox crashes if I assign it the virtual instance.
So my goal is to have the todolist be handled entirely by the todolist class. It does need a way to interact with controls on form1, and that is the puzzle I'm currently trying to solve.
In the original code, the main problem is that the Field that hold the instance of a Control shown if a Form is reassigned to the instance of another ListBox Control defined in a custom class:
Me.Listbox = Me.MyClass1.Listbox
From now on, Me.Listbox points another ListBox that is not show on screen, so any attempt to update the Form's child ListBox fails, except when Me.Listbox.Items.Clear() is called - in the same procedure - after it's being reassigned, because the handle of the Owner of the ObjectCollection (the object that holds the Items shown in the ListBox) has not been updated yet. It's going to fail after the current method exits nonetheless.
As noted in comments, this is a simplified method to handle a Form and its child Controls using a handler class. The contract between the class handler and a Form is sealed by an Interface (named IFormHandler here).
A Form that implements this Interface exposes the methods defined by the Interface that allow to trigger Actions and specific behaviors, depending on the Type of Control and the implementation.
I suggest to take a look at the MVP or ReactiveUI (MVVM-derived) for WinForms Patterns.
How too proceed:
Open up the ApplicationEvents class object.
If you don't have it already, select Project -> Properties -> Application and click the View Application Events button. It will generate ApplicationEvents.vb. Find it in Solution Explorer and open it up.
It should look like this (plus a bunch of comments that explain what it's for):
Imports Microsoft.VisualBasic.ApplicationServices
Namespace My
Partial Friend Class MyApplication
End Class
End Namespace
Paste into MyApplication these lines of code:
Imports Microsoft.VisualBasic.ApplicationServices
Namespace My
Partial Friend Class MyApplication
Public SomeFormHandler As MyFormHandler(Of SomeForm)
Protected Overrides Function OnStartup(e As StartupEventArgs) As Boolean
SomeFormHandler = New MyFormHandler(Of SomeForm)
Return MyBase.OnStartup(e)
End Function
End Class
End Namespace
Add an Interface that defines the Actions (or Behaviors) that a Form must implement.
Here, the GetUsersList() method specifies that a Form that implements this Interface must return the instance of a child ListBox Control.
(To add an Interface, select Project -> Add -> New Item... and select the Interface template. Name the file IFormHandler)
Extend this Interface as needed, to add more Methods or Properties that define actions and behaviors.
Public Interface IFormHandler
Function GetUsersList() As ListBox
End Interface
A Form that implements the IFormHandler Interface implements and exposes the GetUsersList() method, which returns the instance of a ListBox Control (named usersList here)
There's nothing else to do with this Form, the control is handed over to the MyFormHandler object that is initialized with this Type.
Public Class SomeForm
Implements IFormHandler
Public Sub New()
InitializeComponent()
End Sub
Public Function GetUsersList() As ListBox Implements IFormHandler.GetUsersList
Return Me.usersList
End Function
End Class
Now, to show SomeForm, you can use the MyFormHandler class object show below.
' Set the Owner if called from another Form
My.Application.SomeFormHandler.Show(Me)
' Or without an Owner
My.Application.SomeFormHandler.Show()
To close SomeForm, you can either use its handler:
My.Application.SomeFormHandler.Close()
or close it as usual:
[SomeForm Instance].Close()
If MyFormHandler determines that the instance of SomeForm has been disposed, it creates a new one when you call its Show() method again later.
To update the ListBox Control of SomeForm, use the public methods exposed by the MyFormHandler class:
' Add a new element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.AddElement, "Some Item")
' Remove an element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.RemoveElement, "Some Item")
' Replace an element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.ReplaceElement, "New Item", "Some Item")
' Clears the ListBox
My.Application.SomeFormHandler.ClearUsersList()
All these actions generate an event that you can subscribe to when needed.
See also the example that shows how to raise a custom event when the ListBox raises one of its stardard events; SelectedIndexChanged is handled here.
See the implementation of MyFormHandler.
Generic Form handler:
A Form needs to implement the IFormHandler Interface for the MyFormHandler class to accept it as valid.
You can of course extend the Interface, to add more Actions, or build a MyFormHandler class object that uses a different Interface, or more than one.
Public Class MyFormHandler(Of TForm As {Form, IFormHandler, New})
Implements IDisposable
Private formObject As TForm
Private IsInstanceSelfClosing As Boolean = False
Public Event UsersListUpdate(item As Object, changeType As UpdateType)
Public Event UsersListIndexChanged(index As Integer)
Public Sub New()
InitializeInstance()
Dim lstBox = formObject.GetUsersList()
AddHandler lstBox.SelectedIndexChanged, AddressOf OnUsersListIndexChanged
End Sub
Private Sub InitializeInstance()
formObject = New TForm()
AddHandler formObject.FormClosing, AddressOf OnFormClosing
End Sub
Private Sub OnFormClosing(sender As Object, e As FormClosingEventArgs)
IsInstanceSelfClosing = True
Dispose()
End Sub
Public Sub UpdateUsersList(updateMode As UpdateType, newItem As Object, Optional oldItem As Object = Nothing)
If newItem Is Nothing Then Throw New ArgumentException("New Item is null")
Dim lstBox = formObject.GetUsersList()
Select Case updateMode
Case UpdateType.AddElement
lstBox.Items.Add(newItem)
Case UpdateType.RemoveElement
lstBox.Items.Remove(newItem)
Case UpdateType.ReplaceElement
If oldItem Is Nothing Then Throw New ArgumentException("Replacement Item is null")
Dim index = lstBox.Items.IndexOf(oldItem)
lstBox.Items.Remove(oldItem)
lstBox.Items.Insert(index, newItem)
Case Else : Return
End Select
RaiseEvent UsersListUpdate(newItem, updateMode)
End Sub
Public Sub ClearUsersList()
formObject.GetUsersList().Items.Clear()
End Sub
Private Sub OnUsersListIndexChanged(sender As Object, e As EventArgs)
RaiseEvent UsersListIndexChanged(DirectCast(sender, ListBox).SelectedIndex)
End Sub
Public Sub Show(Optional owner As IWin32Window = Nothing)
If formObject Is Nothing OrElse formObject.IsDisposed Then InitializeInstance()
If formObject.Visible Then
formObject.WindowState = FormWindowState.Normal
formObject.BringToFront()
Else
formObject.Show(owner)
End If
End Sub
Public Sub Close()
If formObject IsNot Nothing AndAlso (Not formObject.IsDisposed) Then
RemoveHandler formObject.FormClosing, AddressOf OnFormClosing
IsInstanceSelfClosing = False
Dispose()
End If
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Overridable Sub Dispose(disposing As Boolean)
If disposing Then
If formObject Is Nothing OrElse formObject.IsDisposed Then Return
Dim lstBox = formObject.GetUsersList()
RemoveHandler lstBox.SelectedIndexChanged, AddressOf OnUsersListIndexChanged
RemoveHandler formObject.FormClosing, AddressOf OnFormClosing
If Not IsInstanceSelfClosing Then formObject.Close()
IsInstanceSelfClosing = False
End If
End Sub
End Class
Enumerator used in MyFormHandler:
Public Enum UpdateType
AddElement
RemoveElement
ReplaceElement
End Enum

How to pass value from Form1 to Form2

I'm making a program that generates SQL Server code to use it in my VB.NET program.
I have this first form that contains the connection like you see in picture below:
The connection works 100%, but in the second form I have two DataGridViews, one for tables and one for fields.
So when I click on any table of DataGridView1 => DataGridView2 show it fields:
When I click on DataGridView1 to get the value of ComboBox from Form1 to use it in Form2 I have the following error:
Failed to connect to server.
Code:
Dim frm As New Form2
prd.ServerConnection = New ServerConnection(frm.ComboServer.Text) ' here the error
prd.DGVField(MetroGridTables, MetroGridField)
I use Form1 to make connection and Form2 to make operation.
The simplest way to pass a value from one form to another is to implement the New method on the form you want to pass the value to:
Form1:
Public Class Form1
Private Sub btnPass_Click(sender As Object, e As EventArgs) Handles btnPass.Click
Dim form As New Form2(TextBox1.Text)
form.Show()
End Sub
End Class
Form2:
Public Class Form2
Public Sub New(ByVal value As String)
' This call is required by the designer.
InitializeComponent()
Label1.Text = value
End Sub
End Class
Screenshot:
This question is a bit confusing, but this is how I would pass a variable from one form to another.
Create a class /w variable.
Public Class Variables
Public Shared Property imavariable As String
Get
Return m_imavariable
End Get
Set(value As String)
m_imavariable = value
End Set
End Property
Private Shared m_imavariable As String
End Class
Set the variable from form 1... variables.imavariable = string
Read the variable from form2.... string = variables.imavariable

Passing data between forms DIRECTLY

I'm making a "Preference form" that will hold all the users preferences and when they go to Apply/Save I want the new values to transfer back to the main form and updateand close the form2. In the past I have done this like this:
Private Sub PreferencesToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles PreferencesToolStripMenuItem.Click
Preferences.Show()
End Sub
and when I click the "Apply/Save" button before it closes I would Transfer all data like this:
form1.textbox.text = form2.textbox.text
Is there anything wrong doing it this way??
What I have been reading is I should be doing it like this:
Private Sub PreferencesToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles PreferencesToolStripMenuItem.Click
Dim dialog As New Preferences
dialog.ShowDialog()
End Sub
And when when they click "Apply/Save" it would take all the values from Form2 and store them in a private variable (or Property) in Form2 and when that form closes I would then access the value like this:
Private Sub PreferencesToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles PreferencesToolStripMenuItem.Click
Dim dialog As New Preferences
dialog.ShowDialog()
form1.textbox.text = dialog.variable
End Sub
Why would this be a better way of doing this?
UPDATE....Looking at the code below this is just a SMALL sample of all the options I will have. What is the best way to collect of the data into the object to use when serializing?
<Serializable>
Public Class Preference
#Region "Properties"
Public Property ScaleLowest As String = "5"
Public Property ScaleHighest As String = "200"
Public Property ScaleInc As String = "5"
Public Property ThickLowest As Double = 0.125
Public Property ThickHighest As Double = 4
Public Property ThickInc As Double = 0.125
Public Property WidthLowest As Double = 0.125
Public Property WidthHighest As Double = 0.6
Public Property WidthInc As Double = 0.125
Public Property LengthLowest As Double = 1
Public Property LengthHighest As Double = 96
Public Property LengthInc As Double = 1
Public Property FractionON As Boolean = False
Public Property DecimalON As Boolean = True
Public Property ColorSelection As String = "Colors"
Public Property FinalColor As String = "255, 255, 0"
Public Property roughColor As String = "255, 255, 100"
Public Property SplashON As Boolean = False
Public Property LogInON As Boolean = False
#End Region
Public Sub New()
'for creating new instance for deserializing
End Sub
Public Sub GatherAllData()
'Save Defaults
SaveSerializeObj()
End Sub
Public Sub SaveSerializeObj()
'Get Changes?????
'Serialize object to a text file.
Dim objStreamWriter As New StreamWriter("C:\Users\Zach454\Desktop\test.xml")
Dim x As New XmlSerializer(Me.GetType)
x.Serialize(objStreamWriter, Me)
objStreamWriter.Close()
End Sub
Public Function LoadSerializeObj() As Preference
'Check if new file need created
If File.Exists("C:\Users\454\Desktop\test.xml") = False Then
SaveSerializeObj()
End If
'Deserialize text file to a new object.
Dim objStreamReader As New StreamReader("C:\Users\454\Desktop\test.xml")
Dim newObj As New Preference
Dim x As New XmlSerializer(newObj.GetType)
newObj = CType(x.Deserialize(objStreamReader), Preference)
objStreamReader.Close()
Return newObj
End Function
The best option is to create a class that would have properties for your form controls. Then you can store these properties and then access these when needed.
Also there's really no reason to be passing data back and forth, you can store this data off somewhere (database, file, mysettings etc) and then load this data up into a class. Then you can store and retrieve data from this class. Then if you need to save data back to somewhere you have a class object to use.
Here is a short example to show how you can create another form (Preferences) click save and then show those values back on the other form (calling form).
This is the main form
Public Class Form1
Public _frm2 As Form2
Private Sub btnShowPreferences_Click(sender As Object, e As EventArgs) Handles btnShowPreferences.Click
Using _frm2 As New Form2()
'if we clicked save on the form then show the values in the
'controls that we want to
If _frm2.ShowDialog() = Windows.Forms.DialogResult.OK Then
txtFirstName.Text = _frm2._Preferences.FirstName
txtLastName.Text = _frm2._Preferences.LastName
End If
End Using
End Sub
End Class
Here is an example (Preferences) class
This class would hold all your properties for the preferences. This is an example, you can change anything you need to suit your needs.
Option Strict On
Public Class Preferences
#Region "Properties"
Public Property FirstName As String
Public Property LastName As String
#End Region
Public Sub New()
End Sub
End Class
The second Form could be your (Preference) form with all the controls a user would need to interact with.
Public Class Form2
Public _Preferences As New Preferences 'create class variable you can use for later to store data
Private Sub btnSave_Click(sender As Object, e As EventArgs) Handles btnSave.Click
'set your properties of the class from your form. this will then hold everything you can get from
'the first form...
With _Preferences
.FirstName = txtFirstName.Text
.LastName = txtLastName.Text
End With
Me.DialogResult = Windows.Forms.DialogResult.OK 'this is used to determine if user clicked a save button...
End Sub
End Class
I hope this get's you started, if you do not understand something please let me know.
To directly answer your question, the main difference in your two code samples is that the second uses ShowDialog to open the form modally, vs the first sample which lets you interact with the parent form while the second is open.
The second approach may be better from the view of user flow control. If your real question is whether to push data back to the main form or pull data from the dialog, it is probably better to pull from the dialog. This approach makes the dialog reusable from other forms.

binding list(of class) to datagridview in vb.net

I have a class that contains a list of data that gets populated from a datastream. I want to bind that list to a datagridview so it automatically populates as the list does. However the list isn't refreshing as the data comes in. I have something like this.
Public class MyClass
Private mData as list(Of networkData)
Public Property Data() as list(Of networkData)
Get
return mData
End Get
Set
mData = value
End Set
End Property
' some other properties that aren't imporant
' stuff to load Data with data from network stream
end class
Public class networkDat
Private rawdata as string
Public Property rawdata() as string
Get
return mrawdata
End Get
Set
mrawData = value
End Set
End Property
' some other properties that aren't imporant
' functions to parse rawdata into the other properties
End Class
'form
Public Class dataviewer
Dim dataView as datagridViewer = new datagridviewer()
Private Sub dataviewer_load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
dim x as myClass = new myClass() 'new will start the datastream
datagridview.datasource = x.Data
End Sub
Since I started the datastream first dataviewer will have an inital set of data. However it doesn't update as new data comes in.
The List class does not implement the IBindingList interface which supports list change notifications.
Try using the BindingList class instead:
Provides a generic collection that supports data binding.
To observe changes to the properties in your class, the class would need to implement the INotifyPropertyChanged interface:
Notifies clients that a property value has changed.
List(of ) does not support change notifications. For your purpose, change the List(of ) to a ObservableCollection(of ).

Saving changed values of components on FormClosing

In my application I have a TabControl with some tabs. Each tab contains many components. If the application is closing I want to check If a value of any component has changed. If so, I will ask a user if he wants to save it or not.
I want to know how you solve this situation (because it is standard behaving when application is closing). I thought that I have some flag (bool) and I set an event ValueChanged to each component. If the method handlings this event is fired, this flag is set to true. In case of closing application I will only check if flag is true.
But the problem is that there is more than 30 components and create method handling the event to each component it seems not efectve to me.
You're on the right track wit the boolean flag and the ValueChanged event. As far as I'm aware, that's really the only way to deal with this kind of thing. To accomplish this you could simply writing the handling for the event once and then copy and paste the component over and over as needed.
However, to your question of effectiveness when spread across 30 components, you should consider rolling your own component(s) that inherit from a basal class which exposes an IsDirty property or similar. When closing you could then loop through all controls on your tabs to see any have IsDirty set to true.
Unfortunately, given that you've already created the interface, neither approach will solve your current dilemma.
When you fill in the text/value for each control, also fill in the TAG with the same value. Then you can compare the TAG against the text/value for each control to see if anything changed.
To avoid having to write code for each control (when checking) you cah do a for loop on each control in [Tab Page Name].Controls().
Another way to do this is to extend each control by adding a IsDirty property and overriding the validation event. Then you can set this if it changed. You might also want to have a method to RESET the IsDirty property.
Yet another way, I always bind to a class, it just makes my code less error-prone and gives me intellisense. Also gives you tons of features that you can easily throw in like this. Then just BIND to your custom class.
Here is an example of how I would do this using a custom class. This is just an example of reading a csv and writting it but the key, to answer your question, is in the "Dirty" code.
Imports System.ComponentModel
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
'On form load, open the file and read the data
Using SR As New System.IO.StreamReader("Test.csv")
Do While SR.Peek >= 0
'Put each line into it's own instance of the class
BindingSource1.Add(New MyData(SR.ReadLine))
Loop
End Using
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
'When closing, see if any data has been changed and ask to save
Dim bDirty As Boolean = False
For Each dat As MyData In BindingSource1
If dat.IsDirty Then
bDirty = True
Exit For
End If
Next
If bDirty Then
'Example code for saving
Select Case MessageBox.Show("Do you want to save your changes?", "Save Changes", MessageBoxButtons.YesNoCancel)
Case Windows.Forms.DialogResult.Cancel
e.Cancel = True
Case Windows.Forms.DialogResult.Yes
'Here you should remove the old file, I like to rename it to BAK,
' save the new file, then you can get rid of it.
' Just in case there is a problem saving.
If System.IO.File.Exists("Test.csv") Then System.IO.File.Delete("Test.csv")
Using SW As New System.IO.StreamWriter("Test.csv", False)
For Each dat As MyData In BindingSource1
SW.WriteLine(dat)
Next
End Using
End Select
End If
End Sub
End Class
Public Class MyData
Implements INotifyPropertyChanged
'Event that implements INotifyPropertyChanged. This tells the binding to refresh a property in the UI.
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Public Sub New(ByVal SomeDataToLoad As String)
'Take you data and parse it or whatever into the various properties
'This example uses a comma-seperated string
Dim sWords As String() = SomeDataToLoad.Split(",")
_FirstName = sWords(0)
_LastName = sWords(1)
End Sub
''' <param name="PropertyName">Case-Sensative property name</param>
Public Sub ForcePropertyChanged(ByVal PropertyName As String)
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(PropertyName))
End Sub
Private _IsDirty As Boolean
Public ReadOnly Property IsDirty As Boolean
Get
Return _IsDirty
End Get
End Property
''' <summary>Override the ToString method for getting the data back out, in this case as comma seperated again. You can then write this to file or whatever.</summary>
Public Overrides Function ToString() As String
Return FirstName & "," & LastName
End Function
'--Properties you can bind to------------------------------------------------
Private _FirstName As String
Public Property FirstName As String
Get
Return _FirstName
End Get
Set(value As String)
_FirstName = value
_IsDirty = True
ForcePropertyChanged("FirstName")
End Set
End Property
Private _LastName As String
Public Property LastName As String
Get
Return _LastName
End Get
Set(value As String)
_LastName = value
_IsDirty = True
ForcePropertyChanged("LastName")
End Set
End Property
End Class
I did not go into how to bind here, you can find that all over the web, but I did put the data in a BindingSource so the rest will be easy. Notice that when the form is closing, I can loop through EVERY record to see if there were changes, easily. If you only had a single record, you wouldn't even have to loop, just ask if it is dirty.