如何在ContextMenu中为MenuItem设置CommandTarget? [英] How to set CommandTarget for MenuItem inside a ContextMenu?

查看:54
本文介绍了如何在ContextMenu中为MenuItem设置CommandTarget?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

(此问题与另一个,但与我认为足以保证在此处放置的程度有所不同。)



这里是一个(严重被窃听)的 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>
<!-剪东西->
< Grid>
< Grid.RowDefinitions>
< RowDefinition />
< RowDefinition Height = Auto />
< /Grid.RowDefinitions>
<!-剪切更多内容->
< 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 =这里是什么? />
< / 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 s。我这样做的最终目的是将所有命令逻辑分解为一个更正式定义的Controller层,但我试图一次重构一个步骤。



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



从某种意义上讲,这种行为似乎是已知的-我了解到菜单将通过最后具有焦点的元素来路由事件,以避免始终从菜单项发送事件。不过,我想我希望事件的来源是控件本身,还是控件本身包装的Task(但 Task 是 t元素,因此我认为它可能不是源。)



我以为我可能缺少了 CommandTarget UserControl MenuItem 上的c $ c>属性,我的第一个念头是我希望命令来从UserControl中获取,所以自然而然地我首先尝试:

 < MenuItem x:Name = mnuProperties 
Header = _Properties
Command = {x:静态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. 如何与<$ c中的 MenuItem 绑定,是否正确,因为这提供了一种修改命令源的机制? $ c> UserControl.ContextMenu 到拥有的 UserControl ?还是我只是因为意识到需要而做错了事?

  3. 我是否希望获得由单击以生成上下文菜单的元素所设置的命令的上下文,而不是上下文菜单之前具有焦点的元素,对吗?也许我需要编写自己的命令而不是使用 RoutedUICommand

     私有静态RoutedUICommand viewTaskPropertiesCommand = new RoutedUICommand(查看任务的详细信息。, ViewTaskProperties,typeof(TaskCommands)); 
    public static RoutedUICommand ViewTaskProperties
    {
    get {return viewTaskPropertiesCommand; }
    }


  4. 设计中是否存在更深层的基本缺陷?这是我第一个重要的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& 4:我想说你的愿望是合理的。由于Execute处理程序位于Window上,所以现在没关系,但是如果您在应用程序中有不同的区域,每个区域对于同一命令都具有自己的Execute处理程序,那么焦点在哪里就很重要了。 b $ b

(This question is related to another one, but different enough that I think it warrants placement here.)

Here's a (heavily snipped) 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>

and here's a (heavily snipped) 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>

Through various techniques, a UserControl can be dynamically added to the Window. Perhaps via the Button in the window. Perhaps, more problematically, from a persistent backing store when the application is started.

As can be seen from the xaml, I've decided that it makes sense for me to try to use Commands as a way to handle various operations that the user can perform with Tasks. I'm doing this with the eventual goal of factoring all command logic into a more formally-defined Controller layer, but I'm trying to refactor one step at a time.

The problem that I'm encountering is related to the interaction between the command in the UserControl's ContextMenu and the command's CanExecute, defined in the Window. When the application first starts and the saved Tasks are restored into TaskStopwatches on the Window, no actual UI elements are selected. If I then immediately r-click a UserControl in the Window in an attempt to execute the ViewTaskProperties command, the CanExecute handler never runs and the menu item remains disabled. If I then click some UI element (e.g., the button) just to give something focus, the CanExecute handlers are run with the CanExecuteRoutedEventArgs's Source property set to the UI element that has the focus.

In some respect, this behavior seems to be known-- I've learned that menus will route the event through the element that last had focus to avoid always sending the event from the menu item. What I think I would like, though, is for the source of the event to be the control itself, or the Task that the control is wrapping itself around (but Task isn't an Element, so I don't think it can be a source).

I thought that maybe I was missing the CommandTarget property on the MenuItem in the UserControl, and my first thought was that I wanted the command to come from the UserControl, so naturally I first tried:

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

This failed as an invalid binding. I'm not sure why. Then I thought, "Hmmm, I'm looking up the tree, so maybe what I need is a RelativeSource" and I tried this:

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

That also failed, but when I looked at my xaml again, I realized that the ContextMenu is in a property of the UserControl, it's not a child element. So I guessed (and at this point it was a guess):

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

And that also failed.

One failed guess-and-check like this is enough to make me back off and realize that I'm missing some sort of fundamental concept here, though. So what do I do?

  1. Is my understanding re: the role of CommandTarget correct in that this provides a mechanism to modify the source of a command?
  2. How do I bind from a MenuItem in UserControl.ContextMenu to the owning UserControl? Or am I doing something wrong simply because I perceive a need to?
  3. Is my desire to have the context of a command set by the element that was clicked to generate the context menu, as opposed to the element that had focus before the context menu, incorrect? Perhaps I need to write my own command instead of using the RoutedUICommand:

    private static RoutedUICommand viewTaskPropertiesCommand = new RoutedUICommand("View a task's details.", "ViewTaskProperties", typeof(TaskCommands));
    public static RoutedUICommand ViewTaskProperties
    {
        get { return viewTaskPropertiesCommand; }
    }
    

  4. Is there some deeper fundamental flaw in my design? This is my first significant WPF project, and I'm doing it on my own time as a learning experience, so I'm definitely not opposed to learning a superior solution architecture.

解决方案

1: Yes, CommandTarget controls where the RoutedCommand starts routing from.

2: ContextMenu has a PlacementTarget property that will allow access to your UserControl:

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

To avoid repeating this in every MenuItem you could use a Style.

3 & 4: I would say your desire is reasonable. Since the Execute handler is on the Window it doesn't matter right now, but if you had different regions of the application, each with their own Execute handler for the same command, it would matter where the focus was.

这篇关于如何在ContextMenu中为MenuItem设置CommandTarget?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