CHAPTER 29
WPF Notifications, Validations, Commands, and MVVM
This chapter will conclude your investigation of the WPF programming model by covering the capabilities that support the Model-View-ViewModel (MVVM) pattern. The first section covers the Model-View- ViewModel pattern. Next, you learn about the WPF notification system and its implementation of the Observable pattern through observable models and observable collections. Having the data in the UI accurately portray the current state of the data automatically improves the user experience significantly and reduces the manual coding required in older technologies (such as WinForms) to achieve the same result.
Building on the Observable pattern, you will examine the mechanisms to add validation into your application. Validation is a vital part of any application—not only letting the user know that something is wrong but also letting them know what is wrong. To inform the user what the error is, you will also learn how to incorporate validation into the view markup.
Next, you will take a deeper dive into the WPF command system and create custom commands to encapsulate program logic, much as you did in Chapter 25 with the built-in commands. There are several advantages to creating custom commands, including (but not limited to) enabling code reuse, logic encapsulation, and separation of concerns.
Finally, you will bring all of this together in a sample MVVM application.
Introducing Model-View-ViewModel
Before you dive into notifications, validations, and commands in WPF, it would be good to understand the end goal of this chapter, which is the Model-View-ViewModel pattern (MVVM). Derived from Martin
Fowler’s Presentation Model pattern, MVVM leverages XAML-specific capabilities, discussed in this chapter, to make your WPF development faster and cleaner. The name itself describes the main components of the pattern: model, view, view model.
The Model
The model is the object representation of your data. In MVVM, models are conceptually the same as the models from your data access layer (DAL). Sometimes they are the same physical class, but there is no requirement for this. As you read this chapter, you will learn how to decide whether you can use your DAL models or whether you need to create new ones.
Models typically take advantage of the built-in (or custom) validations through data annotations and the INotifyDataErrorInfo interface and are configured as observable to tie into the WPF notification system. You will see all of this later in this chapter.
© Andrew Troelsen, Phil Japikse 2022
A. Troelsen and P. Japikse, Pro C# 10 with .NET 6, https://doi.org/10.1007/978-1-4842-7869-7_29
1273
The View
The view is the UI of the application, and it is designed to be very lightweight. Think of the menu board at a drive-thru restaurant. The board displays menu items and prices, and it has a mechanism so the user can communicate with the back-end systems. However, there isn’t any intelligence built into the board, unless it is specifically user interface logic, such as turning on the lights if it gets dark.
MVVM views should be developed with the same goals in mind. Any intelligence should be built into the application elsewhere. The only code in the code-behind file (e.g., MainWindow.xaml.cs) should be directly related to manipulating the UI. It should not be based on business rules or anything that needs to be persisted for future use. While not a main goal of MVVM, well-developed MVVM applications typically have very little code in the code-behind.
The View Model
In WPF and other XAML technologies, the view model serves two purposes.
•The view model provides a single stop for all the data needed by the view. This doesn’t mean the view model is responsible for getting the actual data; instead, it is merely a transport mechanism to move the data from the data store to the view. Usually, there is a one-to-one correlation between views and view models, but architectural differences exist, and your mileage may vary.
•The second job is to act as the controller for the view. Just like the menu board, the view model takes direction from the user and relays that call to the relevant code to make sure the proper actions are taken. Quite often this code is in the form of custom commands.
Anemic Models or Anemic View Models
In the early days of WPF, when developers were still working out how best to implement the MVVM pattern, there were significant (and sometimes heated) discussions about where to implement items like validation and the Observable pattern. One camp (the anemic model camp) argued that it all should be in the view model since adding those capabilities to the model broke separation of concerns. The other camp (the anemic view model camp) argued it should all be in the models since that reduced duplication of code.
The real answer is, of course, it depends. When INotifyPropertyChanged, IDataErrorInfo, and INotifyDataErrorInfo are implemented on the model classes, this ensures that the relevant code is close to the target of the code (as you will see in this chapter) and is implemented only once for each model.
That being said, there are times when your view model classes will need to be developed as observables themselves. At the end of the day, you need to determine what makes the most sense for your application, without over-complicating your code or sacrificing the benefits of MVVM.
■Note There are multiple MVVM frameworks available for WPF, such as MVVMLite, Caliburn.Micro, and Prism (although Prism is much more than just an MVVM framework). This chapter discusses the MVVM pattern and the features in WPF that support implementing the pattern. I leave it to you, the reader, to examine the different frameworks and select the one that best matches your app’s needs.
The WPF Binding Notification System
A significant shortcoming in the binding system for WinForms is a lack of notifications. If the data represented in the view is updated programmatically, the UI must also be refreshed programmatically to keep them in sync. This leads to a lot of calls to Refresh() on controls, typically more than are absolutely necessary in order to be safe. While usually not a significant performance issue to include too many calls to Refresh(), if you don’t include enough, the experience for the user could be affected negatively.
The binding system built into XAML-based applications corrects this problem by enabling you to hook your data objects and collections into a notification system by developing them as observables. Whenever a property’s value changes on an observable model or the collection changes (e.g., items are added, removed, or reordered) on an observable collection, an event is raised (either NotifyPropertyChanged
or NotifyCollectionChanged). The binding framework automatically listens for those events to occur
and updates the bound controls when they fire. Even better, as a developer, you have control over which properties raise the notifications. Sounds perfect, right? Well, it’s not quite perfect. There can be a fair amount of code involved in setting this up for observable models if you are doing it all by hand. Fortunately, there is an open source framework that makes it much simpler, as you shall soon see.
Observable Models and Collections
In this section, you will create an application that uses observable models and collections. To get started, create a new WPF application named WpfNotifications. The application will be a master-detail form, allowing the user to select a specific car using a ComboBox, and then the details for that car will be displayed in the following TextBox controls. Update MainWindow.xaml by replacing the default Grid with the following markup:
Your window will resemble Figure 29-1.
Figure 29-1. Master-detail window displaying Car details
The IsSharedSizeScope tag on the Grid control sets up child grids to share dimensions. The ColumnDefinitions marked SharedSizeGroup will automatically be sized to the same width without any programming needed. In this example, if the Pet Name label was changed to something much longer, the Vehicle column (which is in a different Grid control) would be sized to match it, keeping the window’s appearance nice and tidy.
Next, right-click the project name in Solution Explorer, select Add ➤ New Folder, and name the folder
Models. In this new folder, create a class named Car. The initial class is listed here:
public class Car
{
public int Id { get; set; } public string Make { get; set; } public string Color { get; set; }
public string PetName { get; set; }
}
Adding Bindings and Data
The next step is to add the binding statements for the controls. Remember that data-binding statements revolve around a data context, and this can be set on the control itself or on a parent control. Here, you are going to set the context on the DetailsGrid, so each control contained will inherit that data context. Set the DataContext to the SelectedItem property of the ComboBox. Update the Grid that holds the detail controls to the following:
<Grid Grid.Row="1" Name="DetailsGrid"
DataContext="{Binding ElementName=cboCars, Path=SelectedItem}">
The text boxes in the DetailsGrid will show the individual properties of the select car. Add the appropriate text attributes and related bindings to the TextBox controls, like so:
Finally, add data to the ComboBox. In MainWindow.xaml.cs, create a new list of Car records, and set the ItemsSource for the ComboBox to the list. Also, add the using statement for the Notifications.Models namespace.
using WpfNotifications.Models;
//omitted for brevity
public partial class MainWindow : Window
{
readonly IList
{
InitializeComponent();
_cars.Add(new Car {Id = 1, Color = "Blue", Make = "Chevy", PetName = "Kit"});
_cars.Add(new Car {Id = 2, Color = "Red", Make = "Ford", PetName = "Red Rider"}); cboCars.ItemsSource = _cars;
}
}
Run the app. You’ll see that the vehicle selector has two cars to choose from. Choose one of them, and the text boxes will be automatically populated with the vehicle detail. Change the color of one of the vehicles, select the other vehicle, and then go back to the vehicle you edited. You will see the new color is indeed still attached to the vehicle. This isn’t anything remarkable; you’ve seen the power of XAML data binding in previous examples.
Programmatically Changing the Vehicle Data
While the previous example works as expected, if the data is changed programmatically, the UI will not reflect the changes unless you program the app to refresh the data. To demonstrate this, add an event handler for the btnChangeColor Button, like so:
In the BtnChangeColor_Click() event handler, use the SelectedItem property of the ComboBox to locate the selected record from the cars list, and change the color to Pink. The code is listed here:
private void BtnChangeColor_OnClick(object sender, RoutedEventArgs e)
{
_cars.First(x => x.Id == ((Car)cboCars.SelectedItem)?.Id).Color = "Pink";
}
Run the app, select a vehicle, and click the Change Color button. Nothing changes visibly. Select the other vehicle and go back to your originally selected vehicle. Now you will see the updated value. This is not a good experience for the user!
Now add an event handler to the btnAddCar button, like this:
In the BtnAddCar_Click event handler, add a new record to the Car list.
private void BtnAddCar_Click(object sender, RoutedEventArgs e)
{
var maxCount = _cars?.Max(x => x.Id) ?? 0;
_cars?.Add(new Car { Id=++maxCount,Color="Yellow",Make="VW",PetName="Birdie"});
}
Run the app, click the Add Car button, and examine the contents of the ComboBox. Even though you know there are three cars in the list, only two are displayed! To correct both of these problems, you will convert the Car class to an observable model and use an observable collection to hold all the Car instances.
Observable Models
The problem of data changing on a property of your model and not being displayed in the UI is resolved by implementing the INotifyPropertyChanged interface on your Car model class. The INotifyPropertyChanged interface contains a single event: PropertyChangedEvent. The XAML binding engine listens for this event for each bound property on classes that implement the INotifyPropertyChanged interface. The interface is shown here:
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
}
Add the following using statements to the Car.cs class:
using System.ComponentModel;
using System.Runtime.CompilerServices;
Next, implement the INotifyPropertyChanged interface on the class, as follows:
public class Car : INotifyPropertyChanged
{
//Omitted for brevity
public event PropertyChangedEventHandler PropertyChanged;
}
The PropertyChanged event takes an object reference and a new instance of the
PropertyChangedEventArgs class, like in this example:
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs("Model"));
The first parameter is the object instance that is raising the event. The PropertyChangedEventArgs constructor takes a string that indicates the property that was changed and needs to be updated. When the event is raised, the binding engine looks for any controls bound to the named property on that instance. If you pass String.Empty into the PropertyChangedEventArgs, all of the bound properties of the instance are updated.
You control which properties are enlisted in the automatic updates. Only those properties that raise the PropertyChanged event in the setter will be automatically updated. This is usually all the properties on your model classes, but you have the option of omitting certain properties based on your application’s requirements. Instead of raising the event directly in the setter for each of the enlisted properties, a common pattern is to create a helper method (typically named OnPropertyChanged()) that raises the event on behalf of the properties, usually in a base class for your models. Add the following method and code into the Car.cs class:
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
Next, update each of the automatic properties in the Car class to have a full getter and setter with a backing field. When the value is changed, call the OnPropertyChanged() helper method. Here is the Id property updated:
private int _id; public int Id
{
get => _id; set
{
if (value == _id) return;
_id = value;
OnPropertyChanged();
}
}
Make sure you do the same for all the properties in the class and then run the app again. Select a vehicle and click the Change Color button. You will immediately see the change show up in the UI. First problem solved!
Using nameof
A feature added in C# 6 is the nameof operator, which provides the string name of the item passed into the
nameof method. You can use this in the calls to OnPropertyChanged() in your setters, like this:
public string Color
{
get { return _color; } set
{
if (value == _color) return;
_color = value; OnPropertyChanged(nameof(Color));
}
}
Note that you don’t have to remove the CallerMemberName attribute from OnPropertyChanged() when you use the nameof method (although it becomes unnecessary). In the end, whether you use the nameof method or the CallerMemberName attribute comes down to a matter of personal choice.
Observable Collections
The next problem to resolve is updating the UI when the contents of a collection changes. This is done by implementing the INotifyCollectionChanged interface. Like the INotifyPropertyChanged interface, this interface exposes one event, the CollectionChanged event. Unlike the INotifyPropertyChanged event, implementing this interface by hand is more than just calling a method in the setter. You need to create a full List implementation and raise the CollectionChanged event any time your list changes.
Using the ObservableCollections Class
Fortunately, there is a much easier way than creating your own collection class. The ObservableCollection
private readonly IList
Run the app again and click the Add Car button. You will see the new records appear appropriately.
Implementing a Dirty Flag
Another advantage of observable models is the ability to track state changes. Dirty tracking (tracking when one or more of an object’s values have changed) with WPF is fairly trivial. Add a bool property named IsChanged to the Car class. Make sure to call OnPropertyChanged() just like the other properties in the Car class.
private bool _isChanged; public bool IsChanged {
get => _isChanged; set
{
if (value == _isChanged) return;
_isChanged = value;
OnPropertyChanged();
}
}
You need to set the IsChanged property to true in the OnPropertyChanged() method. You also need to make sure you aren’t setting IsChanged to true when IsChanged is updated, or you will hit a stack overflow exception! Update the OnPropertyChanged() method to the following (which uses the nameof method discussed earlier):
protected virtual void OnPropertyChanged( [CallerMemberName] string propertyName = "")
{
if (propertyName != nameof(IsChanged))
{
IsChanged = true;
}
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
Open MainWindow.xaml and add an additional RowDefinition to the DetailsGrid. Add the following to the end of the Grid, which contains a Label and a CheckBox, bound to the IsChanged property, as follows:
If you were to run the app now, you would see that every single record shows up as changed, even though you haven’t changed anything! This is because object creation sets property values, and setting any values calls OnPropertyChanged(). This sets the object’s IsChanged property. To correct this, set the
IsChanged property to false as the last property in the object initialization code. Open MainWindow.xaml.cs
and change the code that creates the list to the following:
_cars.Add(
new Car {Id = 1, Color = "Blue", Make = "Chevy", PetName = "Kit", IsChanged = false});
_cars.Add(
new Car {Id = 2, Color = "Red", Make = "Ford", PetName = "Red Rider", IsChanged = false});
Run the app again, select a vehicle, and click the Change Color button. You will see the check box get selected along with the updated color.
Updating the Source Through UI Interaction
You might notice that if you type text into the UI, the Is Changed check box doesn’t actually get selected until you tab out of the control being edited. This is because of the UpdateSourceTrigger property on the TextBox bindings. The UpdateSourceTrigger determines what event (such as changing the value, tabbing out, etc.) causes the UI to update the underlying data. There are four options, as shown in Table 29-1.
Table 29-1. UpdateSourceTrigger Values
Member Meaning in Life
Default Sets to the default for the control (e.g., LostFocus for TextBox controls).
Explicit Updates the source object only when the UpdateSource method is called.
LostFocus Updates when the control loses focus. This is the default for TextBox controls.
PropertyChanged Updates as soon as the property changes. This is the default for CheckBox controls.
The default source trigger for a TextBox is the LostFocus event. Change this to PropertyChanged by updating the binding for the Color TextBox to the following XAML:
Now, when you run the app and start typing into the Color text box, the check box is immediately selected. You might ask why the default is set to LostFocus for TextBox controls. Any validation (covered in a moment) for a model fires in conjunction with the UpdateSourceTrigger. For a TextBox, this would then potentially cause errors continually flashing until the user entered the correct values. For example, if the validation rules don’t allow less than five characters in a TextBox, the error would show on each keystroke until the user got five or more entered. In those cases, it’s best to wait for the user to tab out of the TextBox (after completing the change to the text) to update the source.
Wrapping Up Notifications and Observables
Using INotifyPropertyChanged on your models and ObservableCollections classes for your lists improves the user experience by keeping the data and the UI in sync. While neither interface is complicated, they
do require updates to your code. Fortunately, Microsoft has included the ObservableCollection class to handle all of the plumbing to create observable collections. Just as fortunate is the update to the Fody project to add INotifyPropertyChanged functionality automatically. With these two tools in hand, there isn’t any reason to not implement observables in your WPF applications.
WPF Validations
Now that you’ve implemented INotifyPropertyChanged and are using an ObservableCollection, it’s time to add validations to your application. Applications need to validate user input and provide feedback to the user when the data entered is incorrect. This section covers the most common validation mechanisms for modern WPF applications, but these are still just a portion of the capabilities built into WPF.
Validation occurs when a data binding attempts to update the data source. In addition to built-in validations, such as exceptions in a property’s setter, you can create custom validation rules. If any validation rule (built-in or custom) fails, the Validation class, discussed shortly, comes into play.
■Note For each of the sections in this chapter, you can continue working in the same project from the previous section, or you can create a copy of the project for each new section. In the repo for this chapter, each section is a different project.
Updating the Sample for the Validation Examples
In the repo for this chapter, the new project (copied from the previous example) is called WpfValidations. If you are using the same project from the previous section, you just need to make note of the namespace changes when copying code into your project from the examples listed in this section.
The Validation Class
Before adding validations to your project, it’s important to understand the Validation class. This class is part of the validation framework, and it provides methods and attached properties that can be used to
display validation results. There are three main properties of the Validation class commonly used when handling validation errors (shown in Table 29-2). You will use each of these through the rest of this section.
Table 29-2. Key Members of the Validation Class
Member Meaning in Life
HasError Attached property indicating that a validation rule failed somewhere in the process
Errors Collection of all active ValidationError objects
ErrorTemplate Control template that becomes visible and adorns the bound element when HasError is set to true
Validation Options
As mentioned, XAML technologies have several mechanisms for incorporating validation logic into your application. You will examine three of the most commonly used validation choices in the next sections.
Notify on Exceptions
While exceptions should not be used to enforce business logic, exceptions can and do happen, and they should be handled appropriately. In case they aren’t handled in code, the user should receive visual feedback of the problem. An important change from WinForms is that WPF binding exceptions are not, by default, propagated to the user as exceptions. They are, however, visually indicated, using an adorner (visual layer that resides on top of your controls).
To test this, run the app, select a record from the ComboBox, and clear out the Id value. Since the Id property is defined as an int (not a nullable int), a numeric value is required. When you tab out of the Id field, an empty string is sent to the Id property by the binding framework, and since an empty string can’t be converted to an int, an exception is thrown in the setter. Normally, an unhandled exception would generate a message box to the user, but in this case, nothing like that happened. If you look in the Debug portion of the Output window, you will see the following:
System.Windows.Data Error: 7 : ConvertBack cannot convert value '' (type 'String'). BindingExpression:Path=Id; DataItem='Car' (HashCode=52579650); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String') FormatException:'System. FormatException: Input string was not in a correct format.
Visual display of the exception is a thin red box around the control, as shown in Figure 29-2.
Figure 29-2. The default error template
The red box is the ErrorTemplate property of the Validation object and acts as an adorner for the bound control. While the default error adorner shows that there is indeed an error, there isn’t any indication as to what is wrong. The good news is that the ErrorTemplate is completely customizable, as you will see later in this chapter.
IDataErrorInfo
The IDataErrorInfo interface provides a mechanism for you to add custom validations to your model classes. This interface is added directly to your model (or view model) classes, and the validation code is placed inside your model classes (preferably in partial classes). This centralizes validation code in your project, in direct contrast to WinForms projects, where validation was typically done in the UI itself.
The IDataErrorInfo interface, shown here, contains two properties: an indexer and a string property named Error. Note that the WPF binding engine doesn’t use the Error property.
public interface IDataErrorInfo
{
string this[string columnName] { get; } string Error { get; }
}
You will be adding the Car partial class shortly, but first you need to update the Car.cs class and mark it as partial. Next, add another file to the Models directory named CarPartial.cs. Rename this class Car, make sure the class is marked as partial, and add the IDataErrorInfo interface. Finally, implement the API for the interface. The initial code is listed here:
public partial class Car : IDataErrorInfo
{
public string this[string columnName] => string.Empty; public string Error { get;}
}
For a bound control to opt in to the IDataErrorInfo interface, it must add ValidatesOnDataErrors to the binding expression. Update the binding expression for the Make text box to the following (and update the rest of the binding statements in the same way):
Once this update is made to the binding statements, the indexer on the model gets called each time the
PropertyChanged event is raised. The property name from the event is used as the columnName parameter in the indexer. If the indexer returns string.Empty, the framework assumes that all validations passed, and no error condition exists. If the indexer returns anything but string.Empty, an error is presumed to exist on the property for that object instance, and each control that is bound to the property being validated on this specific instance of the class is considered to have an error, the HasError property of the Validation object is set to true, and the ErrorTemplate adorner is activated for the controls effected.
Next, you will add some simple validation logic to the indexer in CarPartial.cs. The validation rules are simple.
•If Make equals ModelT, set the error equal to "Too Old".
•If Make equals Chevy and Color equals Pink, set the error equal to $"{Make}'s don't come in {Color}".
Start by adding a switch statement for each of the properties. To avoid using magic strings in the case statements, you will again use the nameof method. If the code falls through the switch statement, return string.Empty. Next, add the validation rules. In the proper case statements, add a check of the property value based on the rules listed earlier. In the case statement for the Make property, first check to make sure the value isn’t ModelT. If it is, return the error. If that passes, the next line will call into a helper method that returns an error if the second rule is violated, or it will return string.Empty if it is not. In the case statement for the Color property, also call the helper method. The code is as follows:
public string this[string columnName]
{
get
{
switch (columnName)
{
case nameof(Id):
break;
case nameof(Make):
return Make == "ModelT"
? “Too Old”
: CheckMakeAndColor(); case nameof(Color):
return CheckMakeAndColor(); case nameof(PetName):
break;
}
return string.Empty;
}
}
internal string CheckMakeAndColor()
{
if (Make == "Chevy" && Color == "Pink")
{
return $"{Make}'s don't come in {Color}";
}
return string.Empty;
}
Run the app, select the Red Rider vehicle (the Ford), and change the Make to ModelT. Once you tab out of the field, the red error decorator appears. Now select Kit (which is a Chevy) from the drop-down and
click the Change Color button to change the color to Pink. Immediately the red error adorner appears on the Color field but doesn’t appear on the Make text box. Now, change Make to Ford, tab out of the text box, and note that the red adorner does not disappear!
This is because the indexer runs only when the PropertyChanged event is fired for a property. As discussed in earlier, the PropertyChanged event fires when the source object’s property changes, and this happens either through code (such as clicking the Change Color button) or through user interaction (the timing of this is controlled through the UpdateSourceTrigger). When you changed the color, the Make property did not change, so the event did not fire for the Make property. Since the event didn’t fire, the indexer did not get called, so the validation for the Make property didn’t run.
There are two ways to fix this. The first is to change PropertyChangedEventArgs to update every bound property by passing in string.Empty instead of a field name. As discussed, this causes the binding engine to update every property on that instance. Update the OnPropertyChanged() method in the Car.cs class like this:
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (propertyName != nameof(IsChanged))
{
IsChanged = true;
}
//PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(string.Empty));
}
Now, when you run the same test, you see that both the Make and Color text boxes are adorned with the error template when one of them is updated. So, why not always raise the event in this manner? It’s largely a matter of performance. It’s possible that refreshing every property on an object could hamper performance. Of course, there’s no way to know without testing, and your mileage may (and probably will) vary.
The other solution is to raise the PropertyChanged event for the other dependent field(s) when one changes. The downside to using this mechanism is that you (or other developers who support your app) must know that the Make and Color properties are related through the validation code.
INotifyDataErrorInfo
The INotifyDataErrorInfo interface introduced in .NET 4.5 builds on the IDataErrorInfo interface and adds additional capabilities for validation. Of course, with additional power comes additional work! In a drastic shift from prior validation techniques that you had to specifically opt into, the ValidatesOnNotifyDataErrors binding property defaults to true, so adding the property to your binding statements is optional.
The INotifyDataErrorInfo interface is extremely small but does take a fair amount of plumbing code to make it effective, as you will see shortly. The interface is shown here:
public interface INotifyDataErrorInfo
{
bool HasErrors { get; }
event EventHandler
IEnumerable GetErrors(string propertyName);
}
The HasErrors property is used by the binding engine to determine whether there are any errors on any of the instance’s properties. If the GetErrors() method is called with a null or empty string for the propertyName parameter, it returns all errors that exist in the instance. If a propertyName is passed into the method, only
the errors for that particular property are returned. The ErrorsChanged event (like the PropertyChanged and
CollectionChanged events) notifies the binding engine to update the UI for the current list of errors.
Implement the Supporting Code
When implementing INotifyDataErrorInfo, most of the code is usually pushed into a base model class, so it needs be written only once. Start by replacing IDataErrorInfo with INotifyDataErrorInfo in the CarPartial.cs class and add the interface members (you can leave the code from IDataErrorInfo in the class; you will be updating this later).
public partial class Car: INotifyDataErrorInfo, IDataErrorInfo
{
...
public IEnumerable GetErrors(string propertyName)
{
throw new NotImplementedException();
}
public bool HasErrors { get; } public event
EventHandler
}
Next, add a Dictionary<string,List
private readonly Dictionary<string,List
= new Dictionary<string, List
The HasErrors property should return true if there are any errors in the dictionary. This is easily accomplished like this:
public bool HasErrors => _errors.Any();
Next, create a helper method to raise the ErrorsChanged event (just like raising the PropertyChanged
event) like this:
private void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this,
new DataErrorsChangedEventArgs(propertyName));
}
As mentioned earlier, the GetErrors() method should return any and all errors in the dictionary if the parameter is empty or null. If a propertyName value is passed in, it will return any errors found for that property. If the parameter doesn’t match (or there aren’t any errors for a property), then the method will return null.
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
return _errors.Values;
}
return _errors.ContainsKey(propertyName)
? _errors[propertyName]
: null;
}
The final set of helpers will add one or more errors for a property or clear all of the errors for a property (or all properties). Any time the dictionary changes, remember to call the OnErrorsChanged() helper method.
private void AddError(string propertyName, string error)
{
AddErrors(propertyName, new List
}
private void AddErrors(
string propertyName, IList
{
if (errors == null || !errors.Any())
{
return;
}
var changed = false;
if (!_errors.ContainsKey(propertyName))
{
_errors.Add(propertyName, new List
}
foreach (var err in errors)
{
if (_errors[propertyName].Contains(err)) continue;
_errors[propertyName].Add(err); changed = true;
}
if (changed)
{
OnErrorsChanged(propertyName);
}
}
protected void ClearErrors(string propertyName = "")
{
if (string.IsNullOrEmpty(propertyName))
{
_errors.Clear();
}
else
{
_errors.Remove(propertyName);
}
OnErrorsChanged(propertyName);
}
Now the question is “how is this code activated?” The binding engine listens for the ErrorsChanged
event and will update the UI if there is a change in the errors collection for a binding statement. But the validation code still needs a trigger to execute. There are two mechanisms for this, and they will be discussed next.
Use INotifyDataErrorInfo for Validations
One place to check for errors is in the property setters, like the following example, simplified to just check for the ModelT validation:
public string Make
{
get { return _make; } set
{
if (value == _make) return;
_make = value;
if (Make == "ModelT")
{
AddError(nameof(Make), "Too Old");
}
else
{
ClearErrors(nameof(Make));
}
OnPropertyChanged(nameof(Make)); OnPropertyChanged(nameof(Color));
}
}
The main issue with this approach is you have to combine validation logic with property setters, making the code harder to read and support.
Combine IDataErrorInfo with INotifyDataErrorInfo for Validations
You saw in the previous section that IDataErrorInfo can be added to a partial class, which means you don’t have to update your setters. You also saw that the indexer automatically gets called when PropertyChanged is raised on a property. Combining IDataErrorInfo and INotifyDataErrorInfo provides you with the additional features for validations from INotifyDataErrorInfo and with the separation from the setters provided by IDataErrorInfo.
The purpose of using IDataErrorInfo is not to run validations but to make sure your validation code that leverages INotifyDataErrorInfo gets called every time PropertyChanged is raised on your object. Since you aren’t using IDataErrorInfo for validation, always return string.Empty from the indexer. Update the indexer and the CheckMakeAndColor() helper method to the following code:
public string this[string columnName]
{
get
{
ClearErrors(columnName); switch (columnName)
{
case nameof(Id): break;
case nameof(Make): CheckMakeAndColor(); if (Make == "ModelT")
{
AddError(nameof(Make), "Too Old"); hasError = true;
}
break;
case nameof(Color): CheckMakeAndColor(); break;
case nameof(PetName):
break;
}
return string.Empty;
}
}
internal bool CheckMakeAndColor()
{
if (Make == "Chevy" && Color == "Pink")
{
AddError(nameof(Make), $"{Make}'s don't come in {Color}"); AddError(nameof(Color),
$"{Make}'s don't come in {Color}"); return true;
}
return false;
}
Run the app, select Chevy, and change the color to Pink. In addition to the red adorners around the Make and Model text boxes, you will also see a red box adorner around the entire grid that holds the Car details fields (shown in Figure 29-3).
Figure 29-3. The updated error adorner
This is another advantage of using INotifyDataErrorInfo. In addition to the controls in error, the control defining the data context also gets adorned with the error template.
Show All Errors
The Errors property on the Validation class returns all the validation errors on a particular object in the form of ValidationError objects. Each ValidationError object has an ErrorContent property that contains the list of error messages for the property. This means the error messages you want to display are in this list within a list. To display them properly, you need to create a ListBox that holds a ListBox to display the data. It sounds a bit recursive, but it will make sense once you see it.
Start by adding another row to the DetailsGrid and make sure the Height of the Window is at least 300. Add a ListBox in the last row, and bind the ItemsSource to the DetailsGrid, using Validation.Errors for the path, as follows:
Add a DataTemplate to the ListBox, and in the DataTemplate, add a ListBox that is bound to the ErrorContent property. The data context for each ListBoxItem in this case is a ValidationError object, so you don’t need to set the data context, just the path. Set the binding path to ErrorContent, like this:
Run the app, select Chevy, and set the color to Pink. You will see the errors displayed in Figure 29-4.
Figure 29-4. Showing the errors collection
This just scratches the surface of what you can do with validations and with displaying the errors generated, but it should have you well on your way to developing informative UIs that improve the user experience.
Move the Support Code to a Base Class
As you probably noticed, there is a lot of code now in the CarPartial.cs class. Since this example has only one model class, this isn’t terrible. But, as you add models to a real application, you don’t want to have to add all of that plumbing into each partial class for your models. The best thing to do is to push all of that supporting code down to a base class. You will do that now.
Add a new class file to the Models folder named BaseEntity.cs. Add using statements for System. Collections and System.ComponentModel. Make the class public, and add the INotifyDataErrorInfo interface, like this:
using System;
using System.Collections;
using System.Collections.Generic; using System.ComponentModel; using System.Linq;
namespace Validations.Models
{
public class BaseEntity : INotifyDataErrorInfo
}
Move all of the code from CarPartial.cs that relates to INofityDataErrorInfo into the new base class.
Any private methods and variables need to be made protected. Next, remove the INotifyDataErrorInfo
interface from the CarPartial.cs class, and add BaseEntity as a base class, as follows:
public partial class Car : BaseEntity, IDataErrorInfo
{
//removed for brevity
}
Now, any additional model classes you create will inherit all of the INotifyDataErrorInfo plumbing code.
Leverage Data Annotations with WPF
WPF can leverage data annotations as well for UI validation. Let’s add some data annotations to the Car model.
Add Data Annotations to the Model
Open Car.cs and add a using statement for System.ComponentModel.DataAnnotations. Add the [Required] and [StringLength(50)] attributes to Make, Color, and PetName. The Required attribute adds a validation rule that the property must not be null (admittedly, this is redundant for the Id property since it
is not a nullable int). The StringLength(50) attribute adds a validation rule that the value of the property cannot be longer than 50 characters.
Check for Data Annotation–Based Validation Errors
In WPF you have to programmatically check for data annotation–based validation errors. Two key classes for annotation-based validations are the ValidationContext and Validator classes. The ValidationContext class provides a context for checking a class for validation errors. The Validator class allows you to check an object for attribute-based errors within a ValidationContext.
Open BaseEntity.cs, and add the following using statements:
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
Next, create a new method named GetErrorsFromAnnotations(). This method is generic, takes a string property name and a value of type T as the parameters, and returns a string array. Make sure the method is marked as protected. The signature is listed here:
protected string[] GetErrorsFromAnnotations
{}
In the method, create a List
protected string[] GetErrorsFromAnnotations
string propertyName, T value)
{
var results = new List
var vc = new ValidationContext(this, null, null)
{ MemberName = propertyName };
var isValid = Validator.TryValidateProperty( value, vc, results);
return (isValid)
? null
: Array.ConvertAll(
results.ToArray(), o => o.ErrorMessage);
}
Now you can update the indexer method in CarPartial.cs to check for any errors based on data annotations. If any errors are found, add them to the errors collection supporting INotifyDataErrorInfo. This enables us to clean up the error handling. In the start of the indexer method, clear the errors for the column.
Then process the validations and finally the custom logic for the entity. The updated indexer code is shown here:
public string this[string columnName]
{
get
{
ClearErrors(columnName); var errorsFromAnnotations =
GetErrorsFromAnnotations(columnName, typeof(Car)
.GetProperty(columnName)?.GetValue(this,null)); if (errorsFromAnnotations != null)
{
AddErrors(columnName, errorsFromAnnotations);
}
switch (columnName)
{
case nameof(Id): break;
case nameof(Make): CheckMakeAndColor(); if (Make == "ModelT")
{
AddError(nameof(Make), "Too Old");
}
break;
case nameof(Color): CheckMakeAndColor(); break;
case nameof(PetName): break;
}
return string.Empty;
}
}
Run the app, select one of the vehicles, and add text for the color that is longer than 50 characters. When you cross the 50-character threshold, the StringLength data annotation creates a validation error, and it is reported to the user, as shown in Figure 29-5.
Figure 29-5. Validating the required data annotation
Customizing the ErrorTemplate
The final topic is to create a style that will be applied when a control is in error and also update the ErrorTemplate to display more meaningful error information. As you learned in Chapter 27, controls are customizable through styles and control templates.
Start by adding a new style in the Windows.Resources section of MainWindow.xaml with a target type of TextBox. Next, add a trigger on the style that sets properties when the Validation.HasError property is set to true. The properties and the values to set are Background (Pink), Foreground (Black), and Tooltip to the ErrorContent. The Background and Foreground setters are nothing new, but the syntax for setting the ToolTip needs some explanation. The binding points back to the control that this style is applied to, in this case, the TextBox. The path is the first ErrorContent value of the Validation.Errors collection. The markup is as follows:
Run the app and create an error condition. The result will be similar to Figure 29-6, complete with a tooltip showing the error message.
Figure 29-6. Showing a custom ErrorTemplate
The previous style changed the appearance of any TextBox that has an error condition. Next, you will create a custom control template to update the ErrorTemplate of the Validation class to show a red exclamation mark and set the tooltips for the exclamation mark. The ErrorTemplate is an adorner, which
sits on top of the control. While the style just created updates the control itself, the ErrorTemplate will sit on top of the control.
Place a setter immediately after the Style.Triggers closing tag within the style you just created. You will be creating a control template that consists of a TextBlock (to show the exclamation mark) and a BorderBrush to surround the TextBox that contains the error(s). There is a special tag in XAML for the control that is being adorned with the ErrorTemplate named AdornedElementPlaceholder. By adding a
name to this control, you can access the errors that are associated with the control. In this example, you want to access the Validation.Errors property so you can get the ErrorContent (just like you did in the Style. Trigger). Here is the full markup for the setter:
Run the app and create an error condition. The result will be similar to Figure 29-7.
Figure 29-7. Showing a custom ErrorTemplate
Wrapping Up Validations
This completes your look at validation methods within WPF. Of course, there is much more that you can do. For more information, consult the WPF documentation.
Creating Custom Commands
As with the validations sections, you can continue working in the same project or create a new one and copy all of the code to it. I will create a new project named WpfCommands. If you are using the same project, be sure to pay attention to the namespaces in the code samples in this section and adjust them as needed.
As you learned in Chapter 25, commands are an integral part of WPF. Commands can be hooked up to WPF controls (such as Button and MenuItem controls) to handle user events, such as the Click() event. Instead of creating an event handler directly and adding the code directly into the code-behind file, the Execute() method of the command is executed when the click event fires. The CanExecute() method is
used to enable or disable the control based on custom code. In addition to the built-in commands you used in Chapter 25, you can create your own custom commands by implementing the ICommand interface. By using commands instead of event handlers, you gain the benefit of encapsulating application code, as well as automatically enabling and disabling controls based on business logic.
Implementing the ICommand Interface
As a quick review from Chapter 25, the ICommand interface is listed here:
public interface ICommand
{
event EventHandler CanExecuteChanged; bool CanExecute(object parameter); void Execute(object parameter);
}
Adding the ChangeColorCommand
The event handlers for your Button controls will be replaced with commands, starting with the Change Color button. Start by adding a new folder (named Cmds) in your project. Add a new class named ChangeColorCommand.cs. Make the class public, and implement the ICommand interface. Add the following using statements (the first one might vary depending on whether you created a new project for this sample):
using WpfCommands.Models; using System.Windows.Input;
Your class should look like this:
public class ChangeColorCommand : ICommand
{
public bool CanExecute(object parameter)
{
throw new NotImplementedException();
}
public void Execute(object parameter)
{
throw new NotImplementedException();
}
public event EventHandler CanExecuteChanged;
}
If the CanExecute() method returns true, any bound controls will be enabled, and if it returns false, they will be disabled. If a control is enabled (because CanExecute() returns true) and clicked, the Execute() method will fire. The parameter passed into both of these methods comes from the UI based on the CommandParameter property set on binding statements. The CanExecuteChanged event ties into the binding and notification system to inform the UI that the result of the CanExecute() method has changed (much like the PropertyChanged event).
In this example, the Change Color button should work only if the parameter is not null and of type Car.
Update the CanExecute() method to the following:
public bool CanExecute(object parameter)
=> (parameter as Car) != null;
The value for the Execute() method parameter is the same as for the CanExecute() method. Since the Execute() method can execute only if the object is of type Car, the argument must be cast to an Car type and have the color updated, as follows:
public void Execute(object parameter)
{
((Car)parameter).Color="Pink";
}
Attaching the Command to the CommandManager
The final update for the command class is to type the command into the command manager. The CanExecute() method fires when the Window first loads and then when the command manager instructs it to reexecute. Each command class has to opt in to the command manager. This is done by updating the code regarding the CanExecuteChanged event, as follows:
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value;
}
Updating MainWindow.xaml.cs
The next change is to create an instance of this class that the Button can access. For now, you will place this in the code-behind file for the MainWindow (later in this chapter, you will move this into a view model). Open MainWindow.xaml.cs and delete the Click event handler for the Change Color button. Add the following using statements to the top of the file (again, the namespace may vary based on whether you are still using the same project or you started a new one):
using WpfCommands.Cmds; using System.Windows.Input;
Next, add a public property named ChangeColorCmd of type ICommand with a backing field. In the expression body for the property, return the backing property (make sure to instantiate a new instance of the ChangeColorCommand if the backing field is null).
private ICommand _changeColorCommand = null; public ICommand ChangeColorCmd
=> _changeColorCommand ??= new ChangeColorCommand());
Updating MainWindow.xaml
As you saw in Chapter 25, clickable controls in WPF (like Button controls) have a Command property that allows you to assign a command object to the control. Start by connecting your command instantiated in the code-behind to the btnChangeColor button. Since the property for the command is on the MainWindow class, you use the RelativeSourceMode binding syntax to get to the Window that contains the Button, as follows:
Command="{Binding Path=ChangeColorCmd,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
The Button still needs to send in a Car object as the parameter for the CanExecute() and Execute() methods. This is assigned through the CommandParameter property. You set this to the SelectedItem of the cboCars ComboBox, as follows:
CommandParameter="{Binding ElementName=cboCars, Path=SelectedItem}"
The complete markup for the button is shown here:
Testing the Application
Run the application. You will see that the Change Color command is not enabled, as shown in Figure 29-8, since there isn’t a vehicle selected.
Figure 29-8. A window with nothing selected
Now, select a vehicle; the button will become enabled, and clicking it will change the color, as expected!
Creating the CommandBase Class
If you continued with this pattern for AddCarCommand.cs, there would be code that would be repeated between the classes. This is a good sign that a base class can help. Create a new class in the Cmds folder named CommandBase.cs and add a using for the System.Windows.Input namespace. Set the class to public and implement the ICommand interface. Change the class and the Execute() and CanExecute() methods to abstract. Finally, add in the updated CanExecuteChanged event from the ChangeColorCommand class. The full implementation is listed here:
using System;
using System.Windows.Input;
namespace WpfCommands.Cmds
{
public abstract class CommandBase : ICommand
{
public abstract bool CanExecute(object parameter); public abstract void Execute(object parameter); public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value;
}
}
}
Adding the AddCarCommand Class
Add a new class named AddCarCommand.cs to the Cmds folder. Make the class public and add CommandBase as the base class. Add the following using statements to the top of the file:
using System.Collections.ObjectModel; using System.Linq;
using WpfCommands.Models;
The parameter is expected to be an ObservableCollection
public class AddCarCommand :CommandBase
{
public override bool CanExecute(object parameter)
=> parameter is ObservableCollection
{
if (parameter is not ObservableCollection
{
return;
}
var maxCount = cars.Max(x => x.Id); cars.Add(new Car
{
Id = ++maxCount, Color = "Yellow", Make = "VW", PetName = "Birdie"
});
}
}
Updating MainWindow.xaml.cs
Add a public property named AddCarCmd of type ICommand with a backing field. In the expression body for the property, return the backing property (make sure to instantiate a new instance of the AddCarCommand if the backing field is null).
private ICommand _addCarCommand = null; public ICommand AddCarCmd
=> _addCarCommand ??= new AddCarCommand());
Updating MainWindow.xaml
Update the XAML to remove the Click attribute and add the Command and CommandParameter attributes. The AddCarCommand will receive the list of cars from the cboCars combo box. The entire button’s XAML is as follows:
With this in place, you can now add cars and update the color of cars using reusable code contained in stand-alone classes.
Updating ChangeColorCommand
The final step is to update the ChangeColorCommand to inherit from CommandBase. Change ICommand to CommandBase, add the override keyword to both methods, and delete the CanExecuteChanged code. It’s really that simple! The new code is listed here:
public class ChangeColorCommand : CommandBase
{
public override bool CanExecute(object parameter)
=> parameter is Car;
public override void Execute(object parameter)
{
((Car)parameter).Color = "Pink";
}
}
RelayCommands
Another implementation of the command pattern in WPF is the RelayCommand. Instead of creating a new class for each command, this pattern uses delegates to implement the ICommand interface. It is a lightweight implementation, in that each command doesn’t have its own class. RelayCommands are usually used when there isn’t any reuse needed for the implementation of the command.
Creating the Base RelayCommand
RelayCommands are typically implemented in two classes. The base RelayCommand class is used when there aren’t any parameters needed for the CanExecute() and Execute() methods, and RelayCommand
private readonly Action _execute; private readonly Func
Create three constructors. The first is the default constructor (needed by the RelayCommand
public RelayCommand(){}
public RelayCommand(Action execute) : this(execute, null) { } public RelayCommand(Action execute, Func
{
_execute = execute
?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
Finally, implement the CanExecute() and Execute() overrides. CanExecute() returns true if the Func is null; or if it is not null, it executes and returns true. Execute() executes the Action parameter.
public override bool CanExecute(object parameter)
=> _canExecute == null || _canExecute();
public override void Execute(object parameter) { _execute(); }
Creating RelayCommand
Add a new class named RelayCommandT.cs to the Cmds folder. This class is almost a carbon copy of the base class, except that the delegates all take a parameter. Make the class public and generic, and add RelayCommand as the base class, as follows:
public class RelayCommand
Add two class-level variables to hold the Execute() and CanExecute() delegates:
private readonly Action
Create two constructors. The first takes an Action
parameter and a Func
public RelayCommand(Action
Action
{
_execute = execute
?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
Finally, implement the CanExecute() and Execute() overrides. CanExecute() returns true if the Func is null; or, if it is not null, it executes and returns true. Execute() executes the Action parameter.
public override bool CanExecute(object parameter)
=> _canExecute == null || _canExecute((T)parameter); public override void Execute(object parameter)
{ _execute((T)parameter); }
Updating MainWindow.xaml.cs
When you use RelayCommands, all of the methods for the delegates need to be specified when a new command is constructed. This doesn’t mean that the code needs to live in the code-behind (as is shown here); it just has to be accessible from the code-behind. It could live in another class (or even another assembly), providing the code encapsulation benefits of creating a custom command class.
Add a new private variable of type RelayCommand
private RelayCommand
=> _deleteCarCommand ??=
new RelayCommand
The DeleteCar() and CanDeleteCar() methods must be created as well, as follows:
private bool CanDeleteCar(Car car) => car != null; private void DeleteCar(Car car)
{
_cars.Remove(car);
}
Notice the strong typing in the methods—this is one of the benefits of using RelayCommand
Adding and Implementing the Delete Car Button
The final step to tie it all together is to add the button and assign the Command and CommandParameter
bindings. Add the following markup:
Now when you run the application, you can test that the Delete Car button is enabled only if a car is selected in the drop-down and that clicking the button does indeed delete the car from the Car list.
Wrapping Up Commands
This concludes your brief journey into WPF commands. By moving the event handling out of the code- behind file and into individual command classes, you gain the benefit of code encapsulating, reuse, and improved maintainability. If you don’t need that much separation of concerns, you can use the lighter- weight RelayCommand implementation. The goal is to improve maintainability and code quality, so choose the method that works best for you.
Migrate Code and Data to a View Model
As in the “WPF Validations” section, you can continue working in the same project, or you can create a new one and copy all of the code over. I will create a new project named WpfViewModel. If you are using the same project, be sure to pay attention to the namespaces in the code samples in this section and adjust as needed.
Create a new folder named ViewModels in your project, and add a new class named
MainWindowViewModel.cs into that folder. Add the following namespaces and make the class public:
using System.Collections.Generic; using System.Collections.ObjectModel; using System.Windows.Input;
using WpfViewModel.Cmds; using WpfViewModel.Models;
■Note a popular convention is to name the view models after the window they support. I typically follow that convention and will do so in this chapter. however, like any pattern or convention, this isn’t a rule, and you will find a wide range of opinions on this.
Moving the MainWindow.xaml.cs Code
Almost all the code from the code-behind file will be moved to the view model. At the end, there will only be a few lines, including the call to InitializeComponent() and the code for setting the data context for the window to the view model.
Create a public property of type IList
public IList
Create a default constructor and move all the Car creation code from the MainWindow.xaml.cs file, updating the list variable name. You can also delete the _cars variable from MainWindow.xaml.cs. Here is the view model constructor:
public MainWindowViewModel()
{
Cars.Add(
new Car { Id = 1, Color = "Blue", Make = "Chevy", PetName = "Kit", IsChanged = false }); Cars.Add(
new Car { Id = 2, Color = "Red", Make = "Ford", PetName = "Red Rider", IsChanged = false });
}
Next, move all the command-related code from the window code-behind file to the view model, updating the _cars variable reference to Cars. Here is just the changed code:
//rest omitted for brevity private void DeleteCar(Car car)
{
Cars.Remove(car);
}
Updating the MainWindow Code and Markup
Most of the code has been removed from the MainWindow.xaml.cs file. Remove the line that assigns the
ItemsSource for the combo box, leaving only the call to InitializeComponent. It should now look like this:
public MainWindow()
{
InitializeComponent();
}
Add the following using statement to the top of the file:
using WpfViewModel.ViewModels;
Next, create a strongly typed property to hold the instance of the view model.
public MainWindowViewModel ViewModel { get; set; }
= new MainWindowViewModel();
Finally, add a DataContext property to the window’s declaration in XAML.
DataContext="{Binding ViewModel, RelativeSource={RelativeSource Self}}"
Updating the Control Markup
Now that the DataContext for the Window is set to the view model, the XAML bindings for the controls need to be updated. Starting with the combo box, update the markup by adding an ItemsSource.
This works because the data context for the Window is the MainWindowViewModel, and Cars is a public property on the view model. Recall that binding calls walk up the element tree until a data context is located. Next, you need to update the bindings for the Button controls. This is straightforward; since the bindings are already set to the window level, you just need to update the binding statement to start with the DataContext property, as follows:
Wrapping Up View Models
Believe it or not, you have just completed your first MVVM WPF application. You might be thinking, “This isn’t a real application. What about data? The data in this example is hard-coded.” And you would be correct. It’s not a real app; it’s demoware. However, that is the beauty of the MVVM pattern. The view doesn’t know anything about where the data is coming from; it’s just binding to a property on the view model. You can swap out view model implementations, perhaps using a version with hard-coded data for testing and one that hits the database for production.
There are a lot of additional points that could be discussed, including various open source frameworks, the View Model Locator pattern, and a host of differing opinions on how to best implement the pattern.
That’s the beauty of software design patterns—there are usually many correct ways to implement it, and then you just need to find the best manner based on your business and technical requirements.
Updating AutoLot.Dal for MVVM
If you want to update AutoLot.Dal for MVVM, you will have to apply the changes that we did for the Car
class to all of the entities in the AutoLot.Dal.Models project, including the BaseEntity.
Summary
This chapter examined the WPF topics that support the Model-View-ViewModel pattern. You started off learning how to tie model classes and collections into the notification system in the binding manager. You implemented INotifyPropertyChanged and used ObservableCollections classes to keep the UI in sync with the bound data.
Next, you added validation code to the model using IDataErrorInfo and INotifyDataErrorInfo and checked for data annotation errors. You then displayed any validation errors in the UI so the user would know what the problem is and how to fix it, and you created a style and custom control template to render errors in a meaningful way.
Finally, you put it all together by adding a view model, and you cleaned up the UI markup and code- behind file to increase separation of concerns.