The AGT (Argentum Tela) series of articles is an effort to do two things. Usually an idea is presented only in its finished form. The first goal is to do some Reality Blogging, to show an idea evolve over time without pulling any punches. The second goal, and the example vehicle for the evolution aspect, is an extensible Design Surface for Silverlight similar to what we have in Visual Studio 2008. This type of application has all sorts of interesting uses. My example is a Home Theater layout tool. Read the entire saga: http://www.damonpayne.com/2008/09/14/RunTimeIsDesignTimeForAGT0.aspx
Gestures, Commands, Transactions, and Undo
There's so many sexy things coming up, it was difficult to postpone them further. There's two more important categories of plumbing that needed to be built. We'll cover the design of one category and its pieces in this article followed by two dedicated implementation articles. The other functionality, Document Types and Serialization, will be next.
I would like to be able to use the keyboard for some gestures on the design surface: for example the arrow keys should move the selection and I should be able to delete items from the surface with the delete or use CTRL-Z for undo.
Additionally, I would like to support some kind of undo. The plumbing needed for undo will allow for other cool interactions later...
Implementing Undo using Transactions and Commands
Undo can be an incredibly hairy thing. In my opinion, going back to a working system and suddenly trying to add undo can be a very good test of how well designed the system is. Those following along should take careful note of the degree to which the changes we see are additive (feels like we're just adding another feature) versus things that are refactoring (changing code that was already working to support this new feature).
Now to reveal a personal bias: I seldom find a use for "Redo" as it is sometimes implemented. For example in MS-Word you can apply some formatting to text. You can then use "redo" to apply the same formatting to some other text. I'm really only interested in in redo as an Undo-for-Undo. So Move something, undo, no wait I really wanted to do that, redo.
This is something that's important to get right so let's list the primary goals:
- The changes made to items on the surface are easily captured to be undone or redone. This should be true for both surface interactions and property editing.
- The number of undo levels should not be artificially limited. In some cases an undo won't "work" because the state of the system has changed - we don't want Units of Undo to have to know about other Units of Undo.
- There should be a centralized means of manipulating, creating, or querying currently known Units of Undo.
- Interactions should be able to be grouped together where it makes sense to do so. For example, moving an IDesignableControl around on the surface may in fact be 200 small changes, but we really only care about the starting position and the final position when the move interaction is over.
Item #4 in particular is important to me. Visual Studio sometimes has some odd behaviors where what is perceived as a single Gesture by the user actually requires several Undo operations to erase. For example, I might paste some code in or use a shortcut Chord and the first undo changes the formatting of the created code and the second undo actually gets rid of the code.
Gestures and Commands
Would it be best to try to mimic the Commanding system from WPF, even partially? I debated this point long and hard: is it worth some research, some effort, some potential confusion due to subtle differences?
I played around on several different occasions and I did come up with some things that I liked better than the other "Commanding in Silverlight" code I've seen out there but for now I didn't want to use it. Specifically, the ability to map keyboard gestures to a command from XAML was problematic. Still, Silverlight does contain the ICommand interface, and I tend to like the Command Pattern for encapsulating units of work, so by this time I had most of a design in mind:
Controls that need to affect the change of something living on the Design Surface will go through some kind of Change Service. This is similar, but not exactly like, how Visual Studio does this today. Using this design I will allow interested parties to be privy to (but not cancel) all the changes going on in case this is needed for some custom logic. The IDesignerChangeService will also be the gateway to creating and undoing transactions. This bears some disucssion.
The IDesignerTransactionService and IDesignerChangeService are both public and overridable or replaceable, but they work in tandem. In any circumstance I can immediately think of, client code will want to deal strictly with the change service. The process will look like this:
Note that it will be up to the Client who is initiating the change to determine if gestures need to be grouped together into a transaction or not. No one else would know! We'll explore more about how we accomplish this in the forthcoming implementation article. The stimuli shown here are not going to map directly to real method calls, but this gives you a good idea of what's going to happen as objects are edited.
I do like the way WPF creates some ways of specifying commonly used UI commands. I can also see how the ability to customize the mapping from gestures to specific ICommand implementations would be useful. Towards these goals, I am going to build some infrastructure modeled after what's in WPF.
These classes all exist in WPF. My goal here is that I will mimic the WPF functionality as closely as possible and provide a public interface to InputBindingCollection. With this, an application that wishes to customize the keyboard behavior of the design surface can simply replace the default ICommand for a gesture.
Over the course of the AGT project and several other efforts, I've written (or at least started) some useful things for Silverlight that may have usefulness beyond the AGT effort. I'm going to be moving these things into a new project: DamonPayne.AGContrib. For the sake of my time, this will be a part of the AGT solution for now, until I get time to move it into it's own CodePlex project.
The first denizen of the AGContrib project will be the Input Binding framework as shown above.
I should most likely move some other handy code from the DamonPayne.AG.IoC project into AGContrib as well. When I get some more time in the coming weeks, I will also be evaluating Unity 1.2 for Silverlight, possibly deprecating my own custom container in favor of it.
Conclusion and Next Steps
It's been a while since I've done an AGT article that's entirely design without code. I'm trying to keep the articles reasonably bite-sized. I've checked in the most recent design documents, CodePlex change set 10098. In the next article I'll implement the all the undo functionality.