23

I have a RoutedUICommand command which can be fired in two different ways:

  • directly via ICommand.Execute upon a button click event;
  • using declarative syntax: <button Command="local:MainWindow.MyCommand" .../>.

The command is handled only by the top window:

<Window.CommandBindings>
    <CommandBinding Command="local:MainWindow.MyCommand" CanExecute="CanExecuteCommmand" Executed="CommandExecuted"/>
</Window.CommandBindings>

The first approach only works if there is a focused element in the window. The second one always does, regardless of the focus.

I've looked into BCL's ICommand.Execute implementation and found that the command doesn't get fired if Keyboard.FocusedElement is null, so this is by design. I'd still question that, because there might be a handler on the top level (like in my case) which still wants to receive commands, even if the app doesn't have a UI focus (e.g., I may want to call ICommand.Execute from an async task when it has received a socket message). Let it be so, it is still unclear to me why the second (declarative) approach always works regardless of the focus state.

What am I missing in my understanding of the WPF command routing? I'm sure this is 'not a bug but a feature'.

Below is the code. If you like to play with it, here's the full project. Click the first button - the command will get executed, because the focus is inside the TextBox. Click the second button - all is fine. Click the Clear Focus button. Now the first button (ICommand.Execute) doesn't execute the command, while the second one still does. You'd need to click into the TextBox to make the first button work again, so there's a focused element.

This is an artificial example, but it has real-life imlications. I'm going to post a related question about hosting WinForms controls with WindowsFormsHost ([EDITED] asked here), in which case Keyboard.FocusedElement is always null when the focus is inside WindowsFormsHost (effectively killing command execution via ICommand.Execute).

XAML code:

<Window x:Class="WpfCommandTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfCommandTest" 
        Title="MainWindow" Height="480" Width="640" Background="Gray">

    <Window.CommandBindings>
        <CommandBinding Command="local:MainWindow.MyCommand" CanExecute="CanExecuteCommmand" Executed="CommandExecuted"/>
    </Window.CommandBindings>

    <StackPanel Margin="20,20,20,20">
        <TextBox Name="textBoxOutput" Focusable="True" IsTabStop="True" Height="300"/>

        <Button FocusManager.IsFocusScope="True" Name="btnTest" Focusable="False" IsTabStop="False" Content="Test (ICommand.Execute)" Click="btnTest_Click" Width="200"/>
        <Button FocusManager.IsFocusScope="True" Focusable="False" IsTabStop="False" Content="Test (Command property)" Command="local:MainWindow.MyCommand" Width="200"/>
        <Button FocusManager.IsFocusScope="True" Name="btnClearFocus" Focusable="False" IsTabStop="False" Content="Clear Focus" Click="btnClearFocus_Click" Width="200" Margin="138,0,139,0"/>
    </StackPanel>

</Window>

C# code, most of it is related to the focus state logging:

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

namespace WpfCommandTest
{
    public partial class MainWindow : Window
    {
        public static readonly RoutedUICommand MyCommand = new RoutedUICommand("MyCommand", "MyCommand", typeof(MainWindow));
        const string Null = "null";

        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += (s, e) => textBoxOutput.Focus(); // set focus on the TextBox
        }

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

        void CommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            var routedCommand = e.Command as RoutedCommand;
            var commandName = routedCommand != null ? routedCommand.Name : Null;
            Log("*** Executed: {0} ***, {1}", commandName, FormatFocus());
        }

        void btnTest_Click(object sender, RoutedEventArgs e)
        {
            Log("btnTest_Click, {0}", FormatFocus());
            ICommand command = MyCommand;
            if (command.CanExecute(null))
                command.Execute(null);
        }

        void btnClearFocus_Click(object sender, RoutedEventArgs e)
        {
            FocusManager.SetFocusedElement(this, this);
            Keyboard.ClearFocus();
            Log("btnClearFocus_Click, {0}", FormatFocus());
        }

        void Log(string format, params object[] args)
        {
            textBoxOutput.AppendText(String.Format(format, args) + Environment.NewLine);
            textBoxOutput.CaretIndex = textBoxOutput.Text.Length;
            textBoxOutput.ScrollToEnd();
        }

        string FormatType(object obj)
        {
            return obj != null ? obj.GetType().Name : Null;
        }

        string FormatFocus()
        {
            return String.Format("focus: {0}, keyboard focus: {1}",
                FormatType(FocusManager.GetFocusedElement(this)),
                FormatType(Keyboard.FocusedElement));
        }
    }
}

[UPDATE] Let's change the code slightly:

void btnClearFocus_Click(object sender, RoutedEventArgs e)
{
    //FocusManager.SetFocusedElement(this, this);
    FocusManager.SetFocusedElement(this, null);
    Keyboard.ClearFocus();
    CommandManager.InvalidateRequerySuggested();
    Log("btnClearFocus_Click, {0}", FormatFocus());
}

