Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

Undo/Redo Implemented via Stateless Command Stacks in WPF Applications (Sprint 1/4)

Rate me:
Please Sign up or sign in to vote.
5.00/5 (11 votes)
17 Nov 2018MIT13 min read 31.2K   503   15   6
Undo/Redo Commands implemented with Minesweeper game example

Source available on GitHub

 

Introduction

Most commercial applications support Undo/Redo yet many line-of-business and custom applications still do not. I will demonstrate how to implement this functionality and underscore considerations while planning your line-of-business applications. While it may seem like more work to implement, it can be worth the effort in terms of the quality, repeatability, supportability, and maintainability.

This application implements the Minesweeper game application shipped with Windows 3.1. It supports sounds, themes, and commands placed in undo/redo command stacks. It is written using standard MVVM/WPF application with minimal code behind and no additional libraries. Although there is brief discussion of how to build and draw the application, the emphasis will be on adding the undo/redo service. The selection of this as a platform for the demonstration is arbitrary - the only requirement is that it be fun to develop and implement undo/redo functions. For those who have never played, Minesweeper is a guessing game; you try not to select a mine (left click) if you do you lose and game is over; if you correctly flag (right click) all the mines you win.

Some Words About State

Application commands fall in to two categories: stateless and stateful. A stateless operation requires no knowledge of previous state to undo. In this sprint we will address only stateless commands; playing a piece, toggling a flag on a piece, showing and hiding the log window. You will see that the majority of the relevant commands are stateless. Also that undo is relatively easy to implement. In the next sprint we will implement commands supporting stateful undo / redo. Stateful operations include setting the theme, undoing game play operations beyond the boundary of current game, choosing parameters for a custom game, and setting the sound level. All of these require knowledge of the previous state. In the following table, notice the stateless column. You will see most commands in our domain are stateless.

Image 1
Table 1 - Most commands are stateless in our domain. Also it is interesting to note that the majority of Undo functions are not written. Yet enough functionality is implemented to unplay/replay an entire game. Place-markers for UnExecute()demarcate clarity about where to implement remaining items if they are later deemed necessary or desirable. This can aid code base maintainability greatly.

 

First Things First [Ctrl-Z][Ctrl-Y]

In addition to [Ctrl-Z][Ctrl-Y] Undo/Redo we will support undoing all commands via [Ctrl-U] and redoing all via [Ctrl-A]. I also added [ Ctrl-S] to display the current commands stack. Let’s add these to the main window:

Image 2 Image 3
Figure 1 - MainWindow.xaml Keyboard Command Bindings to the ViewModel for menu Commands)
Figure 2 - MainWindow.xaml Menu Command Bindings to the ViewModel for menu Commands

 

After creating dependency properties for the Execute and Unexecute Methods, the remainder of the article will follow the path of their execution until we have clear understanding of the path of one of our first command - Play(Row,Col). All other commands follow the identical path and can be understood in the same light. Play(Row, Col) is stateless, and there is little need to carry previous state other than the parameters (Row,Col) used which are pushed and popped in forward and backward command stack. Play and UnPlay , however, are not fully symmetric; as they require unique implementations. If you look at the "Flag" command you will see that "Flag" is a symmetric toggle and because of that symmetry they share an identical code path for Execute and UnExecute.

The Ctrl-S Shortcut leads to a view of our command stack

Image 4
Figure 3 – Ctrl-S Shows the current state of the Undo/Redo Command Stack

 

Detour – View Model/View Drawing the Board and Binding Commands

Attaching Behaviors

Image 5
Figure 4 - The Game Board shown with "Key West" Theme Applied and 9 mines hidden. One of the Mines is "Flagged" via a right-click and the Game is in the "Lost" state. The game was lost because it crossed the threshold of time in Play of 1000 seconds.

 

Attaching commands to Menus and Window Key Bindings, as I just demonstrated, is a snap. It’s completely supported by their class implementations. The Board to the left is our finished app. The "Smiley" Button and all the game pieces are Rectangle class instances. The Rectangle class, however, does not natively support a Command property. Here we will attach a behavior thereby allowing Rectangles to have assignable Command/CommandParameter properties in the ViewModel. The XAML in the following figures of this section will show how to bind commands to Rectangle Elements.

The entire command set supported is by instances of our base class RelayCommand which is found in two files - SweeperViewModel.CMDS.cs and GamePiece.CMDS.cs:

  • SweeperViewModel.CMDS.cs is a partial class of the main ViewModel (SweeperViewModel.cs) and supports the Commands associated with Game Logic.
  • GamePiece.CMDS.cs is also a partial class of GamePiece.cs and implements the logic for each of the Game Pieces on the board.

To accomplish this attach a bindable "Command" as a DependecyProperty. Let’s first look at some XAML that uses the attached behaviors to attach the commands as a property. After attaching this, I will show you how to create the attached behavior class MouseBehavior.cs

