Lesson 5 - Birthday Reminder - Wiring the Presentation and Logic Layers
In the previous lesson, Birthday Reminder - Logic Layer, we programmed most of the logical layer of the birthday reminder application. In today's tutorial, we're going to make the application up and running.
Separating Presentation and Logic
Now we have completed the presentation part of the application (forms) and the logic part (classes). We strictly separate these 2 layers in all applications, otherwise the code would be very confusing. You should never perform calculations, file manipulations, database queries, and similar things directly in the form code! We always create a class that provides the appropriate methods, and only use this class from the form. Logic stays in the class. The class shouldn't know about the form at all. For example, it shouldn't display error messages, but only throw exceptions in case of error. It's then the form's responsibility to display the error to the user. The form is part of the application that communicates with the user, no other part does that.
If you're now thinking that our simple calculator we created in the first lessons of this course was poorly designed, you are right. To keep things simple, we wrote the calculations straight into the button handler method. With proper design, we'd have a class that calculates the results and we'd call it from the form.
So today we'll take a look at how it's done right.
Wiring Presentation and Logic
We'll go to the source code of the OverviewForm
and add a
private field of the PersonManager
type. We'll also create an
instance in the declaration:
private PersonManager personManager = new PersonManager();
The manager instance will be created right after the form is created, then the form will communicate with it to perform actions the user wants.
In the form constructor, we'll set the todayLabel
to the current
date, and set the DataSource
property of the ListBox
to the People
BindingList
of the person manager. This
binds the ListBox
to the BindingList
, so from now on,
it's going to display its contents, and if something is added into the list,
it's going to be reflected in the ListBox
too.
public OverviewForm()
{
InitializeComponent();
todayLabel.Text = DateTime.Now.ToLongDateString();
peopleListBox.DataSource = personManager.People;
}
Adding and Removing People
To finally see something, let's move to adding people. First, we'll go to the
code of the PersonForm
form. As in the previous form, we'll prepare
a person manager as a private field here. But if we created its instance again
here, it wouldn't be very useful, since people would be loaded in the manager
instance of the main form, and loading them again here would be inefficient. So
we'll pass the already loaded manager through the constructor and store it in
the prepared variable:
private PersonManager personManager; public PersonForm(PersonManager personManager) { InitializeComponent(); this.personManager = personManager; }
Now we'll double-click the OK button, and add a new person to the manager
using the values the user entered into each control. We access the value of a
TextBox
using the Text
property, and the value of a
DateTimePicker
using the Value
property.
You certainly remember that the Add()
method is going to throw
an exception in case of very short name or a future date. Therefore, we'll put
the code adding the person in a try
block, followed by a
catch
block. If an exception occurs inside the try
block, the program immediately moves into the catch
block where it
uses MessageBox
to display the error message. If we didn't handle
the exception this way, it'd cause the application to crash.
private void okButton_Click(object sender, EventArgs e) { try { personManager.Add(nameTextBox.Text, birthdayDateTimePicker.Value); Close(); } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } }
We access the exception message via the Message
property. Notice
that we also set the message the exclamation mark icon. We don't place any logic
in the method, so it's relatively short.
We've finished this form. Let's go back to the OverviewForm
designer, then double-click the addButton
and the
removeButton
.
private void addButton_Click(object sender, EventArgs e) { PersonForm personForm = new PersonForm(personManager); personForm.ShowDialog(); }
In the event handler of the addButton
, we create a new instance
of the PersonForm
form and pass it the local person manager. We
then call the ShowDialog()
method on the instance. This will
display the new form (just like the Show()
method) and will also
block the rest of the application until the dialog is closed. As the result, we
aren't able to work with the main form until we confirm or close the dialog.
This is how dialogs (helper forms mostly used for entering data) usually work.
In our case we actually wouldn't mind if the user was using the app while
entering a new person and, for example, opened another input dialog.
The event handler of the removeButton
will look like this:
private void removeButton_Click(object sender, EventArgs e) { if (peopleListBox.SelectedItem != null) { personManager.Remove((Person)peopleListBox.SelectedItem); } }
What's important is the condition that checks whether an item is selected in
the ListBox
. As you can see, we access the selected item using the
SelectedItem
property. The item is then converted to the
Person
type, because it's of the object
type by
default (that makes the ListBox
universal). We pass this person to
the Remove()
method of the manager, which then performs the
physical removal from the collection.
You can now test the app and add or remove people. Added people will appear
immediately in the ListBox
, thanks to the bindings.
ListBox
always displays what the ToString()
method of
the objects returns. For the people, it shows their name. If we wanted to
display another property, we'd specify the property name in the
DisplayMember
property of the ListBox
(e.g. to display
birthdays, we'd set it to Birthday
). Of course, other controls work
similarly, like the ComboBox
for instance.
Nearest birthday
In the main form class, we'll add a new private helper method
RefreshNearest()
to refresh the label showing the nearest
birthday.
private void RefreshNearest() { if (personManager.People.Count > 0) { Person nearest = personManager.FindNearest(); int age = nearest.CalculateAge(); if (DateTime.Today != nearest.Birthday) age++; nearestLabel.Text = nearest.Name + " (" + age + " years old) in " + nearest.RemainingDays() + " days"; } else nearestLabel.Text = "No people in list"; }
If there are people in the person manager, we find the person with the nearest birthday. We get that person's age, and if their birthday isn't today, we add one to their age to make it appear as the person's future age. Then we put the person's name into the label text, together with their future age in parentheses and how many days remain until their birthday.
If there are no people in the manager, we display a message to the user in the label.
We'll call the method at the end of the form constructor and also after adding or removing a person (at the end of the button's event handler method, but still inside the conditions, of course).
Person details
Now we only need to display the details about the selected person. We'll
double-click the ListBox
and Visual Studio will generate the
SelectedIndexChanged
event handler method (triggered by change of
the selected item). Here we'll have to check again whether an item (person) is
selected. If so, we'll load it and display its properties in the appropriate
labels and set the MonthCalendar
to its birth date.
if (peopleListBox.SelectedItem != null) { Person selected = (Person)peopleListBox.SelectedItem; birthdayLabel.Text = selected.Birthday.ToLongDateString(); ageLabel.Text = selected.CalculateAge().ToString(); birthdayMonthCalendar.SelectionStart = selected.Birthday; }
You can now try the application.
In the next lesson, Birthday Reminder - Storing Data and Conclusion, we'll finish saving and loading data from / to a file. The current source code is available to download below as always. If you got stuck somewhere, try to find a mistake.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 119x (728.3 kB)
Application includes source codes in language C#