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):