如何在ContextMenu中为MenuItem设置CommandTarget?

Gre*_*g D 11 wpf user-controls contextmenu

(这个问题与另一个问题有关,但不同,我认为这需要在这里放置.)

这是一个(严重剪断)Window:

<Window x:Class="Gmd.TimeTracker2.TimeTrackerMainForm"
    xmlns:local="clr-namespace:Gmd.TimeTracker2"
    xmlns:localcommands="clr-namespace:Gmd.TimeTracker2.Commands"
    x:Name="This"
    DataContext="{Binding ElementName=This}">
    <Window.CommandBindings>
        <CommandBinding Command="localcommands:TaskCommands.ViewTaskProperties" 
                        Executed="HandleViewTaskProperties" 
                        CanExecute="CanViewTaskPropertiesExecute" />
    </Window.CommandBindings>
    <DockPanel>
<!-- snip stuff -->
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
<!-- snip more stuff -->
            <Button Content="_Create a new task" Grid.Row="1" x:Name="btnAddTask" Click="HandleNewTaskClick" />
        </Grid>
    </DockPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)

这是一个(严重剪断)UserControl:

<UserControl x:Class="Gmd.TimeTracker2.TaskStopwatchControl"
             xmlns:local="clr-namespace:Gmd.TimeTracker2"
             xmlns:localcommands="clr-namespace:Gmd.TimeTracker2.Commands"
             x:Name="This"
             DataContext="{Binding ElementName=This}">
    <UserControl.ContextMenu>
        <ContextMenu>
            <MenuItem x:Name="mnuProperties" Header="_Properties" Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}" 
                      CommandTarget="What goes here?" />
        </ContextMenu>
    </UserControl.ContextMenu>
    <StackPanel>
        <TextBlock MaxWidth="100" Text="{Binding Task.TaskName, Mode=TwoWay}" TextWrapping="WrapWithOverflow" TextAlignment="Center" />
        <TextBlock Text="{Binding Path=ElapsedTime}" TextAlignment="Center" />
        <Button Content="{Binding Path=IsRunning, Converter={StaticResource boolToString}, ConverterParameter='Stop Start'}" Click="HandleStartStopClicked" />
    </StackPanel>
</UserControl>
Run Code Online (Sandbox Code Playgroud)

通过各种技术,a UserControl可以动态添加到Window.也许是通过窗口中的按钮.当应用程序启动时,从持久性后备存储中可能更有问题.

从xaml可以看出,我已经决定尝试使用Commands作为一种处理用户可以用Tasks 执行的各种操作的方法.我这样做的最终目标是将所有命令逻辑分解为更正式定义的Controller层,但我试图一次重构一个步骤.

我遇到的问题与UserControl's中ContextMenu的命令和CanExecute窗口中定义的命令之间的交互有关.当应用程序首次启动并且已保存的任务将恢复到Window上的TaskStopwatches时,不会选择任何实际的UI元素.如果我然后立即R-单击UserControlWindow试图执行ViewTaskProperties命令时,CanExecute处理器运行从不和菜单项目仍处于禁用状态.如果我然后单击某个UI元素(例如,按钮)只是为了给焦点一些东西,则CanExecute处理程序运行时将CanExecuteRoutedEventArgsSource属性设置为具有焦点的UI元素.

在某些方面,这种行为似乎是已知的 - 我已经知道菜单会将事件路由到最后具有焦点的元素,以避免始终从菜单项发送事件.但我认为我想要的是事件的来源是控件本身,或者控件正在包裹的任务(但Task不是元素,所以我不认为它可以是资源).

我想,也许我错过了CommandTarget财产上MenuItemUserControl,我的第一个念头是我想要的命令来自于用户控件,所以很自然我第一次尝试:

<MenuItem x:Name="mnuProperties" 
          Header="_Properties" 
          Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}" 
          CommandTarget="{Binding ElementName=This}" />
Run Code Online (Sandbox Code Playgroud)

这作为无效绑定失败.我不知道为什么.然后我想,"嗯,我正在查找树,所以也许我需要的是一个RelativeSource",我试过这个:

<MenuItem x:Name="mnuProperties" 
          Header="_Properties" 
          Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}" 
          CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TaskStopwatchControl}}}" />
Run Code Online (Sandbox Code Playgroud)

这也失败了,但当我再次看到我的xaml时,我意识到它ContextMenu位于UserControl的属性中,它不是子元素.所以我猜到了(此时这是猜测):

<MenuItem x:Name="mnuProperties" 
          Header="_Properties" 
          Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}" 
          CommandTarget="{Binding RelativeSource={x:Static RelativeSource.Self}}" />
Run Code Online (Sandbox Code Playgroud)

那也失败了.

一次失败的猜测和检查就足以让我退缩,并意识到我在这里错过了某种基本概念.那我该怎么办?

  1. 我的理解是:CommandTarget正确的角色在于这提供了一种修改命令来源的机制吗?
  2. 如何从MenuItemin UserControl.ContextMenu到绑定UserControl?或者我做错了什么只是因为我觉得有必要?
  3. 我是否希望拥有由单击的元素设置的命令的上下文来生成上下文菜单,而不是在上下文菜单之前有焦点的元素,这是不正确的?也许我需要编写自己的命令而不是使用RoutedUICommand:

    private static RoutedUICommand viewTaskPropertiesCommand = new RoutedUICommand("View a task's details.", "ViewTaskProperties", typeof(TaskCommands));
    public static RoutedUICommand ViewTaskProperties
    {
        get { return viewTaskPropertiesCommand; }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 我的设计中存在一些更深层次的根本缺陷吗?这是我的第一个重要的WPF项目,我在自己的时间做这个学习经历,所以我绝对不反对学习优秀的解决方案架构.

Rob*_*nee 7

1:是的,CommandTarget控制RoutedCommand从哪里开始路由.

2:ContextMenu有一个PlacementTarget属性,允许访问您的UserControl:

<MenuItem x:Name="mnuProperties" Header="_Properties"
          Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
          CommandTarget="{Binding PlacementTarget,
                                  RelativeSource={RelativeSource FindAncestor,
                                                                 AncestorType={x:Type ContextMenu}}}"/>
Run Code Online (Sandbox Code Playgroud)

为避免在每个MenuItem中重复此操作,您可以使用Style.

3&4:我会说你的愿望是合理的.由于Execute处理程序在Window上,它现在无关紧要,但是如果你有不同的应用程序区域,每个区域都有自己的同一命令的Execute处理程序,那么焦点在哪里都很重要.