(这个问题与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可以看出,我已经决定尝试使用Commands作为处理用户可以使用Task
执行的各种操作的方法。我这样做的最终目标是将所有命令逻辑分解为更正式定义的Controller层,但我试图一次重构一步。
我遇到的问题与UserControl
的{{1}}中的命令与窗口中定义的命令ContextMenu
之间的交互有关。当应用程序首次启动并且已保存的任务将恢复到Window上的TaskStopwatches时,不会选择任何实际的UI元素。如果我然后立即r单击CanExecute
中的UserControl
以尝试执行Window
命令,则ViewTaskProperties
处理程序永远不会运行并且菜单项保持禁用状态。如果我然后单击某个UI元素(例如,按钮)只是为了给焦点,则CanExecute
处理程序运行时将CanExecute
的Source属性设置为具有焦点的UI元素。 / p>
在某些方面,这种行为似乎是已知的 - 我已经了解到菜单会将事件路由到最后具有焦点的元素,以避免始终从菜单项发送事件。我想我想要的是,事件的来源是控件本身,或者控件自行包装的任务(但是CanExecuteRoutedEventArgs
不是元素,所以我不这样做认为它可以成为一个来源。
我想也许我错过了Task
中CommandTarget
上的MenuItem
属性,我的第一个想法是我希望命令来自UserControl,所以很自然我第一次尝试:
UserControl
这作为无效绑定失败。我不知道为什么。然后我想,“嗯,我正在抬头看树,所以也许我需要的是一个RelativeSource”,我试过这个:
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding ElementName=This}" />
那也失败了,但是当我再次看到我的xaml时,我意识到<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TaskStopwatchControl}}}" />
属于UserControl的属性,它不是子元素。所以我猜到了(此时这是猜测):
ContextMenu
那也失败了。
一次失败的猜测和检查就足以让我退缩并意识到我在这里缺少某种基本概念。那我该怎么办?
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding RelativeSource={x:Static RelativeSource.Self}}" />
的作用是否正确,这提供了一种修改命令来源的机制?CommandTarget
MenuItem
绑定到拥有UserControl.ContextMenu
?或者我做错了什么只是因为我认为需要?我是否希望获得由单击的元素设置的命令的上下文以生成上下文菜单,而不是在上下文菜单之前有焦点的元素,这是不正确的?也许我需要编写自己的命令而不是使用UserControl
:
RoutedUICommand
我的设计中存在一些更深层次的根本缺陷吗?这是我的第一个重要的WPF项目,我在自己的时间做这个学习经历,所以我绝对不反对学习优秀的解决方案架构。
答案 0 :(得分: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}}}"/>
为避免在每个MenuItem中重复此操作,您可以使用样式。
3&amp; 4:我想说你的愿望是合理的。由于Execute处理程序在Window上,它现在无关紧要,但是如果你有不同的应用程序区域,每个区域都有自己的同一命令的Execute处理程序,那么重点在哪里就很重要。
答案 1 :(得分:2)
我发现类似的解决方案是使用父级的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>