解决WPF中ContextMenu绑定RoutedCommand时第一次无法执行的问题 Original yycoding Friday, April 15, 2022 China Standard
作者:互联网
但初次使用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属性。
标签:绑定,15,ContextMenu,Button,Friday,命令,菜单,窗体,Time 来源: https://www.cnblogs.com/cdaniu/p/16520860.html