Ho un comando RoutedUICommand
che può essere licenziato in due modi diversi:incoerenza nel comportamento di comando di routing WPF seconda il focus UI Stato
- direttamente tramite
ICommand.Execute
su un evento click del pulsante; - utilizzando la sintassi dichiarativa:
<button Command="local:MainWindow.MyCommand" .../>
.
Il comando viene gestito solo dalla finestra superiore:
<Window.CommandBindings>
<CommandBinding Command="local:MainWindow.MyCommand" CanExecute="CanExecuteCommmand" Executed="CommandExecuted"/>
</Window.CommandBindings>
Il primo approccio funziona solo se v'è un elemento focalizzato nella finestra. Il secondo lo fa sempre, indipendentemente dalla messa a fuoco.
Ho esaminato ICommand.Execute
implementazione di BCL e ha scoperto che il comando non viene licenziato se Keyboard.FocusedElement
è null
, quindi questo è di progettazione. Vorrei ancora chiedermelo, perché potrebbe esserci un gestore al livello più alto (come nel mio caso) che vuole ancora ricevere comandi, anche se l'app non ha un focus sull'interfaccia utente (ad esempio, potrei voler chiamare ICommand.Execute
da un'attività asincrona quando ha ricevuto un messaggio socket). Lascia che sia così, non mi è ancora chiaro perché il secondo approccio (dichiarativo) funzioni sempre indipendentemente dallo stato di messa a fuoco.
Cosa mi manca nella mia comprensione del routing del comando WPF? Sono sicuro che questo non è "un bug ma una funzionalità".
Di seguito è riportato il codice. Se ti piace giocare con esso, ecco lo full project. Fare clic sul primo pulsante: il comando verrà eseguito, poiché lo stato attivo si trova all'interno dello TextBox
. Fai clic sul secondo pulsante: tutto va bene. Fai clic sul pulsante Clear Focus
. Ora il primo pulsante (ICommand.Execute
) non esegue il comando, mentre il secondo lo fa ancora. Dovresti fare clic sullo TextBox
per far funzionare nuovamente il primo pulsante, quindi c'è un elemento focalizzato.
Questo è un esempio artificiale, ma ha imitazioni reali. Ho intenzione di inviare una domanda relativa a ospitare WinForms controlli con WindowsFormsHost
([Modificato]asked here), nel qual caso è sempre Keyboard.FocusedElement
null
quando il focus è all'interno WindowsFormsHost
(uccidere in modo efficace esecuzione del comando tramite ICommand.Execute
).
codice 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>
codice C#, la maggior parte di esso è legato alla registrazione di stato di attenzione:
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] Cambiamo leggermente il codice:
void btnClearFocus_Click(object sender, RoutedEventArgs e)
{
//FocusManager.SetFocusedElement(this, this);
FocusManager.SetFocusedElement(this, null);
Keyboard.ClearFocus();
CommandManager.InvalidateRequerySuggested();
Log("btnClearFocus_Click, {0}", FormatFocus());
}
Ora abbiamo un altro caso interessante: non attivo logico, senza messa a fuoco della tastiera, ma il comando stil viene licenziato dal secondo pulsante, raggiunge gestore della finestra superiore e Viene eseguito (che io considero il comportamento corretto):
IsFocusScope deve trovarsi sul pannello dello stack di contenimento anziché sui controlli stessi. –
Grazie per l'idea, ma lo spostamento di 'IsFocusScope =" True "' nel contenente 'StackPanel' non cambia il comportamento descritto per quando non è attivo. Cambia comunque la semantica del routing in questo modo: se 'TextBox' ** è focalizzato ** e ha ** il proprio binding ** per' MainWindow.MyCommand' (uguale alla finestra in alto), il comando generato dal secondo pulsante non raggiungerebbe mai il 'TextBox'. La finestra in alto lo inghiottirebbe per primo. Questo non è auspicabile: voglio che l'elemento dell'interfaccia utente focalizzato sia dato in primo luogo la possibilità di gestire un 'RoutedUICommand', sparato senza un bersaglio specifico. – Noseratio