【发布时间】: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 执行的各种操作是有意义的。我这样做的最终目标是将所有命令逻辑分解为更正式定义的控制器层,但我试图一次重构一个步骤。
我遇到的问题与窗口中定义的UserControl 的ContextMenu 中的命令和命令的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}}" />
那也失败了。
像这样一次失败的猜测和检查就足以让我退缩并意识到我在这里遗漏了某种基本概念。那我该怎么办?
- 我对@987654346@ 的作用的理解是否正确,因为它提供了一种修改命令源的机制?
- 如何从
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 项目,我是在利用自己的时间来学习的,所以我绝对不反对学习卓越的解决方案架构。
【问题讨论】:
标签: wpf user-controls contextmenu