为什么人们在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,这在视图是垃圾收集的情况下是好的,但是,没有任何解决方案不会损害性能超过内存占用?

为什么人们在iccommands上使用CommandManager.InvalidateRequerySuggested

为什么网络上所有的例子都没有实现像这样简单的东西?我错过什么了吗?

我猜主要是由于懒惰…你的建议确实是一个更好(更有效)的实现。但是,这还不完全:您仍然需要订阅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>