Image 6
Figure 5 - Drawing The Smiley Element - A rectangle That is filled with a brush which the Converter (faceselector) which simply selects based on GameState values provided by the ViewModel. MouseBehaviors are used to set the commands for usage in the above mouse events. We compiled the MouseBehavior class in to the application. This is also done for all the Rectangles on the GameBoard (MainWindow.xaml)

 

Make Those Rectangles Behave!

To attach a Command to the ViewModel we register DependencyProperty or behavior as it is called in WPF. The MouseLeftButtonUpCommand above is made attachable and bindable by the new DependencyProperty "MouseUpCommandPropery" with a call to DependencyProperty.RegisterAttached.

Image 7
Figure 6 – found in MouseBehavior.cs. You will find Behaviors for all the popular Mouse Events. This File is completely portable and not at all linked to this application. A thank you to Mathew Searle's in his article (here)came up with this nifty class to help attaching UI Events in to a View Model. The line at 17 I thought would prevent the Garbage Collector from getting called. It turns out not to be true. Here is the garbage collection discussion some other codeproject members have engaged on this (Discussion)

 

Board Layout

Now that you know how to attach Commands in the ViewModel and bind them to any FrameworkElement or Rectangle you are ready to layout the GameBoard. I selected the formless ItemsControl, which is simply a container for creating Views of ObservableCollections and other collections in a ViewModel. Interestingly enough, our ViewModel does not use a 2d Array or Collection. It takes a linear Observable Collection and does a transpose of the linear index at construction, saving the [Row,Column] information. I implemented the UniformGrid as the ItemTemplate that takes bound Rows and Columns to determine how large a Board you want to display. Also I left a <!—StackPanel --> comment in the XAML. It may be interesting for you to see what happens when you use StackPanel here instead of UniformGrid. You guessed it -you will see a long linear array of GamePieces! This observation will make this a candidate for refactored implementation as a user control.

Image 8

 

Figure 7 - Laying out the Board – An Items Control that binds to the Board (Observable Collection from the ViewModel) We take the linear collection and wrap the layout in form of a Uniform grid that is bound to the Rows / Columns also provided by ViewModel. GamePieceTemplate attaches mouse behaviors and associates individual Items of the collection to each Rectangle. (MainWindow.xaml)

 

 

The ItemTemplate referenced above describes each element on the Board object. GamePieceTemplate references the MouseBehavior again and inserts a Brush Converter from the Value Property found in the BoardCollection of GamePiece.Value. With a small amount of imagination you can see how this strategy and layout could be used to implement many sorts of board games, i.e., Words With Friends, Checkers, Monopoly, etc. Mix in more of your own imagination and you can invent the next big thing.

Image 9
Figure 8 – A Data Template which associates a command for each mouse gesture (MainWindow.xaml)

 

Command Class Supporting Undo/Redo

Now that the Commands are attached, it is time to look at the ICommand interface and add some fields and methods to support undo/redo and command filtering. In our example we will Execute commands in the Category types ["MOUSE" ,"OPTIONS" , "DIALOG", "STACK","GAME"]. These Commands act through the same Command class and base Execute() method, but only perform Undo/Redo on "GAME" Category commands. In effect, this method applies a filter for Undo/Redo Execution. All you need to do is add to the ICommand Interface which requires implementation of Execute() and CanExecute() methods to additionally support your new UnExecute() and CanUnExecute Methods. I added fields DisplayText to show the Command Name. Also I added a Category field to help you classify the commands, so you can use them in addition to the CanUnExecute() method. This will determine if you can undo them in your application. Additionally, there is a an alternative constructor which does not require creation of UnExecute() method. This forms a good starting point for your application development efforts. Later on you can add the UnExecute functionality.

Here is your new Constructor for an ICommand Implementation

Image 10
Figure 9 – This Constructor shows our new RelayCommand Implementation (RelayCommand.cs) There is an alternate Constructor that does not require the UnExecute method. You can come back and implement UnExecute() functionality later.

 

Following The Path of Play.Execute and Play.UnExecute Commands

Now that you have attached Commands to a Rectangle, all that remains is to implement the Execute/Unexecute Path of the PlayCommand. Eventually you will turn full circle and see how [Ctrl-Z][Ctrl-Y] featured in Figure 1 operates.

Image 11
Figure 10 - Shows all the Elements associated with the PlayCommand. The Point Parameter contains Row and Column information for the given GamePiece Element associated with the Rectangle which was pressed. This Command was invoked Via the GamePiece Command MouseLeftButton Up which was attached in XAML in Figure 8 – A Data Template which associates a command for each mouse gesture

 

The PlayCommand simply performs an Action. In other words, it plays an Element on the Board after a MouseButtonUpCommand is invoked upon a Rectangle/GamePiece element. You attached the mouseup in the Figure 8 . I took every opportunity to show that you could create an Action compliant delegate in several different forms; as in case of ExecutePlay, or you could create an anonymous <Predicate> Compliant delegate as in case of CanExecute(); or simply call a nearby intermediate nearby method on the ViewModel e.g., UnPlay. Really it is a very flexible strategy for developing your application. It avoids code behind, which is a negative for testability. Admittedly there is overhead associated with setting up attachable properties for elements that do not support setting of a "Command" property which is of key interest in this strategy. If you are unready to write an UnExecute simply call the constructor without the functions for unexecuting and default values will be assigned that you can add later. And Yes, you can write the forward path for code and return later to same point and add the reverse path.

