3

有人知道 MenuItem 上的 CommandTarget 属性可以用来做什么吗?该文档说以下内容

当与 RoutedCommand 一起使用时,命令目标是引发 Executed 和 CanExecute 事件的对象。如果未设置 CommandTarget 属性,则具有键盘焦点的元素将用作目标。

但是,在运行时,CommandTarget 的值在命令的 Execute 处理程序中无处可见。sender 是 CommandBinding 所属的窗口。ExecutedRoutedEventArgs 充满了对菜单项远祖的引用。

这里的目标是实现一个命令,该命令从各种不同的网格、列表等各种不同的上下文菜单中执行——它们都包含支持特定界面的项目。它们的上下文菜单不同,但有一些共同的命令。无论您单击什么,通用命令都使用相同的 Executed 和 CanExecute 处理程序,因为“Foo”命令执行“Foo”。处理程序确定您单击的任何网格/列表的所选项目是什么,尝试将其转换为接口,如果它具有该接口,则对其执行某些操作(如果给定命令使用的接口不支持该项目,该命令被禁用)。如果我将 ContextMenu 或 MenuItem 作为发件人,我可以获得 PlacementTarget 并且我知道用户点击了什么,但这只有在我在 ContextMenu 的 XAML 定义中定义 CommandBinding 时才有效——这意味着在使用该命令的每个 ContextMenu 中复制“n”整个 XAML 块,并在每个视图类中重新定义处理程序. 这不是我想要维护的混乱。

似乎在这种情况下,没有独立于语言的理由多次编写这些处理程序,或者将每个处理程序与给定命令关联多次。但据我所知,XAML 似乎希望您将处理程序和目标绑定在一起。你能绑定一次处理程序然后潜入不同的目标吗?

更新:我通过将命令放在静态 Command 类中,将处理程序放在非静态类中(主视图,无关紧要)并编写静态 Command.GetCommandBinding(command) 方法来实例化并返回一个CommandBinding 用于您传入的命令。因此,如果我想在网格 Bar 上使用命令 Foo,在 Bar 所在视图的构造函数中,我只需调用它:

Bar.CommandBindings.Add(Commands.GetCommandBinding(Commands.Foo));

然后,当将 Bar 分配给属于 Bar 的 ContextMenu 的 MenuItem 的 Command 属性时,Bar 作为命令上的 Executed 和 CanExecute 事件的发送者传递。

无法在 XAML 中进行绑定,因为处理程序必须是 View 类的成员。设计人员投入这么多工作来帮助我们重用命令的名称,同时让重用实际的命令变得如此痛苦,比如命令的 CODE,但无论如何,这似乎很奇怪。这不是微软做过的最愚蠢的事情,XAML 的其余大部分都非常棒(恕我直言)。

另一种解决方案:将菜单项定义为独立于上下文菜单的资源,并重用整个菜单项。这是在 Resources.xaml 中,我可以将其作为合并字典包含在其他 XAML 文件中。事件处理程序位于 Resources.cs 中。消费者可以使用 GridContextMenu,或者以同样的方式将 CtxMenuItem_EmailDocument 插入到他们自己的上下文菜单中。

<MenuItem Command="{x:Static vw:Commands.EmailDocument}" 
        x:Key="CtxMenuItem_EmailDocument">
    <MenuItem.CommandBindings>
        <CommandBinding Command="{x:Static vw:Commands.EmailDocument}"
                        Executed="EmailDocument_Executed"
                        CanExecute="EmailDocument_CanExecute"
                        />
    </MenuItem.CommandBindings>
</MenuItem>

<ContextMenu x:Key="GridContextMenu" x:Shared="true">
    <!-- other items -->
    <StaticResource ResourceKey="CtxMenuItem_EmailDocument" />
    <!-- other items -->
</ContextMenu>

CommandTarget 似乎在 Button 上表现出完全不同的行为。如果它们是在单独的文件中定义的,或者作为资源定义的,或者......无论如何,无论是那个,还是 CommandBindings 的行为完全不同。

4

3 回答 3

6

AndrewS 写的一切都是正确的,我只想补充一点,CommandTarget 将是Executed / CanExecute 事件的发送者。为了能够处理命令,CommandTarget 需要一个用于相关命令的 CommandBinding。

最小示例:

<StackPanel>
    <Button Command="Open" CommandTarget="{Binding ElementName=TestTextBox}">Open</Button>
    <TextBox x:Name="TestTextBox">
        <TextBox.CommandBindings>
            <CommandBinding Command="Open" Executed="CommandBinding_Executed"/>
        </TextBox.CommandBindings>
    </TextBox>
