24 April 2013

Data Entry, Save and Restore in VB.NET


Data Entry / Data Storage

A requirement that appears time and again is for a simple application that accepts data input by the user, saves it, and allows the saved data to be retrieved the next time the program starts.  This simple example is a template for that task.  
Whether you are creating a simple list of a few dozen  recipes, or managing the bank accounts of thousands of customers, the processes  of entry, store and retrieve are similar.  The process requires a means for  entering the data, a means for displaying the entered data, and a data storage  structure of some type.
In this example a simple Windows form will be used for entry  and display.  User Settings, a facility provided by the .Net framework, will  be used as the data storage structure.
Create a form with three text boxes (txtName, txtCity and  txtBalance) and a button (btnOK).  These will be used for entering the  data.  Add a listbox – Listbox1.  This will be used for displaying the  data.
The data will be represented by a class.  This is a  simple class with properties that correspond to the data items, and methods  required to manipulate that data.
The data storage structure is User Settings.  This uses  XML files for storage, but all the detail is hidden from the user.  The  only thing we need to do is to ensure our data to be saved is in a form that is  compatible with one of the available user settings types.  In this case we  will use a string collection.  This user setting saves a list of strings.   We will therefore need to be able to convert our data (the class instances) back  and forth between strings.
To create the storage structure for the data, use Project  Properties / Settings and create a new setting with the name SaveList and the  type System.Collections.Specialized.StringCollection.
To convert the class instance to a string, the ToString  method is overidden.  Note that the method could be called anything, but  the default ToString method for a class is not particularly useful, so we may as  well override it and create one that matches our purpose  In this case it  has been created to serve two purposes – convert the instance to a string for  saving in the generic string collection, and to convert the instance to a string  for displaying in the list box.   In a more complex example, those two  requirements might require two separate methods.
To convert the string to a class instance we provide a  constructor – a New method that takes the string stored in the generic string  collection, and creates a new instance from the information in  the string.   In this case there is also a constructor that takes the three text box values  and creates an instance of the class – they are almost the same, and it would be  quite possible to use just one constructor.  But this example demonstrates  how you can have separate constructors for separate purposes.
Note the connection between the ToString method and  the special constructor – those methods must be exact opposites, so  that the instance converted to string in the ToString method is exactly  re-created when it is converted back from string. It is preferable if this  connection is establshed by methods in the class, although obviously code in the  main form could achieve the same result.
If our class had other variables that were not being saved,  then they would be be initialised in the constructor when the string data is  processed.
The list of data is updated whenever the list changes.   This may seem wasteful – rewriting the whole list just because a new item has  been added – but the convenience easily outweighs any extra processing that is  involved.  This becomes particularly obvious if the example is extended -  for instance to allow items to be deleted from the list. Firstly, we know that  the position of each item in the listbox exactly corresponds to the index of  that item in the list – they can never get out of step.  Secondly, we do  not need to manipulate the listbox to remove the deleted item - we adjust the list  (the underlying data storage), and simply re-display the listbox.  Similar considerations would apply if we added an edit procedure – simply redisplay the list from the data source and the edited infoarmtion appears without further work.
To create the example, start a new project named SaveData,  add the 5 controls listed above, create the user setting (SaveList), and paste this code.
Public Class Form1
    ' Demonstrates:
    ' Simple data entry, including numeric validation
    ' Store and retrieve.

    Dim Customers As List(Of Customer) = New List(Of Customer)

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        If My.Settings.SaveList Is Nothing Then My.Settings.SaveList = New System.Collections.Specialized.StringCollection
        For Each S As String In My.Settings.SaveList
            Customers.Add(New Customer(S))
        Next
        ListRefresh()
    End Sub

    Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        My.Settings.SaveList.Clear()
        For Each C As Customer In Customers
            My.Settings.SaveList.Add(C.ToString)
        Next
    End Sub

    Private Sub btnOK_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOK.Click
        Dim D As Decimal
        If Not Decimal.TryParse(txtBalance.Text, D) Then
            MsgBox("Balance is invalid - please re-enter")
            Exit Sub
        End If
        Customers.Add(New Customer(txtName.Text, txtCity.Text, txtBalance.Text))
        ListRefresh()
    End Sub

    Private Sub ListRefresh()
        ListBox1.Items.Clear()
        For Each C As Customer In Customers
            ListBox1.Items.Add(C.ToString)
        Next
    End Sub

End Class

''' <summary>
''' Customer
''' </summary>
''' <remarks>Demonstration class used to show how a ToString and New can
''' be used in conjunction for storing/retrieving</remarks>
Public Class Customer
    Private CustName As String
    Private CustCity As String
    Private CustBalance As Decimal
    Property LastName() As String
        Get
            Return CustName
        End Get
        Set(ByVal value As String)
            CustName = value
        End Set
    End Property
    Property City() As String
        Get
            Return CustCity
        End Get
        Set(ByVal value As String)
            CustCity = value
        End Set
    End Property
    Property Balance() As Decimal
        Get
            Return CustBalance
        End Get
        Set(ByVal value As Decimal)
            CustBalance = value
        End Set
    End Property
    ''' <summary>
    ''' The constructor used for user data entry''' 
    ''' </summary>
    ''' <param name="Name">Customer name</param>
    ''' <param name="City">Customer City</param>
    ''' <param name="Balance">Customer Account Balance</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal Name As String, ByVal City As String, ByVal Balance As String)
        CustName = Name
        CustCity = City
        CustBalance = CDec(Balance)
    End Sub
    ''' <summary>
    ''' The constructor used for the data retrieved from the stored list.
    ''' </summary>
    ''' <param name="Data">Tab-separated string of Name, City, Balance</param>
    ''' <remarks>This load sequence MUST match the sequence used 
    ''' in the ToString method.</remarks>
    Public Sub New(ByVal Data As String)
        Dim CustData As String() = Data.Split(vbTab)
        CustName = CustData(0)
        CustCity = CustData(1)
        CustBalance = CDec(CustData(2))
    End Sub
    ''' <summary>
    ''' Override of ToString to create a string form of the class properties 
    ''' </summary>
    ''' <returns>Tab-separated string of Name, City, Balance</returns>
    ''' <remarks>Used to create the string for the stored list.  Can also
    ''' be used for a class instance display, eg when debugging.</remarks>
    Public Overrides Function ToString() As String
        Return CustName & vbTab & CustCity & vbTab & CStr(CustBalance)
    End Function

End Class
Notes:
1. Even though this is a very simple example, it demonstrates the concept of layering – an important concept in Object Oriented Programming.  The layers implemented in this application are the data layer (the List), The GUI layer (user input and display) and the data storage layer (User Settings).   Although they are all part of one simple application, there is sufficient separation so that changing one part of the application (for instance, using a database instead of User Settings for data storage) could be implemented without affecting other parts of the code.
2. The methods and properties of the class are documented using the faciltities provided by the IDE editor (”’ entered on the line before the item to be documented).  This appears to be overkill for such a simple example, but in fact pays off in simplifying the documentation process, while also providig Intellisense for the user-defined objects.
3. The first logical expansion of this example is to provide a delete function, in which the user highlights one item in the list and then selects a Delete button.
4. Using ToString and New to convert to/from a form that can be stored as a User Setting could be regarded as a small cheat.  There are procedures available where the class can be made into the type of object that can be saved to User Settings without this explicit conversion.  This is called ‘serialization’.  It is effectively the same as the ToString/New technique, but implemented in a way that is standard throughout the .Net framework.

NYC Unveils First Electric Taxi Cabs