Lesson 8 - Birthday reminder in C# .NET WPF - Wiring layers
In the previous lesson, Birthday reminder in C# .NET WPF - Logic layer, we programmed most of our application's C# logic layer. In today's lesson, we're going to make the application runnable.
Separation of presentation and logic
Now that we have a complete application presentation layer (form) and a logic layer (classes). We must strictly separate these layers in the application or else the code would become very confusing otherwise. Never perform calculations, file operations, database queries or anything of the sort directly in the form code! Always create a class that provides the required methods and use it in the form. This way, the logic stays within the class. The class shouldn't even "know" that the form exists. It shouldn't display error messages (throwing exceptions is completely fine). The form is responsible for displaying error messages to the user. The form is the part of that application layer that communicates with the user. No other layer should do that.
If it crossed your mind that our simple calculator, which we made in a couple of lessons back, was incorrectly designed, you're right. For the sake of simplicity, we wrote calculations directly into button handling methods. We should have created a class that computed results and called its methods from the form. Both XAML and its code-behind are the application presentation layer. XAML defines what the form looks like, code-behind calls the logic that XAML does not contain.
That is why, today, we're going to demonstrate how to organize code correctly.
Wiring presentation and logic
Let's move to the code-behind of the MainWindow form, add a private attribute of the PersonManager type to the class, and initialize it with a new instance:
private PersonManager personManager = new PersonManager();
Now, a PersonManager instance is created when at startup and the form will communicate with it and perform actions accordingly.
Adding people
Now let's start adding people! First, we'll move to the PersonWindow form code where we'll store a PersonManager instance as a private class attribute as we did in the previous form. However, we won't create an instance here because all of the people would already be loaded in the PersonManager instance we added in the main form. In other words, we would be loading the same people twice (which is redundant and inefficient). What we'll do here, is pass our loaded PersonManager instance through a constructor and store in a variable:
private PersonManager personManager; public PersonWindow(PersonManager personManager) { InitializeComponent(); this.personManager = personManager; }
Now, double-click the OK button and add a person to the manager using the values filled-in by the user into the form controls, respectively. The DatePicker value can be accessed using the SelectedDate property which is of the nullable DateTime? type.
Remember how the Add() method throws an exception when the name is too short, the date is null, and when the date is in the future? Well, in order to complete the cycle, we will have to wrap the code by adding a person into the try block, followed by the catch block. If an exception occurs in the try block, the program immediately jumps to the catch block where, using a MessageBox, it displays the error message. If we didn't set things up like that, the application would terminate when any exception is thrown.
private void okButton_Click(object sender, RoutedEventArgs e) { try { personManager.Add(nameTextBox.Text, birthdayDatePicker.SelectedDate); Close(); } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Exclamation); } }
We use the Message property to access the exception message. Also, notice how we set the message box's icon to an exclamation mark. The method doesn't contain any actual logic, so the code for it is relatively short.
This form is now complete. Let's move back to the MainWindow, this time to the graphic designer. Double-click the addButton button and change its handling method to this:
private void addButton_Click(object sender, RoutedEventArgs e) { PersonWindow personWindow = new PersonWindow(personManager); personWindow.ShowDialog(); }
Using the addButton() method, we create a new PersonWindow form instance where we will pass the local PersonManager instance. Then, we call the ShowDialog() method on the form instance, which displays a new form (as well as the Show() method) and blocks the rest of the application until the dialog is closed. In other words, we wouldn't be able to work with the main form until we confirm or cancel the dialog. Handling dialogs this way is very common. Especially, when it comes to utility forms used for entering data. In this case, we wouldn't actually mind if the user used opened another entering dialog while entering a new person.
If we run the application now, we'd be able to add people to the ObservableCollection, but we wouldn't see them in the ListBox. Mainly because the ListBox isn't bound to the collection, which we will do using Bindings.
Binding
In order to bind objects, we have to set our window's context first. The DataContext property takes an object whose properties will be bound. Add the following code to your window's constructor:
DataContext = personManager;
The PersonManager instance is now the data context of the main window and is now able to display its properties.
Let's move back to the XAML and add the ItemsSource attribute to the ListBox:
<ListBox Name="personListBox" Grid.Column="0" Grid.Row="2" Margin="0, 0, 0, 10" ItemsSource="{Binding Persons}"/>
What we did here, is define the ListBox's source items as objects of the "Person" type in the context, i.e. the PersonManager.
Run the application and try adding some people. Every time you add someone, they will immediately appear in the ListBox thanks to the binding we have set. A ListBox always displays what the ToString() method of its items returns. In our case, their name is shown. Other controls of the sort, like ComboBoxes, work the same way.
Removing users
The removeButton's handler method will look like this:
private void removeButton_Click(object sender, RoutedEventArgs e) { if (personsListBox.SelectedItem != null) { personManager.Remove((Person)personsListBox.SelectedItem); } }
The most important part of the code above is the condition that determines
whether an item is selected in the ListBox or not. That way, we can access the
selected item through the SelectedItem property. Then we cast the item to a
Person because an instance of the Person class is of the object
type. Just so you know, the ListBox control isn't generic and has to be able to
contain objects of any type. The person is then passed to the PersonManager's
Remove() method which then physically removes the person from the
ObservableCollection.
Today's project is available for download below. I look forward to seeing you in the next lesson, Birthday reminder in C# .NET WPF - Bindings, when we'll continue working on Bindings.
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 129x (192.8 kB)
Application includes source codes in language C#