In the previous articles in this series on building a WPF business application (check www.CODEMag.com for the others), you created a new WPF business application using a pre-existing architecture. You added code to display a message while loading resources in the background. You also learned how to load and close user controls on a main window. You built a login screen, a user feedback screen, and a user maintenance screen to display a list of users, and the detail for a single user. In this article, you’re going to finish this user maintenance screen by learning to manage button state, and to add, edit, and delete users.

This article is the fourth, and final, in a multi-part series on how to create a WPF business application. Instead of starting completely from scratch, I’ve created a starting architecture that you can learn about by reading the blog post entitled "An Architecture for WPF Applications" located at https://bit.ly/2BxpK0P. Download the samples that go along with the blog post to follow along step-by-step with this article. This series of articles is also a Pluralsight.com course that you can view at https://bit.ly/2SjwTeb. You can also read the previous articles in the May/June, July/August, and September/October issues of CODE Magazine (https://www.codemag.com/Magazine/AllIssues).

A Design Pattern for Master/Detail Screens in WPF

In the last article, you built the UI for a user maintenance screen (Figure 1). In this article, you’re going to add the code necessary to manage state so that different controls on the screen can be enabled or disabled depending on what the user is doing. You also create code to add, edit, and delete users.

Figure 1: The sample application with a user list and detail user controls

Overview of Managing State

When building a maintenance screen that will list, add, edit, and delete any records in a database, you need to keep track of what state the user is currently in. There are three different states that you need to keep track of:

  • Displaying the list of users
  • Editing user information
  • Adding a new user

When you are in each of these states, you need to change various controls on the maintenance screen to be enabled or disabled. The following sections describe the state that each of the controls should be in, depending on what the user is currently doing on that screen. The reason to enable or disable controls is to keep the user focused on what they are currently doing with the data on the screen. For example, the user may be browsing the list of users or maybe trying to modify a single user.

The List State

When you first enter the user maintenance screen, the list of all users is displayed. When you are in this List state, the following controls on the screen should be set to the following states.

  • The List View is enabled.
  • The Detail User control is disabled.
  • The Add button is enabled.
  • The Edit button is enabled.
  • The Delete button is enabled.
  • The Undo button is disabled.
  • The Save button is disabled.

The Edit State

When the user clicks on the button to add or edit a user, you want them to focus only on the detail area until they’re finished modifying that user. When in this Edit state, the only way to finish is to click the Undo or the Save buttons. Modify the controls on the screen to the following states.

  • The List View is disabled.
  • The Detail User control is enabled.
  • The Add button is disabled.
  • The Edit button is disabled.
  • The Delete button is disabled.
  • The Undo button is enabled.
  • The Save button is enabled.

The reason to disable the List view when editing is to force the user to focus on just editing a user. If you didn’t disable the List view, they might accidently click on the list and move to a new user before they’ve had a chance to save their changes. Yes, you could add an IsDirty property on the view model, but this requires a lot more code than simply disabling the list view.

The Add State

Adding a user is almost the same state as when editing a user. When you are ready to save the data, you need a flag, so you know to add a new record using the Entity Framework versus merely updating the record. You’re going to use an IsAddMode property to keep track of this state.

A View Model Base Class for Add, Edit, Delete

For any maintenance screen like the one shown in Figure 1, you’re going to need to keep track of the different states outlined in the previous section. Create a class named ViewModelAddEditDeleteBase in the ViewModelLayer project, have it inherit from the ViewModelBase class, and add three new properties; IsListEnabled, IsDetailEnabled and IsAddMode, as shown in Listing 1.

Add a BeginEdit() method in this class to set these properties to the valid state for adding or editing a record.

public virtual void BeginEdit( bool isAddMode = false){ IsListEnabled = false; IsDetailEnabled = true; IsAddMode = isAddMode;}

Add a CancelEdit() method to reset these properties back to the normal mode of displaying a list of users only.

public virtual void CancelEdit(){ base.Clear();
  IsListEnabled = true; IsDetailEnabled = false; IsAddMode = false;}

Add two additional methods to this class, Save() and Delete(). The Save() method is a virtual method with no functionality in this class; it provides a design pattern for you to use in your view models.

public virtual bool Save() { return true;}

The Delete() method is also virtual and provides no functionality in this class; it also serves as a design pattern for your own view models. The signature for this method is as follows.

public virtual bool Delete() { return true;}

Modify User Maintenance List View Model

Originally, you had the UserMaintenanceListViewModel class inherit from the ViewModelBase class. Change that class to inherit from the ViewModelAddEditDeleteBase class by opening the UserMaintenanceListViewModel.cs file in the WPF.Sample.ViewModelLayer project and modify the inheritance, as shown in the code snippet below.

