The ways users have been able to interact with controls on the designer has steadily been evolving. With ActiveX controls in VB 6, users were simply presented with a rectangular region they could click and drag. Grab handles were provided entirely by the designer and were not customizable by the controls. The only mechanism control authors had were “verbs”, which were commands that appeared on the context menu for a control.
Windows Forms 1.0 slightly improved this by giving control authors a design time “paint” event that was called after their control painted. This event allowed a control author to draw additional design time UI on the surface of the control, but all user interaction with that UI had to be done by hand. Windows Forms 2.0 greatly improved this model with an object called the Behavior Service. The behavior service allowed a control designer to add a “behavior” onto a stack of behaviors. A behavior was a hook into all mouse, keyboard and menu command input and could also provide lightweight UI objects called “glyphs” that would float on top of the controls on the design surface. The behavior service enabled us to create some cool features in the Windows Forms designer like snap lines and smart tags.
For Cider, we wanted the equivalent of the behavior service but we wanted to do it in a way that felt more like WPF. We also wanted to enforce just a little more structure to the model. The behavior service is very flexible – almost too flexible – and in a few cases we spent a lot of time debugging tricky issues that cropped up because one behavior collided with another. Cider’s implementation of the behavior service is not a service at all; it is simply a collection of commands. But I’m getting ahead of myself. Let’s start at the beginning. Let’s start with commands.
Commands
WPF supports a simple commanding mechanism. Most command mechanisms today are composed of three pieces of information:
- Some unique identifier that identifies the command, such as “Copy”.
- Some binding that binds the identifier to a user gesture, such as Control-C\
- Some binding that binds the identifier to a body of code, in this case to copy data to the clipboard.
In WPF, the unique identifier is a singleton instance of an object that implements ICommand. Bindings to user gestures are handled through InputBindings, and bindings to code are handled by CommandBindings.
Cider starts with WPF’s commanding mechanism, but it extends it slightly. There are a couple of reasons for this. First, WPF’s CommandBindings are designed around what WPF calls “routed” commands, which are commands that are found based on what control currently has focus. In many scenarios this works, but in a designer controls frequently don’t get focus. In addition to focus, WPF assumes commands don’t require any state, which isn’t always true in a designer either. While the “Copy” command doesn’t require any knowledge of what mouse button or keystroke invoked the command, a command in a designer to begin dragging a control around on the page needs to know this information.
Cider’s solution to this is a simple extension of WPF’s command model. Where WPF introduces RoutedCommand for focus-based command invocations, Cider introduces ToolCommand for tool-based command invocations. Tool commands provide an additional parameter of information, called a “gesture data”, which allows the command to know more about the input gesture that invoked it. With both ToolCommand and RoutedCommand in Cider’s bag of tricks, every user action on the surface of the designer can be broken down into a series of commands.
If RoutedCommands are based on focus hierarchy, what are ToolCommands based on? Why Tools, of course. That brings us to the next section.
Tools
Designers have several modes of operation. The Windows Forms designer, for example, has three modes. It has a mode where you can select and interact with items on the surface, a mode where you can create new controls, and a mode where you can select the tab index for controls that already exist. The same mouse gestures perform different functions in each mode. To Cider, the mode of the designer is defined by the currently active “Tool”. A tool is an object that contains a collection of input and command bindings. Cider routes all user input through the input bindings on the currently active tool. For example, if the user pressed the left button down, Cider routes that event through the active tool’s collection of input bindings. If an input binding is found that matches that event, the command associated with the input binding is invoked.
This simple mechanism provides a great deal of flexibility. Want a new mode for the designer? Simply activate a new tool. In many ways tools map directly to the Windows Forms behavior service, except that tools are not stack based so you can’t “push” a new tool. A new tool simply takes the place of the old.
The behavior service’s ability to push a new behavior temporarily and then pop it when you’re done doesn’t exist on Cider’s Tool class, and that’s a shame because it allows a developer to temporarily establish a local scope of commands. For example, if you wanted to cleanly implement a drag gesture, you would add a command in the tool that was bound to a mouse button being pressed. When that command was invoked you’d want to create a local scope of commands: you only want mouse move and mouse up to be active. We originally tried to overload Tool to support this mechanism, but it became unwieldy. Instead, we’ve added this local scoping mechanism to a Task, which we’ll talk about next.
Tasks
Simply put, a Task is a collection of command and input bindings. Tasks allow you to group several related commands together. For example, Cider has a “drag task” that is used as the basis for dragging elements around on the designer. This task has three commands: “Begin Drag”, which is mapped to an input gesture that occurs when the user presses the left mouse button down and moves the mouse a little bit, “Drag”, which is bound to a mouse move, and “End Drag” which is bound to the left mouse button being released. Cider offers public events on this class that we wire to event handlers all over the place to implement different kinds of drags.
How do tasks relate to tools? Well a tool doesn’t really contain a collection of commands like I said earlier. Instead, it contains a collection of tasks. So tools contain a collection of tasks and tasks contain collections of input and command bindings. Whew. In practice we’ve found that we stay mostly focused on the task at hand, and the fact that a tool contains a collection of tasks is largely an implementation detail.
There’s an additional thing I need to mention about tasks: there are two kinds. There are “normal” tasks, which are simply containers for the collections I mentioned. But, there is also a “transacted” task. A transacted task is a task that has a defined beginning and ending. Cider’s drag task is a good example. It has a beginning – when the user pressed the left mouse button – and an ending – when the user released the button. Transacted tasks add an activate/commit/abort mechanism to the base task class. When a transacted task is activated, two things happen:
- The task opens a change group in the Cider editing model. A change group tracks all changes made in the designer. When the group is committed, a single change is recorded in the undo manager. If the group is aborted, all changes within the group are rolled back. By opening a change group, the transacted task is seen as a single operation by the end user.
- The task tells the currently active tool that it is the active task. If there is an active task on a tool, the tool will only lookup commands and input bindings on that task.
This addition of transacted tools provides the missing functionality that the stack in the behavior service provided, but it provides that functionality in a more structured manor. This allows Cider to behave more intelligently without developers specifically coding for some scenarios. For example, when you drag a control in Cider, the VS toolbar icons for cut/copy/delete/paste are automatically disabled. This is because these commands are offered as tasks on the selection tool, but during a drag the transacted task that runs the drag effectively disables all other commands.
The Designer View
Cider introduces new types of commands to WPF but of course WPF knows nothing about them. Cider teaches WPF about these different constructs by introducing a new element to WPF called DesignerView. DesignerView is a WPF Decorator that wraps all content on the designer. DesignerView maps user input to Cider’s notion of tool-based commands and can be placed anywhere in a XAML file. DesignerView also maps WPF’s RoutedCommands through tools and tasks too, so in cases where you don’t need the additional data provided by a ToolCommand you can simply use a WPF routed command. This allows your commands to be compatible with WPF menus and buttons.
DesignerView also offers the ability to add adorners to elements on the design surface. Adorners are WPF elements that float on top of the design surface and track the size and location of the elements they are adorning. All of the UI Cider presents to the user, including snap lines, grab handles and grid rulers, is composed of these adorners. Adorners can contain any control, and an adorner can be associated with a task so they can participate in Cider’s commanding mechanism. Adorners can also get focus and be treated just like normal WPF elements, because that’s what they are. I’ll dig deeper into adorners in a later post.
Conclusion
I’ve given you a brief overview of how Cider’s input mechanism works. What I’ve talked about here lays the foundation for all user-interactive features in Cider. Throughout this description I’ve never mentioned how control authors take advantage of adding tasks to their own controls. That’s because Cider offers two layers. An “enabling” layer, which provides the key features of tools, tasks and adorners, and an “extensibility” layer which provides a way for authors to use metadata to declare the tasks that should be enabled for a given scenario. I’ll talk about the extensibility layer in a subsequent post.