</StackPanel>
于 2013-06-12T16:35:46.037 回答
4

CommandTarget是当关联的 Command 是 RoutedCommand 时,CommandManager 类将开始路由 CanExecute 和 Execute 事件的元素。所以它不会真正显示为 CanExecute/Execute 的事件参数的任何参数 - 实际上它可能是OriginalSource但我不会依赖它,因为如果 CommandManager 重新路由命令(因为它输入新的FocusScope遍历树)然后它将重新路由,并且重新路由事件的 eventargs 的 OriginalSource 将是它重新路由到的那个元素。

通常,您不会为可以由多种元素类型处理的 RoutedCommands 设置 CommandTarget - 例如,您希望最终用户与之交互的控件以接收和响应命令的应用程序命令(如剪切/复制/粘贴)。但是,如果您遇到这样一种情况,无论最终用户关注的是哪个元素,您都希望确保您为该 ICommandSource 的 Command 属性(在本例中为 MenuItem)设置的 RoutedCommand 在特定元素实例上执行然后您将 CommandTarget 设置为该元素(通常使用 ElementName 绑定)。

编辑: 既然你已经改变了问题,我会增加我的答案。如果您想在特定类类型上处理某些 RoutedCommands 的 CanExecute 和 Execute,那么您想要做的是使用 CommandManager 类 - 特别是它的RegisterClassCommandBinding方法 - 为您的特定 RoutedCommands 注册全局 Execute/CanExecute 处理程序。

于 2013-06-12T14:43:30.320 回答
1

只是为了记录,CommandTarget在 a 的上下文中MenuItem,将OriginalSource属性设置ExecutedRoutedEventArgs为由 theExecutedCanExecute处理程序接收。

所以“目标”就是“源头”
(就像骆驼是委员会设计的马一样)

Sender正如@EdPlunket 所说, 是命令绑定的站点(实际上也是如此e.Source)。

因此,为了回答最初的问题,CommandTargetaMenuItem作为引用传递给命令的Executed和处理程序,在其属性CanExecute上的事件 args ( ExecutedRoutedEventArgsand CanExecuteRoutedEventArgs) 上。OriginalSource因此,您可以使用该引用在目标上做任何您喜欢的事情。

工作示例

CommandTargetMenuItemComboBox 选择控制

主窗口.xaml

<Window x:Class="CommandTarget.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CommandTarget"
        Title="MainWindow" Height="200" Width="400">

    <StackPanel x:Name="RootPanel">

        <StackPanel.CommandBindings>
            <CommandBinding x:Name="Pause" Command="Pause"
                            Executed="{x:Static local:MainWindow.OnButtonPause}"
                            CanExecute="{x:Static local:MainWindow.OnPauseCanExecute}" />
        </StackPanel.CommandBindings>

        <DockPanel>

            <Menu DockPanel.Dock="Top" >
                <MenuItem Header="Click Me" x:Name="Emitter"
                          Command="Pause"
                          CommandTarget="{Binding ElementName=Button2}" />
            </Menu>

        </DockPanel>

        <StackPanel Name="Buttons">

            <ToggleButton x:Name="Button1" Height="30" HorizontalAlignment="Stretch"
                          Content="Button1" />
            <ToggleButton x:Name="Button2" Height="30" HorizontalAlignment="Stretch"
                          Content="Button2" />

        </StackPanel>

    </StackPanel>
</Window>

主窗口.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace CommandTarget
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        //PAUSE COMMAND
        // Static, binding callbacks

        // Executed
        public static ExecutedRoutedEventHandler
            OnButtonPause = (sender, e) =>
            {
                e.Handled = ButtonPauseTarget(e, delegate(ToggleButton target)
                {
                    if (!target.IsEnabled) return false;
                    var flag = target.IsChecked ?? false;
                    target.IsChecked = !flag;
                    return true;
                });
            };

        // CanExecute
        public static CanExecuteRoutedEventHandler
            OnPauseCanExecute = (sender, e) => { e.CanExecute = true; };

        // helper to extract the target from the event args
        private static bool ButtonPauseTarget (RoutedEventArgs e,
            Func<ToggleButton, bool> ex)
        {
            var target = e.OriginalSource as ToggleButton;
            if (target == null) return false;
            var handled = ex(target);

            return handled;
        }
    }
}

注意:暂停命令只是一个随机选择,源元素支持的任何其他命令都不会使用。

更完整的例子

于 2016-12-04T11:54:13.960 回答