public class UserMaintenanceListViewModel : ViewModelAddEditDeleteBase

Modify User Maintenance Detail View Model

Open the UserMaintenanceDetailViewModel.cs file in the WPF.Sample.ViewModelLayer project and override the Save() and Delete() methods. In the Save() method, call the CancelEdit() method to put the state back to List mode, as shown in the following code. You are going to fill in the code to save and delete records later in this article.

public override bool Save(){ // TODO: Save User
  CancelEdit();
  return true;}
public override bool Delete(){ // TODO: Delete User
  return true;}

Bind Controls to State Properties

Now that you’ve changed the inheritance on your view model that’s bound to your WPF screen, you can bind up each of the three new properties to the appropriate controls. These properties enable and disable controls, depending on the state the form is in.

Save and Undo Buttons

Open the UserMaintenanceDetailControl.xaml file and locate the Undo and the Save buttons. Bind the IsDetailEnabled property to the IsEnabled property of each of these buttons.

< Button IsCancel = "True" IsEnabled = "{Binding Path=IsDetailEnabled}" Style = "{StaticResource toolbarButton}" > <StackPanel Orientation="Horizontal"> ... </StackPanel></Button><Button IsDefault="True" IsEnabled="{Binding Path=IsDetailEnabled}" Style="{StaticResource toolbarButton}"> <StackPanel Orientation="Horizontal"> ... </StackPanel></Button>

List Control

Open the UserMaintenanceListControl.xaml file, locate the ListView control and bind the IsListEnabled property to the IsEnabled property of this control.

< ListView ItemsSource = "{Binding Path=Users}" IsEnabled = "{Binding Path=IsListEnabled}" SelectedItem = "{Binding Path=Entity}" > ...</ListView>

Toolbar Buttons

Open the UserMaintenanceControl.xaml file and locate the buttons within the toolbar control. Bind each button's IsEnabled property to the appropriate property in the ViewModelAddEditDeleteBase class, as shown in Listing 2.

Detail User Control

The final binding is on the UserMaintenanceDetailControl user control. Bind the IsEnabled property of this user control to the IsDetailEnabled property. Binding this property on the user control is much less code than setting each input control's IsEnabled property individually.

< UserControls:UserMaintenanceDetailControl
               Grid.Row = "2" x:Name = "detailControl" IsEnabled = "{Binding Path=IsDetailEnabled}" DataContext = "{StaticResource viewModel}" />

Try it Out

Run the application and click on the Users menu item. You should see that the various buttons are all enabled or disabled because they have been bound to the Boolean properties in your view model.

Changing State

When you want to go into Add or Edit mode, call the BeginEdit() method in the view model. You do this by adding click events on each button in the toolbar and the buttons on the detail screen. You can use WPF commanding if you want to, but I prefer click events because I can follow the logic of the screen easier.

Add Events to Detail Screen

Open the UserMaintenanceDetailControl.xaml file and add a Loaded event to the <UserControl> element.

< UserControl x:Class = "WPF.Sample.UserControls.
                   UserMaintenanceDetailControl" ... mc:Ignorable = "d" d:DesignHeight = "450" d:DesignWidth = "800" Loaded = "UserControl_Loaded" >

Open the UserMaintenanceDetailControl.xaml.cs file and add a using statement at the top of this file so you can reference the view model class from the code behind.

using WPF.Sample.ViewModelLayer;

Create a private field in the screen to reference the UserMaintenanceViewModel object. In the UserControl_Loaded() event, grab the instance of the UserMaintenanceViewModel object from the DataContext and assign that value to the field _viewModel, as shown in the code below.

private UserMaintenanceViewModel _viewModel;
private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e){ _viewModel =
   (UserMaintenanceViewModel)this.DataContext;}

Open the UserMaintenanceDetailControl.xaml file and modify the Undo and Save buttons to fire a click event.

< Button IsCancel = "True" ... Click = "UndoButton_Click" Style = "{StaticResource toolbarButton}" > <StackPanel Orientation="Horizontal" ... </StackPanel></Button><Button IsDefault="True" ... Click="SaveButton_Click" Style="{StaticResource toolbarButton}"> <StackPanel Orientation="Horizontal" ... </StackPanel></Button>

In the UndoButton_Click event, call the CancelEdit() method on the view model to reset the state back to List mode. In the SaveButton_Click() event call the Save() method on the view model. The Save() method right now just calls the CancelEdit() method to reset the state back to the List mode. Later in this article, you’ll write code to save the user information.

