IanG on Tap

Ian Griffiths in Weblog Form (RSS 2.0)

Blog Navigation

April (2018)

(1 item)

August (2014)

(1 item)

July (2014)

(5 items)

April (2014)

(1 item)

March (2014)

(1 item)

January (2014)

(2 items)

November (2013)

(2 items)

July (2013)

(4 items)

April (2013)

(1 item)

February (2013)

(6 items)

September (2011)

(2 items)

November (2010)

(4 items)

September (2010)

(1 item)

August (2010)

(4 items)

July (2010)

(2 items)

September (2009)

(1 item)

June (2009)

(1 item)

April (2009)

(1 item)

November (2008)

(1 item)

October (2008)

(1 item)

September (2008)

(1 item)

July (2008)

(1 item)

June (2008)

(1 item)

May (2008)

(2 items)

April (2008)

(2 items)

March (2008)

(5 items)

January (2008)

(3 items)

December (2007)

(1 item)

November (2007)

(1 item)

October (2007)

(1 item)

September (2007)

(3 items)

August (2007)

(1 item)

July (2007)

(1 item)

June (2007)

(2 items)

May (2007)

(8 items)

April (2007)

(2 items)

March (2007)

(7 items)

February (2007)

(2 items)

January (2007)

(2 items)

November (2006)

(1 item)

October (2006)

(2 items)

September (2006)

(1 item)

June (2006)

(2 items)

May (2006)

(4 items)

April (2006)

(1 item)

March (2006)

(5 items)

January (2006)

(1 item)

December (2005)

(3 items)

November (2005)

(2 items)

October (2005)

(2 items)

September (2005)

(8 items)

August (2005)

(7 items)

June (2005)

(3 items)

May (2005)

(7 items)

April (2005)

(6 items)

March (2005)

(1 item)

February (2005)

(2 items)

January (2005)

(5 items)

December (2004)

(5 items)

November (2004)

(7 items)

October (2004)

(3 items)

September (2004)

(7 items)

August (2004)

(16 items)

July (2004)

(10 items)

June (2004)

(27 items)

May (2004)

(15 items)

April (2004)

(15 items)

March (2004)

(13 items)

February (2004)

(16 items)

January (2004)

(15 items)

Blog Home

RSS 2.0

Writing

Programming C# 5.0

Programming WPF

Other Sites

Interact Software

Avalon Input, Commands, and Handlers

Thursday 19 May, 2005, 05:55 PM

For a long time I've been aware that Avalon has a commanding architecture, but I hadn't got around to looking at it in detail. It's one of those areas where at first glance, there seem to be slightly more pieces than you might expect. I've finally got around to getting my head around it. Here's what I've found.

(This applies to the March 2005 CTP. Things may change.)

RoutedCommand represents some operation the user can invoke. This is typically the kind of thing that might be performed using a menu item, a toolbar button, or a keyboard shortcut. For example, clipboard operations like Cut, Copy, and Paste are represented as routed commands.

Avalon defines a whole load of standard commands, such as clipboard operations, file commands like Open, New, Save, and Print, text commands like Bold, Italic and Underline, and application commands like Refresh and Stop. These are all provided as properties of the CommandLibrary. Some of the built-in controls like RichTextBox understand and respond to some of these commands automatically.

You can also define your own commands. This simply involves creating a new RoutedCommand object.

To handle a RoutedCommand, you need a CommandBinding. A CommandBinding associates a particular RoutedCommand with a particular method. A given binding is associated with a particular element. For example, you could decide to handle the CommandLibrary.New command when it hits a Grid element in your application window. A command binding can be per instance, in which case it is stored in the UIElement.CommandBindings property. Alternatively, you can set up a class-level binding using CommandManager.RegisterClassCommandBinding.

