Routed Commands Trap Bubbling Events

I recently started working at the New York Times on the extremely cool Times Reader application.  It’s a WPF application which allows you to read the New York Times using WPF’s high-fidelity flow document support, along with many other slick features.

Luckily for me, I get to work with Nick Thuesen.  He worked with the WPF team at Microsoft while developing the Times Reader, so he knows a lot about WPF.  He mentioned the other day that routed commands affect the bubbling of routed events, which I had never seen mentioned anywhere in the docs.  Tonight I tested it out and, sure enough, Nick was right.  Allow me to explain…

Routed commands are really quite similar to routed events.  They are conceptually just notifications that tunnel down and bubble up the element tree.  In fact, they are so similar that if you process a routed command in response to a routed event, the event stops bubbling up the tree.  I’ve put together a simple demo which proves the point.  You can download it here (remove the .doc file extension and then decompress it).

Let’s say my application should allow the user to capitalize text anywhere in the app by pressing the F12 key.  I might create a command like this:

public class CapitalizeCommand : RoutedCommand
{
 static CapitalizeCommand instance;
 public static CapitalizeCommand Instance
 {
  get
  {
   return
    CapitalizeCommand.instance ??
    (CapitalizeCommand.instance = new CapitalizeCommand());
  }
 }
 private CapitalizeCommand()
 {
  base.InputGestures.Add( new KeyGesture( Key.F12 ) );
 }
}

Next I would create a Window (or Page) which contains an input field.  That TextBox needs a CommandBinding which ties the CapitalizeCommand to itself, so that pressing F12 will do the capitalization magic.  The following XAML is in a Window:

<StackPanel Name="stk">
  <TextBox
    Name="txtBox"
    Text="Hello"
    PreviewKeyDown="txtBox_KeyDownOrPreviewKeyDown"
    KeyDown="txtBox_KeyDownOrPreviewKeyDown"
    >
    <TextBox.CommandBindings>
      <CommandBinding
        Command="{x:Static local:CapitalizeCommand.Instance}"
        Executed="OnCmdExecuted"
        CanExecute="CanCmdExecute"
        />
    </TextBox.CommandBindings>
  </TextBox>
  <TextBlock>Press F12 to capitalize the text...</TextBlock>
</StackPanel>

Here are the event handlers in the Window’s code-behind:

void txtBox_KeyDownOrPreviewKeyDown(
  object sender,
  KeyEventArgs e )
{
 TextBlock msg = new TextBlock();
 msg.Text = String.Format(
  "[{0}]  {1} received @ {2}",
  e.Key,
  e.RoutedEvent.Name,
  DateTime.Now.ToLongTimeString() );
this.stk.Children.Add( msg );
}
void CanCmdExecute(
  object sender,
  CanExecuteRoutedEventArgs e )
{
 e.CanExecute =
  !String.IsNullOrEmpty( this.txtBox.Text );
}
void OnCmdExecuted(
  object sender,
  ExecutedRoutedEventArgs e )
{
 this.txtBox.Text = this.txtBox.Text.ToUpper();
}

Once you run the application, click on the TextBox, and press the F12 key, the TextBox’s text will be capitalized but you’ll notice that the bubbling KeyDown event was never received (as is evident by the missing log message).  Here’s what the application looks like when you run it and press the ‘S’ key and then F12:

Evidence of routed commands trapping routed events.

Notice that the KeyDown event was not logged for the F12 keystroke.  This is because the mere act of executing the CapitalizeCommand effectively handled the KeyDown event, thus preventing it from bubbling any further.  Of course, if you need to you can always subscribe to bubbling event and indicate that your handler should always be invoked, whether it was handled previously or not.  You can run this line of code to achieve that:

this.txtBox.AddHandler(
  TextBox.KeyDownEvent,
  new KeyEventHandler( txtBox_KeyDown ),
  true );

Thanks for the inside scoop, Nick!

3 Responses to Routed Commands Trap Bubbling Events

  1. Rob Relyea says:

    Josh-
    Congrats on your new gig! I will be happy to give you the list of things I’d love to see the Times Reader start doing…
    Love it already…but it can still get much better!
    -Rob

  2. Josh Smith says:

    Rob,

    I’m excited to be working on the Times Reader. I’d love to read your list of feature requests for the app. You have my e-mail address, so please feel free to send your ideas my way whenever you want to.

    Thanks,
    Josh

  3. Kevz says:

    Hi Josh,

    I tried to add a routed command to a button and also the Click event and i was able to get both of them executed … Is it that it works separately for separate controls ?

%d bloggers like this: