为什么禁用此WPF RoutedCommand绑定Context MenuItem?

时间:2009-01-18 17:38:55

标签: wpf menuitem

此刻我仍然在摸索WPF,并且无法弄清楚为什么禁用此上下文菜单项:

<Window x:Class="DisabledMenuItemProblem.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DisabledMenuItemProblem"
        Title="Window1" Height="300" Width="300">
    <TextBlock Text="fooooobaaaaaar">
        <TextBlock.ContextMenu>
            <ContextMenu>
                <MenuItem Header="Foo" Command="{x:Static local:MyCommands.FooBar}" />
            </ContextMenu>
        </TextBlock.ContextMenu>
    </TextBlock>
</Window>

using System.Windows;
using System.Windows.Input;

namespace DisabledMenuItemProblem
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            CommandBindings.Add(new CommandBinding(MyCommands.FooBar, FooExecuted, CanFooExecute));
        }

        public void FooExecuted(object sender, ExecutedRoutedEventArgs e)
        { MessageBox.Show("Foo!"); }

        public void CanFooExecute(object sender, CanExecuteRoutedEventArgs e)
        { e.CanExecute = true; }
    }

    public static class MyCommands
    { 
        public static RoutedCommand FooBar = new RoutedCommand(); 
    }
}

我缺少什么?

令我感到困惑的是,如果我在窗口中抛出一个按钮并将其命令设置为FooBar,它会起作用,一旦执行完毕,就会启用上下文菜单!

干杯队员, 克里斯。

5 个答案:

答案 0 :(得分:10)

这是我使用的一般模式......

首先,将命令保存在自己的静态类中,这样可以促进重用等等。

public static class MyCommands
{
    public static RoutedUICommand CmdFoo = new RoutedUICommand("CmdFoo", 
                                                               "CmdFoo", 
                                                               typeof(MyCommands));
}

第二,在control / window / etc中注册命令。你想要使用它,通常在构造函数

public MyControl
{
    public MyControl()
    {
        CommandBindings.Add( 
            new CommandBinding( MyCommands.CmdFoo,   // this is the command object
                                XCutFooCommand,      // execute
                                CanXCuteFooCommand));// can execute?
    }

第三,在控件/窗口/等中创建处理程序.....

  public void CanExecuteRerollCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;  // can this command be executed?
        e.Handled = true;     // has this event been handled?
    }
    public void ExecuteRerollCommand(object sender, ExecutedRoutedEventArgs e)
    {
    // do stuff
    }
}

最后,你的xaml应该是这样的:

    <ContextMenu>
        <ContextMenu.CommandBindings>
            <CommandBinding Command="foo:MyCommands.CmdFoo" 
                            CanExecute="CanExecuteRerollCommand" 
                            Executed="ExecuteRerollCommand" />
        </ContextMenu.CommandBindings>
        <MenuItem Header="Reroll"  Command="foo:MyCommands.CmdFoo"/>
    </ContextMenu>

注意没有绑定。另请注意<CommandBinding>中的<ContextMenu>。 这是一个参考.... http://www.wiredprairie.us/journal/2007/04/commandtarget_menuitem_context.html

正在禁用的命令在this site

处理

答案 1 :(得分:8)

对于寻找这个问题答案的任何人 - 在浏览互联网之后,我发现最有效的答案是在菜单项的任何声明中包含以下内容,需要其命令被其“所有者”听到。< / p>

外行人的说法;如果你想要你正确点击的东西听到你的上下文菜单的命令。添加以下代码:

CommandTarget =“{Binding Path = PlacementTarget,RelativeSource = {RelativeSource AncestorType = ContextMenu}}”

示例:

    <ContextMenu>
        <MenuItem Header="Close" Command="Application.Close" CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
    </ContextMenu>

这也适用于模板(我发现很多其他解决方案都不支持)。以下是对从其他地方采取的陈述的含义的解释(我在解释事情时骇人听闻):

  

每个FrameworkElement都有一个DataContext,它是一个任意对象。数据绑定的默认源是DataContext。您可以使用RelativeSource.Self来更改绑定到FrameworkElement本身而不是其DataContext的源。因此,RelativeSource部分只是将您从FrameworkElement的DataContext“上升一级”移动到FrameworkElement本身。进入FrameworkElement后,您可以指定其任何属性的路径。如果FrameworkElement是Popup,它将具有PlacementTarget属性,该属性是Popup相对于其定位的另一个FrameworkElement。

     

简而言之,如果你有一个相对于TextBox放置的Popup,那么该表达式将Popup的DataContext设置为TextBox,结果Popup正文中的某个地方的{Binding Text}将绑定到文本TextBox。

老实说,我希望这些信息可以帮助那些对WPF不熟悉的人,这个周末我经历过的头痛......虽然它确实教会了我很多!

史蒂夫

答案 2 :(得分:2)

据我所知,这就是发生的事情。 当显示ContextMenu时,它显示在Popup中,它基本上是一个单独的Window。弹出窗口不属于与窗口中主要内容相同的可视树,因此命令不会“冒泡”到主窗口中。这就是永远不会调用CanExecute方法的原因。例如,如果您在ContextMenu本身附加CommandBindings,则将正确调用CanExecute。

但是,我确实记得在某些地方读过Popup在某些情况下不应该像普通的Window一样,某些东西应该“冒泡”。

我认为必须有一些内在的魔力。如果您只是将TextBlock更改为TextBox,例如它似乎工作。我敢打赌Reflector会在TextEditorBase或类似的东西中向你展示一些额外的逻辑。

如果你真的需要使用TextBlock,我可能会手动将CommandBinding添加到ContextMenu本身,而不是在窗口上。

答案 3 :(得分:1)

我发现解决此问题的最简单方法是将上下文菜单移动到窗口资源中并从那里引用它

<ContextMenu x:Key="ControlContextMenu">
    <ContextMenu.CommandBindings>
        <CommandBinding Command="{StaticResource CloseCommand}" Executed="CloseExecuted" CanExecute="CloseCanExecute" />
    </ContextMenu.CommandBindings>          
    <MenuItem Command="{StaticResource CloseCommand}" />
</ContextMenu>

然后在UIElement上设置ContextMenu属性

<TextBlock ContextMenu="{StaticResource ControlContextMenu}"/>

答案 4 :(得分:0)

更简单的答案是在Window的构造函数中添加对 Focus()的调用。我昨天碰到了这个问题,花了很多时间弄清楚发生了什么。我在这里写了博客:http://cebla5.spaces.live.com/blog/cns!1B8262ED00250003!206.entry

博客文章将解释为什么在构造函数中调用 Focus()可以正常工作。