A CommandBinding offers four events: Execute, PreviewExecute, QueryEnabled, and PreviewQueryEnabled. These use the same style of routing as normal input events: there is a tunnelling Preview event followed by a bubbling main event. So when a RoutedCommand is invoked on a particular target element, both the PreviewExecute and Execute events will be raised on all of the relevant CommandBinding objects. Bindings for the command for any element in the tree from the root down to the target are all fired. The PreviewExecute fires first, starting with the root of the UI tree and working down to the target. Then the Execute is fired, starting from the target and working up to the root. Any handler may set the Handled flag at any stage to suppress further handling of the event. Routing for the [Preview]QueryEnabled events works the same way.

A command may be invoked or queried by calling its Execute or QueryEnabled method. (A command would be queried to find out if it can be invoked right now. This is used for greying out of options.) However, you would not normally write code to do this directly. There are two ways in which you would normally make use of a command: either through 'input gestures' or by telling another control to invoke the command.

An 'input gesture' is either a keyboard shortcut, represented by a KeyGesture or a particular motion made with the mouse or stylus, represented by a MouseGesture. A command may have a number of gestures associated with it when it is created. For example, the built-in CommandLibrary.Copy command is associated with two keyboard gestures: the Ctrl+C and the Ctrl+Insert shortcuts.

You can add further associations between input gestures and commands on a particular user interface element through the use of InputBinding objects. You can do this at a class level with CommandManager.RegisterClassInputBinding, or at an instance level with the InputBindings property.

Command Handling Walkthrough

Let's walk through an example, to see how it all hangs together.

The user is typing into a RichTextBox and hits Ctrl+V. The Avalon input system checks to see if this is an input gesture associated with a command. It must look in the class and instance InputBindings for all the elements in the UI tree from the root down to the RichTextBox. It must also look at the set of input gestures associated with any commands registered for handling at the instance or element level on any of the same elements.

In this example, the RichTextBox understands many of the built-in commands, including the CommandLibrary.Paste command. It will therefore have registered a class-level CommandBinding for that class. Avalon will notice that the Paste is handled by the rich text box, and that the Paste command recognizes Ctrl+V as one of its input gestures. Avalon will therefore execute the Paste command. Since the focus is in the RichTextBox, that will be the target of the command.

The Paste command will first raise its PreviewExecute event. Preview events are always 'tunnelling' events. This means Avalon will start at the root of the UI tree, and will work its way down to the RichTextBox, examining the CommandBindings (both instance-level and class-level) for each of the elements. If it finds any CommandBinding objects for the Paste command, it will call any handlers attached to that binding's PreviewExecute event. If any of these handlers set the Handled flag, command processing will stop.

Note that there may very well be no handlers for the PreviewExecute event. In which case the whole of the previous paragraph is effectively a no-op.

If the PreviewExecute handling completes without the Handled flag getting set, the Paste command will then go on to raise the Execute event. This is a bubbling event, so it starts at the target, which in this case is our RichTextBox. It has registered a class-level handler for the Paste event, which will now proceed to run. This will execute the actual paste functionality. It will also set the Handled event to indicate that the operation has now been handled. This prevents the event from bubbling up any further. And we're done.

The Simple Walkthrough

The previous section contained a load of detail that's only really interesting if you want to know exactly how the command works its way through the system. This is useful if you want to intercept command handling, or write your own commands. But in a lot of cases you just don't need to care. So here's the simple version:

The user hits Ctrl+V, the RichTextBox pastes the clipboard contents into the text. No code is required to make this work.

If you want, you can create a MenuItem or perhaps a Button on a ToolBar with the Command property set to CommandLibrary.Paste. If the user clicks on that menu item or button, the command routing behaviour in Avalon means that the RichTextBox will again just perform the paste operation. (Assuming the focus was in the RichTextBox at the time - by default, focus determines the target for a command.)

In short, Avalon's commanding means that keyboard shortcuts just work like you want them to, and it's easy to add menus and buttons that provide other ways of invoking the same functionality.

What I'm not yet clear on is how you get a MenuItem to display the shortcut for the command it represents automatically.

Copyright © 2002-2024, Interact Software Ltd. Content by Ian Griffiths. Please direct all Web site inquiries to webmaster@interact-sw.co.uk