为什么人们在iccommands上使用CommandManager.InvalidateRequerySuggested
本文关键字:CommandManager InvalidateRequerySuggested iccommands 为什么 | 更新日期: 2023-09-27 18:17:59
我正在制作一些我自己的自定义ICommand实现,我看到很多实现都是这样的:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
protected void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
据我所知,这是一个优化得很差的代码,因为调用RaiseCanExecuteChanged()
将触发UI中的所有命令来检查它们的ICommand.CanExecute
状态,而通常我们只需要其中一个来验证它。
我想我读过一次这是一些WPF iccommands(如RoutedCommand
)的主代码,对他们来说这是有意义的,因为他们想在一些控制失去焦点和类似的事情后自动重新验证所有iccommands,但我仍然不明白为什么人们为自己的ICommand
实现重复这种模式。
public event EventHandler CanExecuteChanged;
protected void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
我测试了,这是有效的,所以为什么所有的例子在网络上不实现这样简单的东西?我错过什么了吗?
我读过关于在常规事件中使用强引用的内存泄漏问题,其中CommandManager
只使用WeakReferences
,这在视图是垃圾收集的情况下是好的,但是,没有任何解决方案不会损害性能超过内存占用?
为什么网络上所有的例子都没有实现像这样简单的东西?我错过什么了吗?
我猜主要是由于懒惰…你的建议确实是一个更好(更有效)的实现。但是,这还不完全:您仍然需要订阅CommandManager.RequerySuggested
,以便在命令上触发CanExecuteChanged
。
非常简单-如果您在ICommand.CanExecute()
中执行繁重的工作,那么您使用Commands
非常糟糕。如果您遵循该规则,那么实际上调用CommandManager.InvalidateRequerySuggested()
应该没有严重的性能影响。
实际上,这是一个比你建议的简单得多的实现。
就我个人而言,我宁愿在特定的ViewModel
中调用CommandManager.InvalidateRequerySuggested()
,当属性发生变化时,这样对用户的反馈是即时的(即在表单完成/有效后立即启用按钮)。
这个问题很老了,现在是2019年了,但我发现了使用CommandManager.InvalidateRequerySuggested()的另一个原因。
我已经为WPF应用程序编写了我自己的自定义iccommand类,在其中我首先像这样直接调用canexecutechange。
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, null);
}
我的WPF应用程序大量使用不同的线程,当上面的方法从另一个线程调用时,然后是主UI线程,它不会抛出错误,只是被忽略了。更糟糕的是,当我发现调用方法中的所有代码行都被跳过时,这导致了奇怪的结果。
我不知道确切的,但我猜原因是,canexecutechange导致了我的UI的变化,这不能从另一个线程改变。
然而,当我将iccommand更改为CommandManager.InvalidateRequerySuggested()时,就没有更多的问题了。似乎CommandManager.InvalidateRequerySuggested()可以从任何线程调用,UI仍然得到更新。
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
我认为这可能是一个有价值的答案,因为在我得到这个解决方案之前,我调试了这个问题3个小时。发现此问题的问题是,在调试时没有抛出错误。代码被跳过了。非常奇怪的行为
这是对这个问题的回答。这是真的,CanExecuteChanged?.Invoke(this, null);
必须由主UI线程调用。
就像这样写:
public void RaiseCanExecuteChanged()
{
Application.Current.Dispatcher.Invoke(() => CanExecuteChanged?.Invoke(this, null));
}
这解决了您的问题,您可以只请求一个命令。然而,你应该让你的CanExecute
-方法尽可能快,因为它无论如何都会定期执行。最好让CanExecute只包含一个return foo;
,其中foo
是一个可以在调用CommandManager.InvalidateRequerySuggested();
之前设置的字段。
我使用了较简单的解决方案很长时间,没有看到任何问题。我用。net 4.7进行了测试,事件被取消订阅了。所以我认为使用CommandManager
public class CanExecuteTestViewModel : INotifyPropertyChanged
{
public CanExecuteTestViewModel()
{
AddCommand = new MyCommand(_ => Item = new Item());
RemoveCommand = new MyCommand(_ => Item = null);
TestCommand = new MyCommand(delegate { });
}
public MyCommand AddCommand { get; }
public MyCommand RemoveCommand { get; }
public MyCommand TestCommand { get; }
private Item _item;
public Item Item { get { return _item; } set { _item = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Item))); } }
public event PropertyChangedEventHandler PropertyChanged;
}
public class Item { }
public class MyCommand : INotifyPropertyChanged, ICommand
{
private readonly Action<object> _execute;
public MyCommand(Action<object> execute)
{
_execute = execute;
}
private event EventHandler _canExecuteChanged;
public event EventHandler CanExecuteChanged
{
add { _canExecuteChanged += value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SubscriberCount))); }
remove { _canExecuteChanged -= value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SubscriberCount))); }
}
public void RaiseCanExecuteChanged() => _canExecuteChanged?.Invoke(this, EventArgs.Empty);
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _execute.Invoke(parameter);
public event PropertyChangedEventHandler PropertyChanged;
public int SubscriberCount { get { return _canExecuteChanged?.GetInvocationList().Length ?? 0; } }
}
ui 和
<Window x:Class="WpfTest.CanExecuteTest"
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:WpfTest"
mc:Ignorable="d"
x:Name="root"
Height="450" Width="800">
<Window.DataContext>
<local:CanExecuteTestViewModel/>
</Window.DataContext>
<DockPanel>
<TextBlock Text="{Binding TestCommand.SubscriberCount}" DockPanel.Dock="Top"/>
<Button Content="Add" Command="{Binding AddCommand}" DockPanel.Dock="Top"/>
<Button Content="Remove" Command="{Binding RemoveCommand}" DockPanel.Dock="Top"/>
<ContentControl Content="{Binding Item}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<Button Content="Test" Command="{Binding DataContext.TestCommand, ElementName=root}"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DockPanel>
</Window>