在单击任何按钮之前,上下文菜单中的 WPF 自定义命令将被禁用

发布于 2024-09-27 05:38:24 字数 1091 浏览 0 评论 0原文

我有一个自定义命令,我尝试从上下文菜单执行它们,但它们始终显示为禁用,除非我单击 UI 上的任何按钮(按钮与命令无关)。

单击按钮后,命令开始正确显示(当它们不可用时,它们会被禁用,如果可用则启用)。

编辑:事实证明,不是单击按钮使命令正常工作,而是按钮或其他控件处于焦点(例如,如果我选项卡进入控件,这也启用我的命令)。

这是命令的代码:

<Window.InputBindings>
    <KeyBinding Command="{x:Static local:MainWindow.Quit}" Key="Q" Modifiers="Ctrl"/>
    <KeyBinding Command="{x:Static local:MainWindow.Disconnect}" Key="D" Modifiers="Ctrl"/>
</Window.InputBindings>

<Window.ContextMenu>
    <ContextMenu Opacity="95">
        <MenuItem Header="Quit Application                  Ctrl + Q"   Command="{x:Static local:MainWindow.Quit}"/>
        <MenuItem Header="Disconnect from the pump   Ctrl + D" Command="{x:Static local:MainWindow.Disconnect}"/>
    </ContextMenu>
</Window.ContextMenu>

这是命令 CanExecuteMethod:

public static RoutedCommand Quit = new RoutedCommand();   

private void QuitCanExecute(object sender, CanExecuteRoutedEventArgs e)
     {
      e.CanExecute = true;
      e.Handled = true;
     }

I have a custom command and I try to execute them from the context menu, but they are always displayed as disabled unless I click any button on the UI (buttons do not have anything to do with commands).

After clicking a button, commands start to be displayed correctly (when they are unavailable they get disabled and enabled if available).

Edit: it turns out that it is not the button click which makes command work correctly, but button or other controls in focus (e.g. if I tab into a control this also enables my commands).

Here is the code for commands:

<Window.InputBindings>
    <KeyBinding Command="{x:Static local:MainWindow.Quit}" Key="Q" Modifiers="Ctrl"/>
    <KeyBinding Command="{x:Static local:MainWindow.Disconnect}" Key="D" Modifiers="Ctrl"/>
</Window.InputBindings>

<Window.ContextMenu>
    <ContextMenu Opacity="95">
        <MenuItem Header="Quit Application                  Ctrl + Q"   Command="{x:Static local:MainWindow.Quit}"/>
        <MenuItem Header="Disconnect from the pump   Ctrl + D" Command="{x:Static local:MainWindow.Disconnect}"/>
    </ContextMenu>
</Window.ContextMenu>

Here is the commands CanExecuteMethod:

public static RoutedCommand Quit = new RoutedCommand();   

private void QuitCanExecute(object sender, CanExecuteRoutedEventArgs e)
     {
      e.CanExecute = true;
      e.Handled = true;
     }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

绮筵 2024-10-04 05:38:24

此问题是由于上下文菜单位于与窗口及其控件不同的视觉和逻辑树上。

对于仍在寻找此问题答案的人 - 在搜索互联网后,我发现最有效的答案是在需要其命令被其“所有者”听到的 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 的人解决我这个周末所经历的头痛……尽管它确实教会了我很多东西!

This issue is due to the ContextMenu being on a separate Visual and Logical Tree to that of the Window and its Controls.

For anyone still looking for an answer to this issue - After trawling the internet I have found the most effective answer to be to include the following in any declaration of a MenuItem that needs its commands to be heard by it's "owner".

In layman's terms; if you want the commands of your context menu to be heard by the thing you're right clicking on. Add this code:

CommandTarget="{Binding Path=PlacementTarget,
                        RelativeSource={RelativeSource AncestorType=ContextMenu}
               }"

Example:

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

