Lesson 7 - Birthday reminder in C# .NET WPF - Logic layer
In the previous lesson, Birthday reminder in C# .NET WPF - Designing windows, we designed the forms needed for our application in WPF. In today's tutorial, we're going to talk about logic layer design and create C# classes that will carry the application logic.
Person
Our application is based on people's birthdays, so they will need a class all
to themselves. Make sure you add the public
modifier before the
class name.
Properties
Each person will have four properties:
- name (string Name)
- birthday (DateTime Birthday)
- age (int Age)
- number of days remaining until their next birthday (int RemainingDays)
The first two properties are simple:
public string Name { get; set; } public DateTime Birthday { get; set; }
We'll set these properties using a parametrized constructor. After which, the class will look something like this:
public class Person { public string Name { get; set; } public DateTime Birthday { get; set; } public Person(string name, DateTime birthday) { Name = name; Birthday = birthday; } }
The other two properties will contain additional logic, and will be displayed on the form using bindings.
Age
The Age property calculates and returns a person's current age. You can't just subtract two dates and expect to get the right answer. A TimeSpan cannot determine the number of years, only a number of days, remember? We will perform the age calculation as follows:
- Get the current date (excluding time) using DateTime.Today.
- Calculate the age as by subtracting the years from the current date and the date of birth. Which will not give an accurate result. If you were born on February 1, 1990, and now it's January 1, 2010, it is not 20 years, but only 19. Which is why there are more steps to the calculation.
- For the cases where the current date is lower (earlier) than the date of birth after adding the years up, we will subtract a year from the age.
- Return the final age.
The Age property code will be as follows:
public int Age { get { DateTime today = DateTime.Today; int age = today.Year - Birthday.Year; if (today < Birthday.AddYears(age)) age--; return age; } }
RemainingDays
The RemainingDays property returns how many days are left until the person's birthday. To do so we will have to include the following steps:
- Get the current date (without time).
- Get the date the birthday lands on by adding the age + 1 and the person's date of birth.
- Subtract the dates and return the difference in days. Since the difference returns a double, we will have to convert it to an int.
public int RemainingDays { get { DateTime today = DateTime.Today; DateTime nextBirthday = Birthday.AddYears(Age + 1); TimeSpan difference = nextBirthday - DateTime.Today; return Convert.ToInt32(difference.TotalDays); } }
ToString()
The people will be listed, so overriding the class' ToString() method to make it return the person's name will prove to be very useful:
public override string ToString() { return Name; }
Person manager
Another logical component we will have to add to the application is the "person manager". The class will be able to add, remove and save a list of people into a file and load it afterward. Also, it'll be able to find the person that has the nearest upcoming birthday.
Add a PersonManager class to the project and make it public.
Properties and attributes
The person manager will have three public properties.
The first is a list of people of the ObservableCollection type. An ObservableCollection is a more efficient sort of List that can raise a change event when its contents. Thanks to this mechanism, all of the form controls that have an ObservableCollection set as their data source would then refresh automatically. Which is great because it would be very confusing to refresh dozens of form controls manually when something is altered externally. Once we add a new person to our application, it will immediately be visible in the person list and will refresh automatically. In this case, we will initialize the ObservableCollection() in the constructor.
If we wanted to be able to edit people, the Person class would have to implement the INotifyPropertyChanged interface. Any change, e.g. the person's Name, would then automatically be propagated to all of the form controls where this person is used. However, we're going to keep it simple for now, and not add that sort of feature.
After adding all of the content stated above, the PersonManager class will look something like this:
public class PersonManager { public ObservableCollection<Person> Persons { get; set; } public PersonManager() { Persons = new ObservableCollection<Person>(); } }
The rest of the necessary properties are:
- TodaysDate that returns the current date.
- NearestPerson that returns the person with the upcoming birthday.
Whose code will look like this:
public Person NearestPerson { get; set; } public DateTime TodaysDate { get { return DateTime.Now; } }
Methods
Other than adding and removing people, the class will also be able to find the person with the nearest upcoming birthday. We'll cover loading/saving people into/from a file later on.
FindNearest()
The FindNearest() method finds and stores the person who has the nearest
upcoming birthday. To find that person in the list we'll use the LINQ OrderBy()
method which will sort people based on how many days are left until their
birthday. We'll store the result into a collection and use the var
keyword instead of specifying the data type of the collection (as is a common
practice in LINQ). Then, we'll return the first person that is found. The method
should only be called when the list is not empty. We will also set the method as
private since we would only call it from within the class.
private void FindNearest() { var sortedPersons = Persons.OrderBy(o => o.RemainingDays); if (sortedPersons.Count() > 0) NearestPerson = sortedPersons.First(); else NearestPerson = null; }
Add()
The Add() method adds a new person to the ObservableCollection. Since the person will be added from the form, we will make the method take person properties as parameters and create a new instance on using the data provided by the user. We will only store the date from the date of birth (exclude time).
Before adding a person to the list, we will have to check whether the name is too short or the date selected has not passed. In any of these cases, we'll throw an exception. Exceptions are the only proper way to handle errors in object-oriented applications. If you haven't met them yet, just know that an exception is done using the throw keyword followed by an exception instance. There are several types of exceptions (you could even make your own). In our case, we'll throw an ArgumentException (attributed to argument errors). We'll enter the error message in the exception constructor. Once an exception is thrown, a method is terminated. We will get to reacting to the exception once we call the method from the form.
A date entered by a DatePicker form control is of the DateTime? type. If you went through the object-oriented course up until the end, you know that a question mark refers to the nullable type. A nullable type is an extension of the value data type makes it so it can contain the null value (which is normally not allowed). If the date is null, a date has not been entered and we will have to throw an exception. The value of a nullable type can be accessed through the Value property.
public void Add(string name, DateTime? dateOfBirth) { if (name.Length < 3) throw new ArgumentException("Name is too short"); if (dateOfBirth == null) throw new ArgumentException("Date of birth wasn't entered"); if (dateOfBirth.Value.Date > DateTime.Today) throw new ArgumentException("Date of birth can't be in the future"); Person person = new Person(name, dateOfBirth.Value.Date); Persons.Add(person); FindNearest(); }
At the end of the method, we run the FindNearest() method since it could be the one that was just added.
Remove()
The Remove() method removes a person from the ObservableCollection. Since all this method will do is remove a person instance, the method will take a Person as a parameter. Once the person has been removed, we will run the FindNearest() method once again.
public void Remove(Person person) { Persons.Remove(person); FindNearest(); }
In the next lesson, Birthday reminder in C# .NET WPF - Wiring layers, we'll continue with our application and make it runnable.
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 70x (449.95 kB)
Application includes source codes in language C#