Now we have another interesting case: no logical focus, no keyboard focus, but the command stil gets fired by the second button, reaches the top window's handler and gets executed (which I consider the correct behavior):

enter image description here

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • IsFocusScope should be on the containing stack panel rather than the controls themselves. – Gayot Fow Aug 25 '13 at 01:15
  • 1
    Thanks for the idea, but moving `IsFocusScope="True"` out to the containing `StackPanel` doesn't change the described behavior for when there is no focus. It does however change the routing semantic this way: if `TextBox` **is focused** and has **its own binding** for `MainWindow.MyCommand` (same as the top window), the command generated by the second button would never reach the `TextBox`. The top window would swallow it first. This is undesirable: I want the focused UI element to be first given a chance to handle a `RoutedUICommand`, fired without a specific target. – noseratio Aug 25 '13 at 02:14

3 Answers3

14

Okay, I'll try to describe the issue, as I understand it. Let's start with a quote from the MSDN section with FAQ (Why are WPF commands not used?):

Additionally, the command handler that the routed event is delivered to is determined by the current focus in the UI. This works fine if the command handler is at the window level, because the window is always in the focus tree of the currently focused element, so it gets called for command messages. However, it does not work for child views who have their own command handlers unless they have the focus at the time. Finally, only one command handler is ever consulted with routed commands.

Please pay attention to the line:

who have their own command handlers unless they have the focus at the time.

It is clear that when the focus is not, the command will not be executed. Now the question is: what is the documentation mean focus? This refers to the type of focus? I remind there are two types of focus: logical and keyboard focus.

Now let a quote from here:

The element within the Windows focus scope that has logical focus will be used as the command target. Note that it's the windows focus scope not the active focus scope. And it's logical focus not keyboard focus. When it comes to command routing FocusScopes remove any item you place them on and it's child elements from the command routing path. So if you create a focus scope in your app and want a command to route in to it you will have to set the command target manually. Or, you can not use FocusScopes other than for toolbars, menus etc and handle the container focus problem manually.

According to these sources, it is possible to assume that the focus must be active, i.e. an element that can be used with keyboard focus, for example: TextBox.

To further investigate, I am a little changed your example (XAML section):

<StackPanel Margin="20,20,20,20">
    <StackPanel.CommandBindings>
        <CommandBinding Command="local:MainWindow.MyCommand" CanExecute="CanExecuteCommmand" Executed="CommandExecuted"/>
    </StackPanel.CommandBindings>
    
    <TextBox Name="textBoxOutput" Focusable="True" IsTabStop="True" Height="150" Text="WPF TextBox&#x0a;"/>

    <Menu>
        <MenuItem Header="Sample1" Command="local:MainWindow.MyCommand" />
        <MenuItem Header="Sample2" />
        <MenuItem Header="Sample3" />
    </Menu>

    <Button FocusManager.IsFocusScope="True" 
            Name="btnTest" Focusable="False" 
            IsTabStop="False" 
            Content="Test (ICommand.Execute)" 
            Click="btnTest_Click" Width="200"/>
    
    <Button FocusManager.IsFocusScope="True" 
            Content="Test (Command property)"
            Command="local:MainWindow.MyCommand" Width="200"/>
    
    <Button FocusManager.IsFocusScope="True" 
            Name="btnClearFocus" Focusable="False" 
            IsTabStop="False" Content="Clear Focus"
            Click="btnClearFocus_Click" Width="200"
            Margin="138,0,139,0"/>
</StackPanel>

I added the command in StackPanel and added Menu control. Now, if you click to clear focus, controls associated with the command, will not be available:

enter image description here

Now, if we click on the button Test (ICommand.Execute) we see the following:

enter image description here

Keyboard focus is set on the Window, but the command still does not run. Once again, remember the note, the above:

Note that it's the windows focus scope not the active focus scope.

He does not have an active focus, so the command does not work. It will only work if the focus is active, set to TextBox:

enter image description here

Let's go back to your original example.

Clearly, the first Button does not cause the command, without the active focus. The only difference is that in this case, the second button is not disabled because there is no active focus, so clicking on it, we call the command directly. Perhaps, this is explained by a string of MSDN quotes:

This works fine if the command handler is at the window level, because the window is always in the focus tree of the currently focused element, so it gets called for command messages.

I think, I found another source that should explain this strange behavior. Quote from here:

Menu items or toolbar buttons are by default placed within a separate FocusScope (for the menu or toolbar respectively). If any such items trigger routed commands, and they do not have a command target already set, then WPF always looks for a command target by searching the element that has keyboard focus within the containing window (i.e. the next higher-up focus scope).

So WPF does NOT simply look up the command bindings of the containing window, as you'd intuitively expect, but rather always looks for a keyboard-focused element to set as the current command target! Apparently the WPF team took the quickest route here to make built-in commands such as Copy/Cut/Paste work with windows that contain multiple text boxes or the like; unfortunately they broke every other command along the way.

