其他分享
首页 > 其他分享> > 解决WPF中ContextMenu绑定RoutedCommand时第一次无法执行的问题 Original yycoding Friday, April 15, 2022 China Standard

解决WPF中ContextMenu绑定RoutedCommand时第一次无法执行的问题 Original yycoding Friday, April 15, 2022 China Standard

作者:互联网

WPF中RoutedCommand是一个非常实用的功能,它能够将某一命令绑定到多个控件上,比如同一个命令可以绑定到Button,Menu,ContextMenu上,这样不但可以避免重复,还可以统一行为。

    但初次使用RoutedCommand时我就遇到了一个很奇怪的Bug,就是将右键弹出菜单ContextMenu的某一个菜单MenuItem和窗体上的某个Button同时绑定到了某一个命令RoutedUICommand上。当程序初次运行时,ContextMenu里面的绑定了命令的菜单是灰色的,不可用,即使设置IsEnable=true,也不行。而Button却是正常的,但在点击Button执行一次命令后,ContextMenu里面的菜单就变得可用了。

    在一顿搜索之后发现了这篇文章How to Solve Execution Problems of RoutedCommands in a WPF ContextMenu,完美的解决了这一问题。

问题


     其实这个问题在上面这个链接里说的非常清楚,我这里还是以NTP时间同步这个应用的代码来说明一下情况。在app.xaml的Resouce中我定义了一个RoutedUICommand,Key为"SyncNow",表示用来同步时间:

<Application  x:Class="NTPClock.App"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      Startup="AppOnStartup"
      StartupUri="MainWindow.xaml" >
    <Application.Resources>
        <ResourceDictionary>
            <RoutedUICommand x:Key="SyncNow" />
        </ResourceDictionary>
    </Application.Resources>
</Application>

    紧接着在MainWindow中定义了这个命令的执行委托:

<Window.CommandBindings>
    <CommandBinding Command="{StaticResource SyncNow}"  Executed="CommandBinding_Executed"/>
</Window.CommandBindings>

    紧接着定义了一个ContextMenu里面添加了一个MenuItem,绑定了这个命令。

<Grid.ContextMenu>
    <ContextMenu Name="gridContextMenu">
        <MenuItem Header="{DynamicResource S.TimeSetting.SyncNow}"  Command="{StaticResource SyncNow}"  />
    </ContextMenu>
</Grid.ContextMenu>

     在窗体的Button上也绑定了这个方法:

<Button VerticalAlignment="Center" Name="btnSync"  Command="{StaticResource SyncNow}"  />

原因


    原因在于ContextMenu是一个独立的窗体,它有自己的视觉树和逻辑树。CommandManager会在当前聚焦的范围(focus scope)内查找绑定,如果当前的聚焦范围内没有命令绑定,它会在其父聚焦范围内查找。当应用程序启动时,主窗体的聚焦范围还没有被设定。调用FocusManager.GetFocusElement(this)可以看到他会返回null。

解决方法


     最简单的解决方法是,在程序启动后,在构造函数里调用Focus方法,手动设置聚焦范围。

public MainWindow()
{
    InitializeComponent();
    this.Closed += delegate { Application.Current.Shutdown(); };
    Focus();
}

    或者在xaml里面 定义:

<controls:MetroWindow x:Class="NTPClock.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:NTPClock"
        mc:Ignorable="d"
        Icon="NTPClock.ico"
        ShowIconOnTitleBar="True"
        Title="{DynamicResource S.Title}" 
        FocusManager.FocusedElement="{Binding RelativeSource={x:Static RelativeSource.Self}, Mode=OneTime}"
        Height="450"  Width="550" ResizeMode="CanMinimize" Loaded="MainWindow_OnLoaded">
</controls:MetroWindow>

     这样,CommandManager就能在其父窗体控件的聚焦范围内找到对应的命令绑定。

     还有一种方法是设置CommandTarget对象:

<MenuItem Header="{DynamicResource S.TimeSetting.SyncNow}"  Command="{StaticResource SyncNow}" CommandTarget="{Binding Path=PlacementTarget,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ContextMenu}}}" >

待解决的问题


    在我的程序中,在使用handycontrol添加了一个托盘菜单,托盘菜单里也有一个同步菜单,这个菜单目前还无法绑定到SyncNow命令上,参考开源项目GifRecorder,可能解决的方法是把这些命令放到ViewModel里,然后设置DataContext,就能解决吧。目前还是直接使用Click事件来处理的,不够优雅。

总结


    由于ContextMenu跟窗体都有自己独立的视觉树和逻辑树,所以在将ContextMenu与RoutedCommand绑定时,第一次运行时,由于主窗体的聚焦范围(focus scope)没有被设定,导致CommandManager找不到对应的绑定命令,从而使得ContextMenu里面的MenuItem不可用,这是一个非常常见的问题。解决方法是在主窗体的构造函数里调用Focus方法,或者手动设置MenuItem的CommandTarget属性。

 转载:https://www.yycoding.xyz/post/2022/4/15/solve-execution-problems-of-contextmenu-binding-routedcommands-in-wpf

标签:绑定,15,ContextMenu,Button,Friday,命令,菜单,窗体,Time
来源: https://www.cnblogs.com/cdaniu/p/16520860.html