views:

380

answers:

3

In my WPF application I would like to attach an input gesture to a command so that the input gesture is globally available in the main window, no matter which control has the focus.

In my case I would like to bind Key.PageDown to a command, however, as soon as certain controls receive the focus (e.g. a TextBox or TreeView control), these controls receive the key events and the command is no longer triggered. These controls have no specific CommandBindings or InputBindings defined.

This is how I define my input gesture:

XAML:

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300" >
    <StackPanel>
        <TreeView>
            <TreeViewItem Header="1">
                <TreeViewItem Header="1.1"></TreeViewItem>
                <TreeViewItem Header="1.2"></TreeViewItem>
            </TreeViewItem>
            <TreeViewItem Header="2" ></TreeViewItem>
        </TreeView>
        <TextBox />
        <Label Name="label1" />
    </StackPanel>
</Window>

Code:

using System;
using System.Windows;
using System.Windows.Input;

public static class Commands
{
    private static RoutedUICommand _myCommand;

    static Commands()
    {
        _myCommand = new RoutedUICommand("My Command",
            "My Command",
            typeof(Commands),
            new InputGestureCollection()
                {
                    new KeyGesture(Key.PageDown, ModifierKeys.None)
                });
    }

    public static ICommand MyCommand
    {
        get { return _myCommand; }
    }
}

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();

        CommandBinding cb = new CommandBinding();
        cb.Command = Commands.MyCommand;
        cb.Executed += new ExecutedRoutedEventHandler(cb_Executed);
        cb.CanExecute += new CanExecuteRoutedEventHandler(cb_CanExecute);
        this.CommandBindings.Add(cb);
    }

    void cb_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }

    void cb_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        this.label1.Content = string.Format("My Command was executed {0}", DateTime.Now);
    }
}

I already tried catching the window's PreviewKeyDown event and marking it as handled This had not the desired effect. I also set the Focusable property to false. This helped for TextBox controls, but not for the TreeView (and has the unwanted effect, that the TextBox no longer can be edited so it is not a solution for me).

So my question is how can I define a keyboard shortcut that works everywhere in the main window?

A: 

Using PreviewKeyDown is exactly what you should do... the "PreviewXYZ" events are fired from the bottom up (so the Window gets it first, then the control)... that lets you do whatever you wanted to do globaly on the "Window" level.

Then, you can choose to say "IsHandled = true" which would prevent it from going to the next control (as far as you are concerned), but you don't have to do this. If you want the event to bubble, then just add your code and leave "IsHandled" to false.

Timothy Khouri
That's what I tried. But then the key gesture is also filtered and not passed to the window.
0xA3
P.S.: Does that mean that I cannot use `InputGestures` on my commands at all but rather have to switch/case on the key events in `PreviewKeyDown` manually? I was hoping that it is possible to avoid that in WPF...
0xA3
To be honest, I'm not very good with InputGestures myself (used them maybe once and I'm not that strong). My point was just that you can do it with the PreviewKeyDown. I do know that you can absolutely come up with your own commands and use the gestures... I've done it, but only in a demo and a long time ago. - Maybe someone smarter than me will comment soon :)
Timothy Khouri
A: 

The following workaround seems to have the desired effect of having the command global to the window; however, I still wonder whether there is no easier way to do this in WPF:

private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
    foreach (InputBinding inputBinding in this.InputBindings)
    {
        KeyGesture keyGesture = inputBinding.Gesture as KeyGesture;
        if (keyGesture != null && keyGesture.Key == e.Key && keyGesture.Modifiers == Keyboard.Modifiers)
        {
            if (inputBinding.Command != null)
            {
                inputBinding.Command.Execute(0);
                e.Handled = true;
            }
        }
    }

    foreach (CommandBinding cb in this.CommandBindings)
    {
        RoutedCommand command = cb.Command as RoutedCommand;
        if (command != null)
        {
            foreach (InputGesture inputGesture in command.InputGestures)
            {
                KeyGesture keyGesture = inputGesture as KeyGesture;
                if (keyGesture != null && keyGesture.Key == e.Key && keyGesture.Modifiers == Keyboard.Modifiers)
                {
                    command.Execute(0, this);
                    e.Handled = true;
                }
            }
        }
    }
}

}

0xA3
You may also want to check InputBindings collection instead of CommandBindings. Sometimes you will not have RoutedCommand in CommandBindings...
Anvaka
Yes, I forgot about that, thanks for the tip :-)
0xA3
A: 

Hi Divo,

Unfortunately in WPF some controls have some keyboard processing hardcoded internally. The PreviewKeyDown handler in the main window is the answer to

How can I define a keyboard shortcut that works everywhere in the main window

question. And yes, it does mean that you may want to choose switch/case on the key events in the PreviewKeyDown manually...

Anvaka