And here's why: if the focused element within the containing window cannot receive keyboard focus (say, it's a non-interactive image), then ALL menu items and toolbar buttons are disabled -- even if they don't require any command target to execute! The CanExecute handler of such commands is simply ignored.

Apparently the only workaround for problem #2 is to explicitly set the CommandTarget of any such menu items or toolbar buttons to the containing window (or some other control).

Community
  • 1
  • 1
Anatoliy Nikolaev
  • 22,370
  • 15
  • 69
  • 68
  • Thanks for your research, I'm glad I'm not alone :) I believe I've got a good grasp of the concept of logical focus and its scope (albeit indeed confusing; IMO, the best explanation is [here](http://stackoverflow.com/a/10834342/1768303)). That's why I've put `FocusManager.IsFocusScope="True"` on buttons (to make them behave like a menu item) and I don't need another focus scope besides the main window's one. Check my latest update to see what I mean. What matters here, I think, is that in the second case the command *does have an invoker* (the button), while in the first case it doesn't. – noseratio Aug 26 '13 at 13:11
  • Regarding your link to the [MSDN article on PRISM](http://msdn.microsoft.com/en-us/library/ff921126.aspx), IMO it's actually quite indicatory how they discourage the native WPF commands for a serious MVVM WPF framework. – noseratio Aug 26 '13 at 13:13
  • 1
    I've up-voted your answer for the useful infromation and as acknowledgement for your research. Though, I'm still looking for an explanation on why `ICommand.CanExecute/Execute` case doesn't work, especially in the light of this quote: *Additionally, the command handler that the routed event is delivered to is determined by the current focus in the UI. This works fine if the command handler is at the window level, because the window is always in the focus tree of the currently focused element, so it gets called for command messages.* – noseratio Aug 26 '13 at 14:21
  • @Noseratio: About your update, the line `CommandManager.InvalidateRequerySuggested();` your forces the CommandManager to raise the RequerySuggested event bypassing the `Button.Click` event (which should give a message to the system of its execution).It applies in conditions that cause a command to not be able to execute. MSDN [link](http://msdn.microsoft.com/en-us/library/system.windows.input.commandmanager.invalidaterequerysuggested.aspx). I think all the focus on the issue, and the fact that the documentation does not describe everything in detail, namely, what the focus, and what type, etc. – Anatoliy Nikolaev Aug 26 '13 at 14:56
  • @Noseratio: Plus, your update with `CommandManager.InvalidateRequerySuggested();` is not worked for me. The command is not executed (tested on Windows XP), may I blame for this. – Anatoliy Nikolaev Aug 26 '13 at 15:01
  • Anatoliy, the major change was `FocusManager.SetFocusedElement(this, null)` rather than `InvalidateRequerySuggested`. I added `InvalidateRequerySuggested` just in case, to reinforce the command state update. I just removed it and it changed nothing. I guess you're seeing a different behavior because XP is limited to .NET 4.0. I'm testing it with .NET 4.5 under Win8. – noseratio Aug 26 '13 at 15:10
  • @Noseratio: Please see my edit, I found another source that could shed light on our questions. – Anatoliy Nikolaev Aug 26 '13 at 18:37
  • 1
    Anatoliy, to test your theory I did this: 1) changed the code to use standard `ApplicationCommands.Paste`, 2) added a menu item next to the 2nd button, as shown here: http://pastebin.com/um3nhSqV. That did not change the existing behavior a bit. When there is no focus, I still can execute `Paste` via the menu (still enabled) or the 2nd button, and the command is handled by the top window. – noseratio Aug 26 '13 at 19:27
5

To elaborate on Noseratio's answer, RoutedCommand implements ICommand explicitly but also has its own Execute and CanExcute methods that take an additional target parameter. When you call RoutedCommand's explicit implementation of ICommand.Execute and ICommand.CanExcute, it will call its own version of these functions passing null as the target. If target is null, it will default to using Keyboard.FocusedElement. If target is still null after that (ie nothing has focus), the main body of the function is skipped and it just returns false. See the RoutedCommand source code on line 146 and 445.

If you know the command is a RoutedCommand you can get around the focus issue by calling RoutedCommand.Execute(object, IInputElement) instead and provide a target. Here's a relevant extension method I wrote:

public static void TryExecute(this ICommand command, object parameter, IInputElement target)
{
    if (command == null) return;

    var routed = command as RoutedCommand;
    if (routed != null)
    {
        if (routed.CanExecute(parameter, target))
            routed.Execute(parameter, target);
    }
    else if (command.CanExecute(parameter))
        command.Execute(parameter);
}

For custom controls, I would typically call it like Command.TryExecute(parameter, this).

user12034691
  • 51
  • 1
  • 1
3

JoeGaggler, a colleague of mine, has apparently found the reason for this behavior:

I think I found it using reflector: if the command target is null (i.e. keyboard focus is null), then the ICommandSource uses itself (not the window) as the command target, which ultimately hits the CommandBinding for the window (this is why the declarative binding works).

I'm making this answer a community wiki, so I don't get credits for his research.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486