【问题标题】:ICommand CanExecute not triggering after PropertyChanged?PropertyChanged 后 ICommand CanExecute 不触发?
【发布时间】:2013-01-23 12:04:24
【问题描述】:

我有一个 WPF 应用程序,它显示了一个绑定到类似命令的按钮:

<Button Command="{Binding Path=TestrunStartCommand}" Content="GO!">

命令是这样定义的:

public ICommand TestrunStartCommand
{
    get { return new RelayCommand(TestrunStartExecute, () => !IsTestrunInProgress); }
}

public bool IsTestrunInProgress
{
    get{
        return _isTestrunInProgress;
    }
    set{
        _isTestrunInProgress = value;
        RaisePropertyChanged(IsTestrunInProgressPropertyName);
    }
}   

问题是,在我将IsTestrunInProgress 设置为false 后,按钮不会立即启用,而是只有在我在应用程序窗口内单击后才能启用

您能帮我理解这种行为并告诉我如何解决这个问题吗?

进一步阅读: wpf command pattern - when does it query canexecute

【问题讨论】:

  • only after I click inside the application window;您是否暗示操作系统中当前活动的窗口是不是此程序窗口?或者换句话说,此应用程序已启动并正在运行,但您在记事本中,您只能在后台看到窗口。
  • @MichaelPerrenoud:不,应用程序窗口处于活动状态并具有焦点。似乎只有在我在窗口内单击时才会评估 CanExecuteChanged
  • Galasoft 库中的 RelayCommand 有效地工作
  • @HichemC 我确信它可以有效地工作,错误肯定在我(初学者)方面。但我的错误在哪里?

标签: c# wpf mvvm-light


【解决方案1】:

ICommand 接口公开了一个事件ICommand.CanExecuteChanged,用于通知 UI 何时重新确定命令驱动的 UI 组件的IsEnabled 状态。

根据您使用的RelayCommand 的实现,您可能需要引发此事件;许多实现都公开了一个方法,例如 RelayCommand.RaiseCanExecuteChanged(),您可以调用该方法来强制刷新 UI。

RelayCommand 的某些实现使用了CommandManager.RequerySuggested,在这种情况下,您需要调用CommandManager.InvalidateRequerySuggested() 来强制刷新 UI。

长话短说,您需要从您的属性设置器中调用这些方法之一。

更新

由于在活动焦点发生变化时正在确定按钮的状态,因此我相信正在使用CommandManager。因此,在您的属性的设置器中,在分配支持字段后,调用 CommandManager.InvalidateRequerySuggested()

更新 2

RelayCommand 实现来自 MVVM 轻量级工具包。当从 WPF/.NET 使用时,实现包装从 CommandManager 公开的方法和事件。这将意味着这些命令在大多数情况下(UI 更改或焦点元素更改)会自动运行。但在少数情况下,例如这种情况,您将需要手动强制命令重新查询。使用此库执行此操作的正确方法是在 RelayCommand 上调用 RaiseCanExecuteChanged() 方法。

【讨论】:

  • 你说得对:我的RelayCommand 中有RaiseCanExecuteChanged 方法。但我不明白的是,我有很多工作按钮,而我从未手动引发此事件,并且不知何故它就像魔术一样工作。在这种情况下有什么不同?我认为这个谜题中仍然缺少一些东西......
  • 所有这些按钮都使用RelayCommand的相同实现?您能否告诉我您正在使用哪种实现?即该类来自哪个库?
  • 是的,所有按钮都使用相同的RelayCommand。我正在使用mvvm light framework
  • 如果您使用的是 WPF 4.5 或更高版本,则需要使用命名空间 GalaSoft.MvvmLight.CommandWpf 中的 RelayCommand 而不是 GalaSoft.MvvmLight.Command。这将重新启用 RelayCommand 以使用 CommandManager。
  • 我注意到的另一件重要的事情是,如果您从非 UI 线程设置(导致更改的)属性,则需要通过 Dispatcher 调用上述方法。否则,WPF 只会吞下通知。
【解决方案2】:

