У меня есть команда RoutedUICommand
, которую можно запустить двумя разными способами:
- непосредственно через
ICommand.Execute
при событии нажатия кнопки; - с использованием декларативного синтаксиса:
<button Command="local:MainWindow.MyCommand" .../>
.
Команда обрабатывается только в верхнем окне:
<Window.CommandBindings>
<CommandBinding Command="local:MainWindow.MyCommand" CanExecute="CanExecuteCommmand" Executed="CommandExecuted"/>
</Window.CommandBindings>
Первый подход работает только в том случае, если в окне есть сфокусированный элемент. Второй всегда делает, независимо от фокуса.
Я просмотрел реализацию BCL ICommand.Execute
и обнаружил, что команда не запускается, если Keyboard.FocusedElement
is null
, так что это по дизайну. Я все еще сомневаюсь в этом, потому что может быть обработчик на верхнем уровне (как и в моем случае), который все еще хочет получать команды, даже если приложение не имеет фокуса пользовательского интерфейса (например, я могу позвонить ICommand.Execute
из задачи async, когда он получил сообщение сокета). Пусть это так, мне все еще непонятно, почему второй (декларативный) подход всегда работает независимо от состояния фокусировки.
Что мне не хватает в моем понимании маршрутизации команд WPF? Я уверен, что это "не ошибка, а функция".
Ниже приведен код. Если вам нравится играть с ним, здесь полный проект. Нажмите первую кнопку - команда будет выполнена, потому что фокус находится внутри TextBox
. Нажмите вторую кнопку - все в порядке. Нажмите кнопку Clear Focus
. Теперь первая кнопка (ICommand.Execute
) не выполняет команду, а вторая - еще. Вам нужно будет щелкнуть по TextBox
, чтобы первая кнопка снова работала, поэтому есть сфокусированный элемент.
Это искусственный пример, но он имеет реальные последствия. Я собираюсь опубликовать связанный с этим вопрос о размещении элементов управления WinForms с WindowsFormsHost
([EDITED] здесь), и в этом случае Keyboard.FocusedElement
всегда null
, когда фокус находится внутри WindowsFormsHost
(эффективно убивая выполнение команды через ICommand.Execute
).
Код XAML:
<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>
Код С#, большинство из них связано с регистрацией состояния фокусировки:
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));
}
}
}
[ОБНОВЛЕНИЕ] Немного измените код:
void btnClearFocus_Click(object sender, RoutedEventArgs e)
{
//FocusManager.SetFocusedElement(this, this);
FocusManager.SetFocusedElement(this, null);
Keyboard.ClearFocus();
CommandManager.InvalidateRequerySuggested();
Log("btnClearFocus_Click, {0}", FormatFocus());
}
Теперь у нас есть еще один интересный случай: нет логического фокуса, нет фокуса клавиатуры, но команда stil запускается второй кнопкой, достигает обработчика верхнего окна и запускается (что я считаю правильным):