11

(这个问题与另一个问题有关,但差异很大,我认为它值得放在这里。)

这是一个(严重剪断)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可以将 a 动态添加到Window. 也许通过窗口中的按钮。也许,更有问题的是,当应用程序启动时,来自持久性后备存储。

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

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

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

我想也许我错过了 中的属性CommandTarget,我的第一个想法是我希望命令来自 UserControl,所以我自然首先尝试了:MenuItemUserControl

<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. 我的理解是:CommandTarget正确的作用在于它提供了一种修改命令源的机制吗?
  2. 如何从MenuItemin绑定UserControl.ContextMenu到 owning 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 项目,我在自己的时间做这件事作为学习经验,所以我绝对不反对学习卓越的解决方案架构。

4

2 回答 2

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 中重复此操作,您可以使用 Style。

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

于 2009-03-05T20:22:55.110 回答
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>
于 2010-12-22T13:57:39.223 回答