什么时候会调用CanExecute?

36
在演示中,我有一个按钮用于切换布尔字段isAsking。我创建了一个命令,只能在isAsking==true时执行。

一旦我按下切换按钮,okButton.IsEnable立即更改,这表明命令发现了isAsking的更改。

我感到非常困惑,为什么命令对象会注意到字段的更改。CanExecute何时被调用?

虽然我写WPF应用程序已经有一段时间了,但我是WPF命令的新手。请对此情况进行解释,如果可能的话,请指出一些相关的文章或博客(我已经阅读了太多关于剪切/粘贴命令的文章)。

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="WpfApplication1.MainWindow"
        Title="MainWindow" Height="350" Width="525" x:Name="mainWindow" >
    <StackPanel>
        <Button Name="okButton" Content="Ok" />
        <Button Content="Toggle"  Click="Button_Click_1"/>
    </StackPanel>
</Window>

代码后端:

public partial class MainWindow : Window
{
    private bool isAsking;

    public MainWindow()
    {
        InitializeComponent();

        CommandBinding cb = new CommandBinding();
        cb.Command = okCommand;
        cb.CanExecute += CanOKExecute;
        cb.Executed += cb_Executed;
        mainWindow.CommandBindings.Add(cb);
        okButton.Command = okCommand;
    }

    private RoutedCommand okCommand = new RoutedCommand("ok", typeof(MainWindow));


    void cb_Executed(object sender, ExecutedRoutedEventArgs e)
    {
       
    }

    void CanOKExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = isAsking;
    }

    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        isAsking = !isAsking;
    }
}
4个回答

41
技术上来讲,只要触发CommandManager.RequerySuggested事件时,CanExecute就会被调用。根据文档,此事件将在命令的可执行能力可能改变时发生。实际上,这意味着您不需要担心何时调用CanExecute: WPF会在认为适当的时候调用它,而且根据我的经验,这几乎总是符合您的需求。唯一的例外情况是如果您有一个后台任务,会导致CanExecute基于UI未触发的某些内容更改其返回值。在这种情况下,您可能需要手动强制WPF运行时重新查询CanExecute,可以通过调用CommandManager.InvalidateRequerySuggested来实现。

7
如果这是一个 Prism 的 DelegateCommand,则需要在 Command 自身上调用 RaiseCanExecuteChanged() - default
4
@Default为true。同样,如果是您自己的ICommand实现,您可以手动在命令上引发“CanExecuteChanged”事件。 - Steve Greatrex
哎呀呀.. 我完全忘记了ICommand中的那个事件.. 我还以为Prism已经想到了什么好办法 :) - default
考虑到您无法控制其运行频率,因此需要确保它不会执行大量操作,例如不要在其中查询数据库。 - Paul McCarthy

32

我尝试搜索"命令管理器检测条件"并找到了这篇优秀的文章

作者通过检查.NET Framework源代码发现,CommandManager并不是自己检测条件,而是当Keyboard.KeyUpEventMouse.MouseUpEventKeyboard.GotKeyboardFocusEventKeyboard.LostKeyboardFocusEvent事件发生时,它会重新评估CanExecute方法。

文章包含其他信息,但上面的部分已经足够我使用。


10
我略感惊讶于这种默认行为。如果一个表单有很多控件,并且 CanExecute 方法被不理想地编写,那么这将产生大量可能无用的 CanExecute 评估,是吗?你在看到架构如何扩展方面遇到了困难吗? - Shiv

9

RoutedCommand 包含一个事件 CanExecuteChanged,它内部挂钩到了 CommandManager.RequerySuggested 事件 -

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

每当命令源(在此情况下为窗口)检测到更改时,就会引发CommandManager.RequerySuggested事件。因此,当单击按钮时,命令管理器将引发RequerySuggested事件,从而执行为命令注册的CanExecute谓词。

此外,CommandManager还有一个静态方法-InvalidateRequerySuggested,它强制CommandManager引发RequerySuggestedEvent。因此,您也可以手动调用该方法来验证您的命令。


1

默认情况下展开 评论

假设我们有以下内容

public class SomeClass : ViewModelBase {
   public ICommand ConnectButtonCommand { get; }
   public SomeClass(){
      //...
      ConnectButtonCommand = new DelegateCommand(ConnectButton_Click, ConnectButton_CanExecute);
      //...
   }
   public DoSomething(){
      //do something that affects the result of ConnectButton_CanExecute
      ((DelegateCommand)ConnectButtonCommand).RaiseCanExecuteChanged();
   }
   private void ConnectButton_Click() {/*...*/}
   private bool ConnectButton_CanExecute() {/*...*/}
}

我正在开发一个使用Prism for MVVM的UWP应用程序。Universal Windows平台与WPF非常相似。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接