【问题标题】:What's the default command target?默认的命令目标是什么?
【发布时间】:2012-05-12 13:04:33
【问题描述】:

在《WPF控件开发》一书中,关于扮演默认命令目标的元素有两种不同的想法:

第 258 页» 命令目标是命令所在的对象 提高。 ICommandSource 接口包含一个 CommandTarget 属性 可以设置为特定对象。 默认情况下,命令源 本身被认为是命令目标。

第 262 页» 默认情况下,当未设置 CommandTarget 时,元素 使用键盘焦点。

此外,在这个tutorial,我们可以不定义菜单项和按钮命令目标,而只有菜单项(即不是按钮)可以真正检测命令目标。那么默认的命令目标是什么?!

【问题讨论】:

    标签: c# wpf command


    【解决方案1】:

    基于一些更多不同的测试用例以及关于@dowhilefor 和@hbarck 的回答,我得出结论,每种情况都有一条特定的行进路径。

    指定的CommandTarget:CommandTarget开始,向可视化树的根元素寻找第一个(最近的)绑定了命令的元素。 (它只在这条路径上寻找这个元素。)结论:

    1. sender:绑定了命令的CommandTarget容器元素(与CommandBinding)。
    2. e.source: 指定为CommandTarget的元素。

    未指定的CommandTarget:它从获得焦点(CommandSource 范围)的元素开始向可视化树的根元素查找绑定命令的第一个(最近的)元素。在这种情况下,焦点元素将被确定为CommandTarget。结论:

    1. sender: 已绑定命令的焦点元素容器(带有CommandBinding标签)。
    2. e.Source:焦点元素。

    【讨论】:

    • 是的,我想你知道了。实际上,启动命令的元素(Button 或 MenuItem,在 WPF 文档中称为 Command 源)完全不在图片中,这里我最初的答案是错误的,因此我要更改或删除它...
    • 谢谢@hbarck。你的回答很有帮助。
    【解决方案2】:

    断章取义,我不明白第一个突出显示的句子是什么意思,但我认为它是错误的。另一方面第二句话是对的

    Msdn:

    如果未定义命令目标,则具有键盘焦点的元素 将用作命令目标。

    如果您希望命令对某些内容进行操作,例如在当前聚焦的文本框上粘贴命令,这将非常有用。您希望粘贴命令始终有效,无论哪个文本框或哪个其他控件具有焦点,这使这成为可能。值得指出的是,关于菜单,还有另一个需要记住的概念,称为FocusScope。 WPF 中的命令有时可能很棘手,考虑一个不获取文本框焦点的保存按钮,因此不刷新 Text 属性(因为它只更新 focuslost 上的目标绑定)。但请记住,CommandTarget 仅适用于 RoutedCommands,不适用于“简单”ICommands。

    关于你的教程视频,还没看过:这个概念适用于所有不带键盘焦点的 CommandSources。

    因此得出结论:CommandTarget 是,只要Command 是RoutedCommand,则当前键盘聚焦的元素,否则将被忽略。

    【讨论】:

    • +1 谢谢@dowhilefor。我怀疑是否理解它; If an element is focusable, it cannot detect undefined routed command target automatically.这是正确的吗?
    • @Mimi 老实说我也不明白。可聚焦控件与命令无关,也不关心命令目标。 Command Target 只是一个控件,从该控件开始向上路由到窗口,直到找到处理实际命令逻辑的 CommandBinding。
    【解决方案3】:

    我想我刚刚明白这意味着什么:

    如果一个元素是可聚焦的,它就无法检测到未定义的路由命令 自动瞄准。

    如果一个元素是可聚焦的,这意味着当它被激活以发出命令时,它将始终具有键盘焦点。因此,如果它有一个用于命令的 CommandBinding,它总是会自己处理它,如果没有,它总是会被禁用。

    但是,您可以通过在控件的容器上将 FocusManager.IsFocusScope 设置为 true 来解决此问题,就像在此 XAML 中一样:

    <Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:CommandRouting"
        Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Menu IsMainMenu="True">
                <MenuItem x:Name="TestMenuItem" Command="{x:Static my:MainWindow.TestCommand}"/>
            </Menu>
            <GroupBox x:Name="CommandBindingOnControlsGroupBox" Header="CommandBinding on Controls" Grid.Row="1">
                <StackPanel>
                <Button x:Name="CommandBindingOnButtonButton" Command="{x:Static my:MainWindow.TestCommand}" Content="CommandBinding on Button">
                   <Button.CommandBindings>
                        <CommandBinding Command="{x:Static my:MainWindow.TestCommand}" Executed="CommandBinding_Executed" PreviewExecuted="CommandBinding_Executed"/>
                    </Button.CommandBindings>
                </Button>
                    <TextBox x:Name="CommandBindingOnTextBoxTextBox">
                        <TextBox.CommandBindings>
                            <CommandBinding Command="{x:Static my:MainWindow.TestCommand}" Executed="CommandBinding_Executed"/>
                        </TextBox.CommandBindings>
                        <TextBox.InputBindings>
                            <!-- provide alternate keyboard shortcut -->
                            <KeyBinding Key="{x:Static Key.P}" Modifiers="{x:Static ModifierKeys.Control}" Command="{x:Static my:MainWindow.TestCommand}"/>
                        </TextBox.InputBindings>
                    </TextBox>
                    <Button x:Name="CommandTargetOnButtonButton" Command="{x:Static my:MainWindow.TestCommand}" Content="CommandTarget on Button" CommandTarget="{Binding ElementName=CommandBindingOnControlsGroupBox}">
                        <Button.CommandBindings>
                            <CommandBinding Command="{x:Static my:MainWindow.TestCommand}" Executed="CommandBinding_Executed"/>
                        </Button.CommandBindings>
                    </Button>
                </StackPanel>
            </GroupBox>
            <GroupBox x:Name="CommandBindingOnContainerGroupBox" Header="CommandBinding on Container" Grid.Row="2">
                <GroupBox.CommandBindings>
                    <CommandBinding Command="{x:Static my:MainWindow.TestCommand}" PreviewExecuted="CommandBinding_Executed"/>
                </GroupBox.CommandBindings>
                <StackPanel x:Name="CommandBindingOnInnerContainerStackPanel">
                    <StackPanel.CommandBindings>
                        <CommandBinding Command="{x:Static my:MainWindow.TestCommand}" Executed="CommandBinding_Executed"/>
                    </StackPanel.CommandBindings>
                    <Button x:Name="CommandBindingOnContainerButton" Command="{x:Static my:MainWindow.TestCommand}" Content="CommandBinding on Two Containers">
                    </Button>
                    <TextBox x:Name="CommandBindingOnContainerTextBox">
                        <TextBox.InputBindings>
                            <!-- provide alternate keyboard shortcut -->
                            <KeyBinding Key="{x:Static Key.P}" Modifiers="{x:Static ModifierKeys.Control}" Command="{x:Static my:MainWindow.TestCommand}"/>
                        </TextBox.InputBindings>
                    </TextBox>
                </StackPanel>
            </GroupBox>
            <GroupBox x:Name="OtherFocusScopeGroupBox" FocusManager.IsFocusScope="True" Header="Other FocusScope, No CommandBindings" Grid.Row="3">
                <StackPanel >
                    <Button x:Name="OtherFocusScopeButton" Command="{x:Static my:MainWindow.TestCommand}" Content="Other FocusScope">
                    </Button>
                    <TextBox x:Name="OtherFocusScopeTextBox">
                        <TextBox.CommandBindings>
                            <CommandBinding Command="{x:Static my:MainWindow.TestCommand}" Executed="CommandBinding_Executed"/>
                        </TextBox.CommandBindings>
                        <TextBox.InputBindings>
                            <!-- provide alternate keyboard shortcut -->
                            <KeyBinding Key="{x:Static Key.P}" Modifiers="{x:Static ModifierKeys.Control}" Command="{x:Static my:MainWindow.TestCommand}"/>
                        </TextBox.InputBindings>
                    </TextBox>
                </StackPanel>
            </GroupBox>
        </Grid>
    </Window>
    

    【讨论】:

    • +1 谢谢- 正如我所提到的,在 Unspecified CommandTarget 的情况下,焦点元素将被视为CommandTarget,并且机制将从它开始移动到找到最近的命令活页夹。因此,我认为最好说:Thus, if it has a CommandBinding for the Command,它会引发 Executed 路由事件(作为它的 e.sender),因此it will always handle it itself, and if it hasn't,该机制将继续搜索直到它到达了根。因此,如果在此路径上找到另一个命令绑定器,则可以启用它。同意我的观点吗?
    • 是的,这在技术上更完整。但是,这里的要点是,可聚焦控件始终是它自己的 CommandTarget,因为它总是将键盘焦点拉到自己身上,而不指定 CommandTarget 的想法是该命令可以由当前聚焦的任何元素处理。正如我已经说过的,可以通过设置 FocusManager.IsFocusScope 来绕过这个限制。人们可能应该在 CommandTarget(引发 Executed RoutedEvent 的元素)和 CommandHandler(具有处理事件的 CommandBinding 的元素)之间有所不同。
    【解决方案4】:

    这里似乎缺少一点:给定命令的 CommandTarget 只能是为该命令定义 CommandBinding 的对象。

    编辑:澄清和更正以下段落,以免在系统中留下误导性信息。

    命令路由是事件路由的一种特殊情况,即事件在逻辑树上上下移动:实现 ICommandSource 接口的控件,如 InputBindings、Buttons 或 MenuItems,是 CommandSources。如果他们发出命令,这会导致 RoutedEvent 在 CommandTarget 处开始。这通常是具有键盘焦点的元素。事件沿着逻辑树向上传播,直到到达根。沿这种方式,所有具有命令的 CommandBindings 的元素都有机会处理命令,尽管通常处理命令的第一个元素获胜并停止路由过程。这甚至可能是 CommandSource 本身,如果它有一个用于命令的 CommandBinding,那么这可能就是您的第一个引用。如果元素处理事件,sender 参数将是定义 CommandBinding 的元素,而事件的 RoutedEventArgs 的 Source 属性将是事件开始路由的元素,即 CommandTarget。

    为了完全混淆,ICommandSource 接口定义了一个名为 CommandTarget 的属性。此属性适用于您想要短路命令路由的情况,并且想要一个特殊的控件来处理命令,无论键盘焦点在哪里。在这种情况下,您可以在相关的 Button 或 MenuItem 上编写类似 CommandTarget="{Binding ElementName=MyCommandTargetControl}" 的内容。同样,您必须确保该控件具有用于命令的 CommandBinding,否则该命令将被永久禁用。

    【讨论】:

    • 我不确定我是否正确理解了您的第一句话。但是命令目标可以是任何东西,而不是对该特定命令具有命令绑定的东西。例如,粘贴命令,假设窗口上有一个 CommandBinding,现在一个按钮获取粘贴命令,并且目标设置为这个特定的文本框。这工作正常,没有文本框具有用于粘贴的命令绑定。
    • 在明确指定CommandTarget 的情况下,我发现最接近CommandTarget 的元素(在它之间是可视树的根)具有CommandBinding 执行命令。这里e.Source 将是目标元素,确定真正调用程序源的唯一方法是使用CommandParameter 作为自相关绑定源。 (通过 e.Parameter)。另一方面,在未知CommandTarget 的情况下,命令机制开始从命令源(设置Command 属性)向上移动到根,并且不会再次返回焦点元素
    • @dowhilefor:TextBoxes 具有类命令绑定(有关详细信息,请参阅 CommandManager.RegisterClassCommandBinding)用于许多编辑命令。这就是为什么他们可以在没有显式 CommandBindings 的情况下处理诸如复制和粘贴之类的事情。为了测试命令路由,我会使用自定义的 RoutedCommand,这样我就可以确定没有任何内置行为妨碍我...
    • @mimi:你确定它不会返回吗?如果您使用 Button 作为 CommandSource,则当您按下该 Button 时,该 Button 具有 KeyBoard 焦点,因此向上路由和向下路由实际上是相同的。如果引发命令的控件 (CommandSource) 和接收命令的控件 (CommandTarget) 位于不同的 FocusScopes 中,则只能对此进行测试,例如像 MenuItems(默认情况下,Menu 是一个新的 FocusScope)。
    • @mimi 再次:对了,Executed-Eventhandler 的 sender 参数将是 CommandTarget,这里不需要绑定任何东西到 CommandParameter。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-28
    • 2017-04-18
    • 1970-01-01
    • 1970-01-01
    • 2023-01-05
    • 2015-08-12
    相关资源
    最近更新 更多