这非常重要且容易错过,我重复@Samir 在评论中所说的话。 Laurent Bugnion 先生在他的blog 中写道:

然而,在 WPF 4 和 WPF 4.5 中,有一个问题:将 MVVM Light 升级到 V5 后,CommandManager 将停止工作。您将观察到,当 RelayCommand 的 CanExecute 委托返回 false 时,您的 UI 元素(按钮等)将停止禁用/启用。

如果你赶时间,这里是解决方法:在任何使用 RelayCommand 的类中,替换以下行:

using GalaSoft.MvvmLight.Command;

与:

using GalaSoft.MvvmLight.CommandWpf;

【讨论】:

  • 非常感谢......你拯救了我的周末:)
  • 哭笑不得!我在这上面浪费了很多时间,通常我喜欢 Mvvm Light 但不是今天。
  • 天哪。现在是 2020 年,比最初的答案晚了 5 年,我刚刚花了两个小时的大部分时间试图弄清楚为什么这在一个 View/ViewModel 中不起作用,但在另一个 View/ViewModel 中起作用......两个命令代码都是完全一样...除了一个使用GalaSoft.MvvmLight.Command而不是GalaSoft.MvvmLight.CommandWpf...天哪,这令人沮丧。
  • 你是最棒的!
【解决方案3】:

你可以试试CommandManager.InvalidateRequerySuggested

无论如何,这在过去有时对我没有帮助。对我来说,最好的解决方案是将布尔属性绑定到 Button.IsEnabled 依赖属性。

在你的情况下像

IsEnabled={Binding IsTestrunInProgress}

【讨论】:

  • 是的,像你说的那样使用绑定,它就像一个魅力。
  • 这首先违背了使用命令的目的,并且仅当您的启用状态依赖于单个属性时才有效。
【解决方案4】:

问题是,每当访问 ICommand 属性 TestrunStartCommand 时,它总是返回一个新的命令对象。

一个简单的解决方法是创建一次 ICommand 对象并一次又一次地使用它。

private ICommand _testRunCommand = null;
public ICommand TestrunStartCommand
{
    get 
    { 
        return _testRunCommand ?? (_testRunCommand = new RelayCommand(TestrunStartExecute, () => !IsTestrunInProgress)); 
    }
}

这是一个非常简单的修复,它对我有用。

【讨论】:

    【解决方案5】:

    除了上面 Riegardt Steyn 的回答:https://stackoverflow.com/a/33503341/1964969

    如果您不想将 Command 更改为 CommandWpf 用法(因为两个 RelayCommand 版本之间不兼容),另一种解决方法可能是不在声明位置实例化命令。改用构造函数代码:

    public class SomeVMClass
    {
        // CanExecute won't work:
        // Declaration and instantiation same place
        public RelayCommand MyCommand1 => new RelayCommand(MyBusinessLogic, MyCanExecuteValidator);
    
        // CanExecute will work
        // Declaration only
        public RelayCommand MyCommand2 { get; private set; }
    
        public SomeVMClass()
        {
            // Let's instantiate our declared command
            MyCommand2 = new RelayCommand(MyBusinessLogic, MyCanExecuteValidator);
           ...
    

    【讨论】:

      【解决方案6】:

      块引用

      在您的 Command 类中将 CanExcutedChanged 更改为此

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

      这是我的命令类的示例

      public class SaveConfigCommand : ICommand
      {
          public MyViewModel VM { get; set; }
      
          public event EventHandler CanExecuteChanged
          {
              add { CommandManager.RequerySuggested += value; }
              remove { CommandManager.RequerySuggested -= value; }
          }
      
          public SaveConfigCommand(MyViewModel vm)
          {
              VM = vm;
          }
      
          public bool CanExecute(object? parameter)
          {
              MyObjectModel model = parameter as MyObjectModel;
      
              if (model == null)
                  return false;
      
              // Validate others properties here 
      
              return true;
          }
      
          public void Execute(object? parameter)
          {
              VM.MyMethodInViewModel();
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-28
        • 2017-07-08
        • 2015-04-13
        • 2013-01-01
        • 1970-01-01
        相关资源
        最近更新 更多