Welcome to the third part of a four-part series that explains WPF through an example. We're now deep into the process of building our to-do list application. We need to make it possible for someone using the application to check off tasks when they are done, and add new tasks. Keep reading to learn how we do this.
So, we have a CheckBox that needs to be checked or unchecked depending on whether the task is done or not done, and we have a TextBlock that needs to be colored according to the task's priority. The CheckBox contains a property called IsChecked, which is a boolean indicating whether or not the box is checked, but the XML simply contains a "Yes" or "No" string. Similarly, the TextBlock contains a Foreground property to set the color, but priority in the XML file is indicated by "Low," "Medium" or "High." We're going to have to convert the values in the XML to the correct property values, and, for the CheckBox, we need a way to convert the value of IsChecked back into a string for when the user marks a task as done.
Conversion is done by creating a class that implements the IValueConverter interface and providing the proper code for conversion between the data source and the appropriate controls. Fortunately, this interface isn't very complicated, and not a lot of code will be required. The interface calls for two methods: Convert and ConvertBack. The Convert method is called when a value is taken from the data source, and the ConvertBack method is called when a value needs to be put back into the data source.
Let's create a converter class for the CheckBox first. Create a new class in Visual Studio called StatusConverter. Create the class, implementing IValueConverter:
Next, we need to implement the Convert method. The Convert method takes several parameters, but only the first one is important here, since it contains the actual value that we need to convert. Remember that we'll be accepting a "Yes" or "No" string from the XML file and will need to return a boolean. This conversion can be done in a single step:
publicobject Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((string)value).ToLower() == "yes";
}
Above, the incoming string is converted to lower case. That way, the content of the Status element can be case insensitive.
Converting the boolean back into a string is also very simple:
publicobject ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
Now the converter for status is done, and it's time to move on to the converter for priority. The converter needs to convert the priority into an appropriate color name: red for high priority, orange for medium priority, and green for low priority. The process is exactly the same as before, only there's no need to be able to convert back (since there's no way to convert back from within the application-the color is static). Create a class named PriorityConverter:
publicobject Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string rawPriority = ((string)value).ToLower();
if (rawPriority == "high")
{
return"Red";
}
elseif (rawPriority == "medium")
{
return"Orange";
}
else
{
return"Green";
}
}
publicobject ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
thrownewNotImplementedException();
}
}
}
So, the application contains some two classes that were coded in, but that code now needs to be referenced in the XAML. Fortunately, however, the intermediate work involved in making this possible is very simple. First, the WpfToDo namespace, which contains the two classes, needs to be worked into the XAML. Place the following attribute in the Window tag:
xmlns:local="clr-namespace:WpfToDo"
You'll find several similar attributes already there. Place the new one right under them.
Next, instances of each of the classes need to be placed in the Resources section, just like the XmlDataProvider:
The final step is very simple. The binding needs to be set up, along with references to the two converters. Modify the existing tags, adding the appropriate attributes:
We now need to create a way to add tasks to the list. The easiest way to do this is to create an "Add Task" dialog that's shown when the user clicks the "Add Task" button. The dialog will prompt the user for the required information (title, priority and description), and when the "Okay" button is clicked, the new task will be added to the list.
The dialog will require a new window. Add a new Window (WPF) to the project called AddTaskDialog.xaml. Visual Studio will then create the basic XAML for the window. The default height of the new window is too big for our needs, so we'll need to decrease it a bit. The title should also be changed to something more appropriate:
Next, the dialog needs some controls. For the title, a TextBox control will do; for the priority, a ComboBox control will do; and for the description, a TextBox control with wrapping will do. These input controls will need labels. Two buttons will also be necessary, an "Okay" button and a "Cancel" button.
It's best that the controls be organized into a grid of five rows and three columns in order to achieve a look like this (the grid lines are shown by setting the ShowGridLines attribute of Grid to True):
I'll go ahead and place all the required XAML below. Most of it should be familiar to you, and so I'll only explain a few parts in bold:
The ComboBox control is pretty simple to understand, but it's nonetheless new and merits being pointed out. Notice how the priorities are colored as they are in the task list itself.
In order to wrap text in the description TextBox, the TextWrapping attribute is set to Wrap.
The IsCancel attribute of the cancel button is set to True. This way, if the user presses escape, the button will automatically be "clicked."
Next, the dialog needs to respond to the okay button being clicked. If the button is clicked, then the window's DialogResult property must be set to true. This will close the dialog and return true (as described below). Either double click the button in the designer or add an event handler through XAML:
Now that the dialog is set up, it's time to make it all work. This is actually really simple. Back in Window1, either double click the "Add Task" button in the designer to create an event handler, or add it manually through XAML:
In the event handler, we need to create an instance of AddTaskDialog and call it as a dialog using the ShowDialog method. This will return a boolean value like a normal dialog does. If True is returned (that is, if the user gives the application the okay to actually add a new task), then a task needs to be added to the ListBox.
To do this, we need to convert all of the data into an XML element (represented by the XmlElement class) and stick it into the XmlDataProvider. Open up the code-behind for Window1 (Window1.xaml.cs). We're going to be using various classes in the System.Xml namespace, so go ahead and add a using directive:
using System.Xml;
Most of the code relates to XML rather than WPF, so there's no need to explain anything, except for one method in bold:
The FindResource method does as its name implies: it obtains a reference to a resource defined in XAML. Note that the result must then be cast into the proper type.
Next week, we will finish building our application. You won't want to miss it.