Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/281.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# WPF命令路由行为中的不一致性取决于UI焦点状态_C#_.net_Wpf_Xaml_Focus - Fatal编程技术网

C# WPF命令路由行为中的不一致性取决于UI焦点状态

C# WPF命令路由行为中的不一致性取决于UI焦点状态,c#,.net,wpf,xaml,focus,C#,.net,Wpf,Xaml,Focus,我有一个RoutedUICommand命令,它可以通过两种不同的方式启动: 直接通过ICommand。在按钮单击事件中执行 使用声明性语法: 该命令仅由顶部窗口处理: <Window.CommandBindings> <CommandBinding Command="local:MainWindow.MyCommand" CanExecute="CanExecuteCommmand" Executed="CommandExecuted"/> </Wind

我有一个
RoutedUICommand
命令,它可以通过两种不同的方式启动:

  • 直接通过
    ICommand。在按钮单击事件中执行
    
    
  • 使用声明性语法:
该命令仅由顶部窗口处理:

<Window.CommandBindings>
    <CommandBinding Command="local:MainWindow.MyCommand" CanExecute="CanExecuteCommmand" Executed="CommandExecuted"/>
</Window.CommandBindings>
C#代码,大部分与焦点状态日志记录有关:

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被第二个按钮触发,到达顶部窗口的处理程序并被执行(我认为正确的行为):


好的,我会尽力描述我所理解的问题。让我们从FAQ部分的一句话开始(
为什么不使用WPF命令?
):

此外,路由事件传递到的命令处理程序由UI中的当前焦点确定。如果命令处理程序位于窗口级别,则这种方法可以正常工作,因为窗口始终位于当前焦点元素的焦点树中,因此它会被调用以获取命令消息。但是,它不适用于具有自己的命令处理程序的子视图,除非它们当时具有焦点。最后,只有一个命令处理程序会被路由命令查询

请注意线路:

他们有自己的命令处理程序,除非他们当时有焦点

很明显,当焦点不在时,该命令将不会执行。现在的问题是:文档是什么意思?这是指焦点的类型?我提醒大家有两种焦点:逻辑焦点和键盘焦点

现在让我们引用一下:

Windows焦点范围内具有逻辑焦点的元素将用作命令目标。
注意
这是windows焦点作用域,而不是活动焦点作用域。这是逻辑焦点,而不是键盘焦点。当涉及到命令路由聚焦镜时,请从命令路由路径中删除放置它们的任何项及其子元素。因此,如果您在应用程序中创建一个焦点作用域,并希望命令路由到该作用域,则必须手动设置命令目标。或者,除了工具栏、菜单等,您不能使用聚焦镜,也不能手动处理容器聚焦问题

根据这些来源,可以假设焦点必须处于活动状态,即可以与键盘焦点一起使用的元素,例如:
TextBox

为了进一步研究,我对您的示例(XAML部分)做了一些更改:


我在
StackPanel
中添加了命令,并添加了
菜单
控件。现在,如果单击以清除焦点,则与该命令关联的控件将不可用:

现在,如果我们单击按钮
Test(ICommand.Execute)
我们会看到以下内容:

键盘焦点设置在
窗口上,但命令仍不运行。再一次,记住上面的注释:

请注意,它是windows焦点作用域,而不是活动焦点作用域

他没有活动焦点,因此命令不起作用。只有当焦点处于活动状态,设置为
文本框时,它才会工作:

让我们回到您最初的示例

显然,第一个
按钮
在没有活动焦点的情况下不会导致命令。唯一的区别是,在这种情况下,第二个按钮没有被禁用,因为没有活动焦点,所以单击它,我们直接调用命令。也许,这可以用一系列
MSDN
引号来解释:

如果命令处理程序位于窗口级别,则这种方法可以正常工作,因为窗口始终位于当前焦点元素的焦点树中,因此它会被调用以获取命令消息

我想,我找到了另一个可以解释这种奇怪行为的来源。引自:

默认情况下,菜单项或工具栏按钮放置在单独的聚焦范围内(分别用于菜单或工具栏)。如果任何此类项触发路由命令,并且它们没有设置命令目标,则WPF始终通过搜索包含窗口中具有键盘焦点的元素(即下一个更高的焦点范围)来查找命令目标

因此,WPF并不像您直观地期望的那样,只是查找包含窗口的命令绑定,而是始终查找以键盘为中心的元素以设置为当前命令目标!显然,WPF团队在这里采取了最快的方法,使诸如复制/剪切/粘贴之类的内置命令与包含多个文本框等的窗口一起工作;不幸的是,他们破坏了沿途的所有其他命令

原因如下:如果包含窗口中的聚焦元素无法接收键盘焦点(例如,它是非交互式图像),那么所有菜单项和工具栏按钮都将被禁用——即使它们不需要任何命令目标来执行!这类命令的CanExecute处理程序将被忽略

显然,解决问题#2的唯一方法是将任何此类菜单项或工具栏按钮的CommandTarget显式设置为包含窗口(或其他控件)

,我的一位同事,显然已经找到了这种行为的原因:

我想我是通过reflector找到它的:如果命令目标为null(即键盘焦点为null),则使用自身(而不是窗口)作为命令目标,最终命中窗口的CommandBinding(这就是声明性绑定工作的原因)


我把这个答案做成了一个社区维基,所以我没有因为他的研究而获得学分。

为了详细说明Nosertio的答案,
RoutedCommand
明确地实现了
ICommand
,但也有自己的
Ex
void btnClearFocus_Click(object sender, RoutedEventArgs e)
{
    //FocusManager.SetFocusedElement(this, this);
    FocusManager.SetFocusedElement(this, null);
    Keyboard.ClearFocus();
    CommandManager.InvalidateRequerySuggested();
    Log("btnClearFocus_Click, {0}", FormatFocus());
}
<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>
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);
}