Lesson 5 - Storing objects in CSV format in C# .NET, part 2
In the previous lesson, Storing objects in the CSV format in C#, we started coding an application for storing users in CSV files. In today's tutorial, we're going to finish and improve this application.
Loading users from the CSV file
Now that saving works, all that's left for us to do is to load the data back into the application. We'll read the file line by line, split each line using the Split() method, and then add an object with those values to the collection. We'll empty the collection before loading, so as to get rid of the users loaded from before (in case we extend the application in the future).
public void Load() { users.Clear(); // Opens the file for reading using (StreamReader sr = new StreamReader(file)) { string s; // Reads it line by line while ((s = sr.ReadLine()) != null) { // splits the string line using semicolons string[] values = s.Split(';'); string name = values[0]; int age = int.Parse(values[1]); DateTime registered = DateTime.Parse(values[2]); // adds a new user with those values AddUser(name, age, registered); } } }
The database class is now complete. Let's focus on the form part now.
The application presentation layer
First, we'll prepare new form controls (from the ToolBox). We'll add a Load
button, then a userListBox ListBox with the Sorted
property set to
true
. Then, we'll add a TextBox to enter a new user's name, a
NumericUpDown for their age, and a DateTimePicker for their registration date.
We'll set the shorter format to it. We'll also add some labels to the controls.
We can group these controls using GroupBox. In another GroupBox, we'll add three
labels for the user's details and name them nameLabel, ageLabel, and
registeredLabel. We'll add 3 more labels to describe those as well. Finally,
we'll add a button for adding a new user. We can make the application more
user-friendly by adding a PictureBox with an icon. If this was all too much
information for you, don't worry, here's a picture of the final form to help you
get situated:
In a real application, adding users would probably be done in an entirely separate dialog. However, a single form is just fine for our purposes.
Let's remove the test users creation part from the Save button handler. Next,
we'll add saving in a try-catch block. We already know that the
finally
keyword (the using
block in our database)
doesn't actually catch exceptions, which is what we want. Therefore, we'll react
to them in the presentation layer (in the form), where we're supposed to. Error
notifications, i.e. communicating with the user, would be done incorrectly if
placed in the Database class. We'll pull up a MessageBox if we catch an
exception. With all of that in mind, the button's handling method will look like
this:
try { database.Save(); } catch { MessageBox.Show("Unable to save the database, check the file's access privileges.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); }
We'll click a handler for the Load button in a similar manner, i.e. we'll load objects into the ListBox as well once they've loaded from the database. We'll clear the ListBox to avoid mixing the loaded users with the previous contents. In real applications, the loading would probably be performed automatically when the application starts. However, we'll leave that to the buttons for the better control. The Load button method now looks as follows:
try { database.Load(); userListBox.Items.Clear(); userListBox.Items.AddRange(database.ReturnAll()); } catch { MessageBox.Show("Unable to load the database. The file might not exist.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); }
Now let's handle clicking events for the userListBox, which will display the user details to the labels that we've prepared:
if (userListBox.SelectedItem != null) { User u = (User)userListBox.SelectedItem; nameLabel.Text = u.Name; ageLabel.Text = u.Age.ToString(); registeredLabel.Text = u.Registered.ToShortDateString(); }
We added a condition in case no user is selected (the listBox would be empty). The type casting of the selected item to the User data type is also worth mentioning. Since ListBox isn't a generic collection, C# has no idea what to do about the item types. Feel free to test it all out to make sure that it's all working properly.
There is one last button without a handler method and that's the one used to add new users. We'll click it and simply add a user. However, we'll have to add the item to both the ListBox and the database. In more complex applications, we'd use data binding, but we won't so as to keep things simple (see the Windows forms application course if you're interested in more information about it).
private void addButton_Click(object sender, EventArgs e) { string name = fullNameTextBox.Text; int age = Convert.ToInt32(ageNumericUpDown.Value); DateTime registered = registeredDateTimePicker.Value; database.AddUser(name, age, registered); userListBox.Items.Add(new User(name, age, registered)); }
Let's try to add a new user:
We could implement removing users in a very similar fashion, but I'll leave
this up to you. The last thing that's left for us to do is to sanitize the file
path that leads to the AppData folder (not the application's folder). We already
know how to do it thanks to the Introduction to working
with folders lesson. We could also clear the labels' text at the application
start. We'll do it all in the form's constructor (don't forget to add
using System.IO;
).
public Form1() { InitializeComponent(); // clears the user detail labels nameLabel.Text = ""; ageLabel.Text = ""; registeredLabel.Text = ""; // creates an application folder in AppData string path = ""; try { path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "UserDatabase"); if (!Directory.Exists(path)) Directory.CreateDirectory(path); } catch { MessageBox.Show("Unable to create folder " + path + ", check your user privileges.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } // creates a database database = new Database(Path.Combine(path, "users.csv")); }
That's it!
Our application is almost done. Let's think for a bit about what would happen if someone wrote a semicolon in their name. As you may have guessed, the application would break. That's why we'll remove semicolons from the name in the Save() method. If we needed to store semicolons in our application (which isn't a very common thing), we could come up with another separator. If we wanted things to be perfect, we'd add the value with the semicolon in quotes. However, the Split() method will no longer suffice for parsing. If you're interested in parsing CSVs like this, take a look at the Microsoft.VisualBasic.FileIO.TextFieldParser class. Of course, we could also solve this problem by using a different file format. In this case, we'll simply remove semicolons. More accurately, we'll replace them with spaces by changing a single line in the Save() method:
string[] values = { u.Name.Replace(';',' '), u.Age.ToString(), u.Registered.ToShortDateString() };
We're now done. If you had any problems, the finished project is attached below, as always. In the next lesson, Introduction to XML and writing via the SAX approach, we'll introduce the XML format.
Download
By downloading the following file, you agree to the license termsDownloaded 709x (91.19 kB)