private void UndoButton_Click(object sender, System.Windows.RoutedEventArgs e){ _viewModel.CancelEdit();}
private void SaveButton_Click(object sender, System.Windows.RoutedEventArgs e){ _viewModel.Save();}

Add Click Events to List Screen

Open the UserMaintenanceListControl.xaml file and at the top of this user control, add a Loaded event procedure to the <UserControl> element.

< UserControl x:Class = "WPF.Sample.UserControls.
                   UserMaintenanceListControl" mc:Ignorable = "d" d:DesignHeight = "450" d:DesignWidth = "800" Loaded = "UserControl_Loaded" >

Open the UserMaintenanceListControl.xaml.cs file and add two using statements so you can access classes in the data layer and the view model layer projects.

using WPF.Sample.DataLayer;using WPF.Sample.ViewModelLayer;

Create a private field in the screen to reference the UserMaintenanceViewModel object. In the UserControl_Loaded() event, grab the instance of the UserMaintenanceViewModel object from the DataContext and assign it to the field _viewModel.

private UserMaintenanceViewModel _viewModel;
private void UserControl_Loaded(object sender,
  RoutedEventArgs e){ _viewModel =
   (UserMaintenanceViewModel)this.DataContext;}

Write the code for the EditButton_Click event to set the Entity property in the view model to the value you retrieve from the button's Tag property. Once this property has been set, call the BeginEdit() method. The reason you need to set the Entity property is in case the ListView is currently displaying the first user, but the user clicks on the third user. You need the Entity property to be set to the third user instead of the one that has focus.

private void EditButton_Click(object sender,
   RoutedEventArgs e){ // Set selected item _viewModel.Entity = (User) ((Button)sender).Tag; // Go into Edit mode _viewModel.BeginEdit(false);}

Write a DeleteUser() method to be called from the DeleteButton_Click event procedure you created earlier. This method asks the user if they wish to delete the current user. If they answer Yes, call the Delete() method on the view model to delete the user from the database.

public void DeleteUser(){ // Ask if the user wants to delete this user if (MessageBox.Show("Delete User " +
    _viewModel.Entity.LastName + ", " +
    _viewModel.Entity.FirstName + "?",
      "Delete?", MessageBoxButton.YesNo)
        == MessageBoxResult.Yes) { _viewModel.Delete(); }}

Write the DeleteButton_Click event to set the Entity property on the view model from the button's Tag property. Just like you did for the Edit button, ensure that the Entity property is set to the one the user clicked on, and not the one currently selected in the DataGrid. Once the Entity property has been set, call the DeleteUser() method.

