【问题标题】:Why is this WPF RoutedCommand bound Context MenuItem disabled?为什么这个 WPF RoutedCommand 绑定的 Context MenuItem 被禁用?
【发布时间】:2009-01-18 17:38:55
【问题描述】:

目前我仍在摸索 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,它就可以工作,并且一旦执行,上下文菜单就会启用!

干杯,伙计们, 克里斯。

【问题讨论】:

    标签: wpf menuitem


    【解决方案1】:

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

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

    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>
    

    注意没有绑定。另外,请注意&lt;ContextMenu&gt; 中的&lt;CommandBinding&gt;。 这是一个参考...... http://www.wiredprairie.us/journal/2007/04/commandtarget_menuitem_context.html

    被禁用的命令地址为this site

    【讨论】:

    • 实际上,如果您在 XAML 中有 声明,则可以跳过 ctor 中的 CommandBindings.Add() ......如果这有意义的话。
    • 菜单项最初显示为禁用的链接对我来说是关键。谢谢
    【解决方案2】:

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

    <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}"/>
    

    【讨论】:

      【解决方案3】:

      对于任何寻求此问题答案的人 - 在搜索互联网后,我发现最有效的答案是将以下内容包含在需要其“所有者”听到其命令的 MenuItem 的任何声明中。

      通俗地说;如果您希望右键单击的内容可以听到上下文菜单的命令。添加此代码:

      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} 将绑定到文本的文本框。

      我真诚地希望这些信息可以让 WPF 新手免去我这个周末所经历的头痛……尽管它确实教会了我很多东西!

      史蒂夫

      【讨论】:

        【解决方案4】:

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

        但我确实记得在某处读过,弹出窗口在某些情况下不应表现得像普通窗口,某些东西应该“冒泡”。

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

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

        【讨论】:

          【解决方案5】:

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

          博文将解释为什么在构造函数中调用 Focus() 有效。

          【讨论】:

            猜你喜欢
            • 2013-03-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-04-13
            • 1970-01-01
            • 2018-07-15
            • 1970-01-01
            • 2015-08-21
            相关资源
            最近更新 更多