Adding Repeatability Stacks (Undo Redo and BusTub)

Up to now you have Executed all RelayCommands through a single Execute(object) method; however, you have not stored the Execution of each Command for Redo/Undo. In the Execute Method there is an Event that is raised during Execution to notify the ViewModel to push the command into the command stack.

Image 12
Figure 11 – All Commands are Execute/UnExecuted through this single Class Method. An Event is Raised. The ViewModel is listening for the Event and stores commands in Undo/Redo Stacks for Replay

 

Storing Commands for later Undo/Redo Execution

Image 13
Figure 12 – The ViewModels Event Handler for a New Command Item Execution Element in the Queue. Some Commands are discarded because they are not in the Active Filter Category of "GAME" or for some reason they have a CanUnExecute() evaluation of false( reference implementation of CanUnExecute() in Figure 9 and Figure 10 )

 

UndoLast Methods of the ViewModel

Image 14 Image 15
Figure 13 – The UndoLast() Method of the ViewModel,
Figure 14 – The RedoLast() Method of the ViewModel() no need to Push on to the RedoStack the RedoStack was populated in the NewCommandItem Event Handler.

 

Winding Down – Full Circle

We started this journey by adding the Undo/Redo Command Items to the MainWindow.xaml ( Figure 1 ). Then injected commands and bound Rectangles to Commands in the ViewModel (Figure 5..Figure 8) Now we have come full circle showing you the simple methods that are invoked upon "Ctrl-Z" and "Ctrl-Y". It is pretty easy to implement. In another iteration we should probably make the board control a User Control to truly encapsulate the object, but the focus here is on the command stack, and how to use it to implement undo and redo service.

Conclusion and next Steps

After reading this article you can now fearlessly attach behaviors and inject command properties in to your ViewModels. Also, if I succeeded in my efforts, you should have a clear idea of how to strategically implement Undo/Redo from day one of your App Development. Also you should realize you will not be immediately burdened with reverse code path implementation, but if you inherit from a base class with default unexecuted method you can implement later.

In the next steps we will see about taking this same app and reenacting the game by using only the commands in the "MOUSE" Category, or perhaps a named set of Categories/Commands. In this incarnation of the Code – "Version 0.25" - as I am calling it we filtered and re-executed only stateless "GAME" category commands. In future sprints we will add a Command Model and Save the Commands for future replay. Possibly implement a control for the current incarnation of the Rectangle objects currently used. Possibly we also will see about going to the stack window and re-executing any selected commands not in "stack" order.

I hope you can see the power of app development in this manner. It is not hard to believe your users and tech support personnel would not appreciate such a feature. Also if you were to have enough data from your user communities, it may helpful in identifying what features of your app are actually being used and to what degree, which would be extremely valuable information in writing the next generation of your app.

Additional Thoughts

Could the output of such a model be the beginning place for construction of a Domain Specific Scripting Language for your application? There are a lot of possibilities.

Do’s and Don’ts for Planning Undo/Redo Command Stack Applications

  • Do Implement a Base Relay Command where all Commands are Executed. If you do not initially specify an UnExecute Method you can later and add the Unexecute Method.
  • Do Implement Undo / Redo early in your development cycle.
  • Do Implement a Command Stack View early as an aid to your Development cycle. Will help in debugging.
  • Do Not Assume it will be easy to implement by modifying the ViewModel and converting to Command pattern later.
  • Do Not assume state is required to implement a command’s UnExecute. I thought I needed that for the UnPlay Command. Further examination of the problem showed that to be untrue.
  • Do Not assume that all commands needs to be Unexecuted to provide useful undo/redo Functionality to your user. Please observe the number of Commands I relegated to the BusTub via unimplemented UnExecutes was large and was yet able to provide useful functionality.

Acknowledgements

Thanks to Sam Gilcrist, Athea Davis, Linda Hagood and Ken Clement my advisors and editors

History

May 14, 2015 - Initial Article Published

May 17, 2015 Added Acknowledgments

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralGood Pin
Akhil Mittal17-May-15 20:04
professionalAkhil Mittal17-May-15 20:04 
GeneralRe: Good Pin
MarkWardell1-Jun-15 14:47
MarkWardell1-Jun-15 14:47 
QuestionVoting of 5 Pin
Ganesan Senthilvel17-May-15 19:58
Ganesan Senthilvel17-May-15 19:58 
AnswerRe: Voting of 5 Pin
MarkWardell1-Jun-15 14:48
MarkWardell1-Jun-15 14:48 
QuestionNice Pin
Sacha Barber14-May-15 22:17
Sacha Barber14-May-15 22:17 
I like this more for the minesweeper than the undo/redo. Still nice

AnswerRe: Nice Pin
MarkWardell1-Jun-15 14:48
MarkWardell1-Jun-15 14:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.