private void DeleteButton_Click(object sender,
  RoutedEventArgs e){ // Set selected item _viewModel.Entity = (User) ((Button)sender).Tag; // Delete user DeleteUser();}

Add Click Events to Toolbar

Open the UserMaintenanceControl.xaml file and add Click event procedures to each Toolbar button. The complete code for the Toolbar is shown in Listing 3. After you create each of these Click events, add the appropriate calls to the methods in the view model class, as shown in Listing 4.

Notice that the DeleteButton_Click event procedure calls the public DeleteUser() method on the UserMaintenaceListControl class. This is done because the DeleteUser() method must display a message box, and UI code doesn’t belong in a view model class.

Try it Out

Run the application and click on the Users screen and then try pressing the different buttons to watch the screen move in and out of the different states.

Begin and Cancel Edits

When a user starts making changes to the user data in the text boxes, those changes are updated into the bound properties in your view model. If the user wishes to cancel the edit mode, you must have some way of putting back the original data. One way to do this is to add another field to your view model to hold the original entity data. Open the UserMaintenanceDetailViewModel.cs file and add the following variable.

private User _OriginalEntity = new User();

You are going to set each property on this User object with the values from the currently selected user.

Override BeginEdit Method

In the UserMaintenanceDetailViewModel class, override the BeginEdit() method from the ViewModelAddEditDeleteBase class. Copy all of the properties in the Entity object and place them into the corresponding properties of the _OriginalEntity field. The CommonBase class has a Clone() method that performs this copying for you.

public override void BeginEdit(
  bool isAddMode = false){ // Create a copy in case user
        // wants undo their changes base.Clone<User>(Entity, _OriginalEntity);
  if (isAddMode) { Entity = new User(); }
  base.BeginEdit(isAddMode);}

You can't just assign _OriginalEntity property equal to the Entity property, as that creates a reference between the two objects. When you have a reference between two objects, the changes you make to one are changed in the other too. The Clone() method uses reflection to perform a GET on each property in the Entity object, then calls the set on the corresponding property in the _OriginalEntity object. This accomplishes two goals: it makes a copy of the data, and it fires each property's RaisePropertyChanged event. This is important when the user cancels the edit because you want the old values to propagate to the screen, and this is done by firing the RaisePropertyChanged event.

After cloning the user, check whether the user is adding a new user, and if so, create a new user and put it into the Entity property. The Entity property is bound to the product detail user control, so creating a new instance of the User class, all fields are displayed as blanks. Finally, call the BeginEdit() method to change the state of the UI.

Override CancelEdit Method

Override the CancelEdit() method so if the user clicks the Undo button, the Entity property is set back to what it was prior to beginning the add or edit process. You once again call the Clone() method to put all the values from the _OriginalEntity object into the Entity object.

public override void CancelEdit(){ base.CancelEdit();
  // Clone Original to Entity object // so each RaisePropertyChanged event fires base.Clone<User>(_OriginalEntity, Entity);}

Add/Update a User

It’s now time to write code to add or update a user in the user table using the Entity Framework. To start, add some using statements to the top of the UserMaintenanceDetailViewModel.cs file.

using System;using System.Collections.ObjectModel;using System.Data.Entity;using System.Data.Entity.Validation;using System.Linq;using Common.Library;

Modify the Save Method

Modify the Save() method you created earlier with the appropriate code to add or update a record in the User table. The complete Save() method is shown in Listing 5.

The Save() method creates a new instance of the SampleDbContext class. This class inherits from the Entity Framework's DbContext class. If the user is adding a new user, a random password is generated for the new user. Optionally, you could add a PasswordBox control that only shows up on the screen when you’re in add mode. This allows the person entering a new user to add a password. The new user object in the Entity property is added to the Users collection in the EF object. If the user is in Edit mode, the state of the existing entity is changed to Modified.

The SaveChanges() method is called to have EF submit the changes to the User table in SQL Server. If this call is successful, the return value is set to true and the Entity property is set to the _OriginalEntity field. If adding a new record, the new user object is added to the Users collection property in the view model. The ListView control is notified so that it can redisplay the new collection. Finally, the CancelEdit() method is called to reset the state of the form back to List mode.

Try it Out

Run the application, add a new user, then save the new user. Also, try starting the add process, but then click the Undo button to ensure that the new user is aborted and is placed back to the original user. Try editing a user and, while editing. try clicking the Undo button to ensure that the changes you made to the existing user are reverted to the original values.

Validation Messages

Add a list box to display validation messages that may arise from the user entering incorrect information. Open the UserMaintenanceDetailViewModel.xaml file and, just before the final </Grid> element, add the following XAML.

<!-- Validation Message Area --> < ListBox Grid.Row = "5" Grid.ColumnSpan = "2" Style = "{StaticResource validationArea}" Visibility = "{Binding IsValidationVisible,
    Converter={StaticResource
                 visibilityConverter}}" ItemsSource = "{Binding ValidationMessages}" DisplayMemberPath = "Message" />

If you look at the User class, you can see that it’s decorated with Data Annotations. The validation system works the same as that described for the login screen shown in the second part of this article series (CODE Magazine, July/August 2019).

Delete a User

The last bit of functionality to add to your user maintenance screen is to delete a user. Modify the Delete() method that you created earlier in the UserMaintenanceDetailViewModel class and fill in the appropriate code to delete a record from the User table, as shown in Listing 6.

Although there may seem to be a lot of code in Listing 6 for a simple delete operation, it’s necessary. If you delete a record from the User table, you also need to delete the entity object from the Users collection property in the view model class. This leaves the Entity property pointing to an invalid user and thus the ListView object isn’t highlighting a user. The screen is now in an invalid state as there’s nothing selected in the list view, and nothing being displayed in the detail area of the screen.

To avoid this invalid state, locate the user to delete in the EF Users collection. Retrieve the index of where this user is in the EF Users collection. Save this index in the variable index. Remove the user from the EF collection and call SaveChanges() on the DbContext object.

Remove the user from the Users collection property of the view model class. Find a valid user in the Users collection to set the Entity property to, so the ListView control can display a valid user. If there are no users left, set the Entity property to a null value.

Try it Out

Run the application and try deleting a user. Ensure that a valid user is selected after the delete has been run.

Summary

In this article, you learned to move from one state to another on the user maintenance screen. With just a few properties and a few lines of code, you can keep the user focused on what they’re doing, and they always know what state they’re in just by looking at the buttons. You also added code to add, edit, and delete users in the User table. Having a good set of base classes helps you follow a design pattern for standard add, edit, and delete screens. Use reflection to copy properties from a current user into another User object. This allows you to put values back if the user cancels the editing process.