This will also work within Templates (something I found a lot of another solutions not to support). Here is an explanation of the meaning of the statement taken from elsewhere (I'm appalling at explaining things):

Every FrameworkElement has a DataContext that is an arbitrary object. The default source for a data binding is that DataContext. You can use RelativeSource.Self to change the source for a binding to the FrameworkElement itself instead of its DataContext. So the RelativeSource part just moves you "up one level" from the DataContext of the FrameworkElement to the FrameworkElement itself. Once you are at the FrameworkElement you can specify a path to any of its properties. If the FrameworkElement is a Popup, it will have a PlacementTarget property that is the other FrameworkElement that the Popup is positioned relative to.

In short, if you have a Popup placed relative to a TextBox for example, that expression sets the DataContext of the Popup to the TextBox and as a result {Binding Text} somewhere in the body of the Popup would bind to the text of the TextBox.

I honestly hope that this information saves someone who's new to WPF the headache I've gone through this weekend... though it did teach me a lot!

空城仅有旧梦在 2024-10-04 05:38:24

现在完全不同的轨道:
ContextMenu 作为命令的载体确实有一些特别之处:
菜单不被视为窗口的一部分,因此其行为不像其可视树中的元素那样。

对于此处定义的问题,有不同的解决方案:
http://www.wpftutorial.net/RoulatedCommandsInContextMenu.html

最简单的方法似乎是添加这个到您的 XAML(对于窗口):

FocusManager.FocusedElement="{Binding RelativeSource={x:Static RelativeSource.Self}, Mode=OneTime}"

Completely different track, now:
there is indeed something special about the ContextMenu as the carrier for commands:
the menu is not regarded as part of the window and therefore does not behave like an element in its visual tree would.

There are different solutions for your problems defined here:
http://www.wpftutorial.net/RoutedCommandsInContextMenu.html

The easiest approach seems to be adding this to your XAML (for the window):

FocusManager.FocusedElement="{Binding RelativeSource={x:Static RelativeSource.Self}, Mode=OneTime}"
离旧人 2024-10-04 05:38:24

我刚刚在尝试为 AvalonDock 实现自定义上下文菜单时遇到了这个问题。上面建议的解决方案都不适合我。

除了主窗口之外,我还通过在 ContextMenu 类上显式注册命令处理程序来使上下文菜单正常工作。下面的函数是我用来注册命令的助手。

    void RegisterCmd(RoutedCommand command, ExecutedRoutedEventHandler handler, CanExecuteRoutedEventHandler canExecute)
    {
        var binding = new CommandBinding(command, handler, canExecute);
        this.CommandBindings.Add(binding);
        CommandManager.RegisterClassCommandBinding(typeof(ContextMenu), binding);
    }

I just ran into this while trying to implement a custom context menu for AvalonDock. None of the solutions suggested above worked for me.

I got the context menu working by explicitly registering my command handlers on the ContextMenu class in addition to the main widow. The function below is a helper I used for command registration.

    void RegisterCmd(RoutedCommand command, ExecutedRoutedEventHandler handler, CanExecuteRoutedEventHandler canExecute)
    {
        var binding = new CommandBinding(command, handler, canExecute);
        this.CommandBindings.Add(binding);
        CommandManager.RegisterClassCommandBinding(typeof(ContextMenu), binding);
    }
魄砕の薆 2024-10-04 05:38:24

可能有一些“幕后”更改通常会启用命令,但视图不知道此更改。
人们需要查看命令实现才能给出更精确的提示。

您可以进行任何更改命令启用状态的操作来通知视图,也可以通过 CommandManager.InvalidateRequerySuggested() 手动触发命令刷新,例如在上下文菜单打开时。

WPF ICommand 就是这样工作的;每当视图中的某些内容发生更改(例如,触发 PropertyChanged 事件或单击按钮)时,它们就会重新查询 CanExecute 函数,但如果没有理由,它们就不会重新查询。

There is probably some change "behind the scenes" that would normally enable the commands, but the view is not aware of this change.
One would need to see the Command-implementations to give more precise hints.

You can either make anything that changes your command-enable-state notify the view or manually trigger a command-refresh via CommandManager.InvalidateRequerySuggested(), for example when the context-menu opens.

WPF ICommands work that way; they requery their CanExecute function whenever something in the view changes (e.g. PropertyChanged-event is fired or a button is clicked), but they don't requery if they have no reason to.

美胚控场 2024-10-04 05:38:24

这是一个已知的错误。如果窗口的主焦点范围内没有焦点元素,则 CanExecute 路由将在 ContextMenu 处停止,因此不会到达 Window 上的 CommandBinding,一种解决方法是将 MenuItem 的 CommandTarget 绑定到主窗口,如下代码所示:

<Window.ContextMenu>
  <ContextMenu >
    <ContextMenu.Items>
     <MenuItem Command="ApplicationCommands.Open"
               CommandTarget="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
    </ContextMenu.Items>
  </ContextMenu>
</Window.ContextMenu>

This is a known bug. If there is no focused element in the window's main focus scope, the CanExecute routing will stop at the ContextMenu, so it will not reach to the CommandBinding on the Window, one workaround is to bind MenuItem's CommandTarget to the main window, as following code demonstrates:

<Window.ContextMenu>
  <ContextMenu >
    <ContextMenu.Items>
     <MenuItem Command="ApplicationCommands.Open"
               CommandTarget="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
    </ContextMenu.Items>
  </ContextMenu>
</Window.ContextMenu>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文