如何在ContextMenu中为MenuItem设置CommandTarget?
-
03-07-2019 - |
题
(此问题与另一个,但不同,我认为它保证在这里安置。)
这是一个(严重剪断的) 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可以看出,我已经决定尝试使用Commands作为处理用户可以使用 Task
执行的各种操作的方法。我这样做的最终目标是将所有命令逻辑分解为更正式定义的Controller层,但我试图一次重构一步。
我遇到的问题与 UserControl
的 ContextMenu
中的命令与命令的 CanExecute
之间的交互有关,在窗口中定义。当应用程序首次启动并且已保存的任务将恢复到Window上的TaskStopwatches时,不会选择任何实际的UI元素。如果我然后立即在 Window
中单击 UserControl
以尝试执行 ViewTaskProperties
命令,则 CanExecute
处理程序从不运行,菜单项保持禁用状态。如果我然后单击某个UI元素(例如,按钮)只是为了给焦点,那么 CanExecute
处理程序运行时将 CanExecuteRoutedEventArgs
的Source属性设置为UI元素这是重点。
在某些方面,这种行为似乎是已知的 - 我已经了解到菜单会将事件路由到最后具有焦点的元素,以避免始终从菜单项发送事件。但我认为我想要的是事件源是控件本身,还是控件自行包装的任务(但是 Task
不是Element,所以我不要认为它可以成为一个来源。
我想也许我错过了 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}}" />
那也失败了。
一次失败的猜测和检查就足以让我退缩并意识到我在这里缺少某种基本概念。那我该怎么办?
- 我的理解是:
CommandTarget
的作用是否正确,这提供了一种修改命令来源的机制? - 如何从
UserControl.ContextMenu
中的MenuItem
绑定到拥有的UserControl
?或者我做错了什么只是因为我认为需要? -
我是否希望获得由单击的元素设置的命令的上下文以生成上下文菜单,而不是在上下文菜单之前具有焦点的元素不正确?也许我需要编写自己的命令而不是使用
RoutedUICommand
:private static RoutedUICommand viewTaskPropertiesCommand = new RoutedUICommand("View a task's details.", "ViewTaskProperties", typeof(TaskCommands)); public static RoutedUICommand ViewTaskProperties { get { return viewTaskPropertiesCommand; } }
-
我的设计中存在一些更深层次的根本缺陷吗?这是我的第一个重要的WPF项目,我在我自己的时间做这个
解决方案
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&amp; 4:我想说你的愿望是合理的。由于Execute处理程序在Window上,它现在无关紧要,但是如果你有不同的应用程序区域,每个区域都有自己的同一命令的Execute处理程序,那么重点在哪里就很重要。
其他提示
我发现类似的解决方案是使用父级的Tag属性来获取datacontext:
<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>