【问题标题】:How to set CommandTarget for MenuItem inside a ContextMenu?如何在 ContextMenu 中为 MenuItem 设置 CommandTarget?
【发布时间】:2009-03-05 19:14:07
【问题描述】:

(这个问题与another one 相关,但完全不同,我认为它值得放在这里。)

这是一个(严重剪断)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>

这是一个(严重剪断)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>

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

从 xaml 中可以看出,我认为尝试使用命令来处理用户可以使用Tasks 执行的各种操作是有意义的。我这样做的最终目标是将所有命令逻辑分解为更正式定义的控制器层,但我试图一次重构一个步骤。

我遇到的问题与窗口中定义的UserControlContextMenu 中的命令和命令的CanExecute 之间的交互有关。当应用程序第一次启动并将保存的任务恢复到窗口上的 TaskStopwatches 时,没有选择实际的 UI 元素。如果我随后立即在Window 中单击UserControl 以尝试执行ViewTaskProperties 命令,则CanExecute 处理程序永远不会运行并且菜单项保持禁用状态。如果我随后单击某个 UI 元素(例如,按钮)只是为了获得焦点,CanExecute 处理程序将在 CanExecuteRoutedEventArgs 的 Source 属性设置为具有焦点的 UI 元素的情况下运行。

在某些方面,这种行为似乎是已知的——我了解到菜单将通过最后一个具有焦点的元素路由事件,以避免总是从菜单项发送事件。不过,我想我想要的是事件的来源是控件本身,或者是控件环绕的任务(但Task 不是元素,所以我不认为它可以作为来源)。

我想可能是我在UserControl 中缺少MenuItem 上的CommandTarget 属性,我的第一个想法是我希望命令来自UserControl,所以我很自然地首先尝试了:

<MenuItem x:Name="mnuProperties" 
          Header="_Properties" 
          Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}" 
          CommandTarget="{Binding ElementName=This}" />

由于绑定无效而失败。我不确定为什么。然后我想,“嗯,我正在查找树,所以也许我需要的是一个 RelativeSource”,然后我尝试了这个:

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

这也失败了,但是当我再次查看我的 xaml 时,我意识到 ContextMenu 在 UserControl 的属性中,它不是子元素。所以我猜到了(在这一点上是一个猜测):

<MenuItem x:Name="mnuProperties" 
          Header="_Properties" 
          Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}" 
          CommandTarget="{Binding RelativeSource={x:Static RelativeSource.Self}}" />

那也失败了。

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

  1. 我对@9​​87654346@ 的作用的理解是否正确,因为它提供了一种修改命令源的机制?
  2. 如何从UserControl.ContextMenu 中的MenuItem 绑定到拥有的UserControl?还是我做错了什么只是因为我觉得有必要?
  3. 我希望通过单击以生成上下文菜单的元素设置命令的上下文,而不是在上下文菜单之前具有焦点的元素,不正确吗?也许我需要编写自己的命令而不是使用RoutedUICommand

    private static RoutedUICommand viewTaskPropertiesCommand = new RoutedUICommand("View a task's details.", "ViewTaskProperties", typeof(TaskCommands));
    public static RoutedUICommand ViewTaskProperties
    {
        get { return viewTaskPropertiesCommand; }
    }
    
  4. 我的设计中是否存在一些更深层次的基本缺陷?这是我的第一个重要的 WPF 项目,我是在利用自己的时间来学习的,所以我绝对不反对学习卓越的解决方案架构。

【问题讨论】:

    标签: wpf user-controls contextmenu


    【解决方案1】:

    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}}}"/>
    

    为避免在每个 MenuItem 中重复此操作,您可以使用样式。

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

    【讨论】:

    • 每天学习新东西!我以前从未遇到过 PlacementTarget。 :) 谢谢,罗伯特。当我有机会再次研究它时,我会进一步更新。 (希望今晚晚些时候)
    【解决方案2】:

    我发现的类似解决方案是使用父级的 Tag 属性来获取数据上下文:

    <Grid Tag="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
        <Grid.ContextMenu>
            <ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
                <MenuItem 
                    Header="{Binding Path=ToolbarDelete, Mode=Default, Source={StaticResource Resx}}" 
                    Command="{Binding RemoveCommand}" 
                    CommandParameter="{Binding DataContext.Id, RelativeSource={RelativeSource TemplatedParent}}"/>
            </ContextMenu>
        </Grid.ContextMenu>
    
        <TextBlock Text="{Binding Name}" Padding="2" />
    
    </Grid>
    

    【讨论】:

    • 我在 HeriachicalDataTemplate 中为 TreeView 使用 MenuItem 并从您的示例中添加 de CommandParameter 行对我来说足以使其工作。是否绝对有必要将绑定添加到 DataContext?
    猜你喜欢
    • 2012-02-24
    • 1970-01-01
    • 1970-01-01
    • 2017-03-13
    • 1970-01-01
    • 2014-09-21
    • 1970-01-01
    • 2010-11-04
    • 2022-01-04
    相关资源
    最近更新 更多