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
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.