【问题标题】:ICommand CanExecuteChanged is always nullICommand CanExecuteChanged 始终为空
【发布时间】:2021-07-29 08:23:54
【问题描述】:

我有一个带有视图的基本 MVVM WPF 应用程序,由一个 texbox 和提交按钮组成。两个控件都正确绑定到 ViewModel 中的属性和命令。问题在于 CanSubmit 未触发,因为 CanExecuteChanged 事件处理程序(在 DelegateCommand 中)始终为空。基本上问题是如何正确通知命令在更新 textox 时运行 CanExecute 检查。

public DelegateCommand  SubmitCommand => new DelegateCommand(Submit, CanSubmit);

private string _company;
public string Company
 {
     get => _company;
     set
     {
         SetProperty(ref _company, value);
         SubmitCommand.RaiseCanExecuteChanged();
      }
  }

我的委托命令

public class DelegateCommand : ICommand
{
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;

    public event EventHandler CanExecuteChanged;

    public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public DelegateCommand(Action<object> execute) : this(execute, null) { }

    public virtual bool CanExecute(object parameter)
    {
        if (_canExecute == null)
        {
            return true;
        }

        return _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public void RaiseCanExecuteChanged()
    {
        if(CanExecuteChanged != null) <------ Always null
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

【问题讨论】:

  • 也许您可以为您的事件使用用户定义的添加和删除访问器,并在此访问器中设置断点以便更好地了解发生了什么?

标签: c# wpf


【解决方案1】:

我认为问题在于用于初始化 get only 属性的较新 C# 功能的一个非常微妙的细微差别。使用您拥有的语法:

public DelegateCommand  SubmitCommand => new DelegateCommand(Submit, CanSubmit);

每次调用 SumitCommand getter 时都会返回一个新的 Command 实例。您在 XAML 中绑定的 SubmitCommand 实例与 Company setter 中的实例不同:SubmitCommand.RaiseCanExecuteChanged(); setter 的 SubmitCommand 实例当然没有 CanExecuteChaned 的处理程序,因为它对 UI 是未知的。并且后面的代码中没有处理程序。

如果您将其更改为:

public DelegateCommand  SubmitCommand { get; } = new DelegateCommand(Submit, CanSubmit);

我认为问题将得到解决。 因为您只需创建一次命令,按钮 CanExecuteChanged 处理程序将绑定到公司设置器为其调用 RaiseCanExecuteChanged 的​​同一 SubmitCommand 实例。

【讨论】:

    【解决方案2】:

    我会补充@lidqy 的答案。
    他完全正确,问题在于每次访问属性时都会创建一个新命令。
    命令的实例必须始终相同。

    Submit 和 CanSubmit 通常是实例方法。
    但是字段(属性)初始化器只能访问静态成员。

    有两种典型的初始化命令的方法:

    1. ViewModel 构造函数中的初始化:
            public DelegateCommand SubmitCommand { get; }
    
            public ViewModel()
            {
                SubmitCommand = new DelegateCommand(Submit, CanSubmit);
            }
    
    1. 第一次调用属性时初始化:
        private DelegateCommand _submitCommand;
    
        public DelegateCommand SubmitCommand => _submitCommand
            ?? (_submitCommand = new DelegateCommand(Submit, CanSubmit));
    

    其他建议。
    如果您有 WPF 解决方案(不是 UWP),则应将该命令订阅到 CommandManager.RequerySuggested。
    在这种情况下,当 GUI 发生变化时,将自动调用命令验证。
    并且在属性设置器中执行此操作的需要将消失。

        public class DelegateCommand : ICommand
        {
            private readonly Predicate<object> _canExecute;
            private readonly Action<object> _execute;
    
            private readonly EventHandler requerySuggested;
            public event EventHandler CanExecuteChanged;
    
            public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
            {
                _execute = execute;
                _canExecute = canExecute;
                requerySuggested = (s, e) => RaiseCanExecuteChanged();
                CommandManager.RequerySuggested += requerySuggested;
            }
    
            public DelegateCommand(Action<object> execute) : this(execute, null) { }
    
            public virtual bool CanExecute(object parameter)
            {
                if (_canExecute == null)
                {
                    return true;
                }
    
                return _canExecute(parameter);
            }
    
            public void Execute(object parameter)
            {
                _execute(parameter);
            }
    
            public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    
            private string _company;
            public string Company { get => _company; set => SetProperty(ref _company, value); }
    

    还请记住,对于绑定到 GUI 中 WPF 元素的命令,应始终在应用程序的主线程上调用 CanExecuteChanged 事件。
    这也适用于在属性设置器中调用它,因为属性不仅可以从 GUI 更改。
    另请参阅这些实现:BaseInpc, RelayCommand and RelayCommand<T> classes

    【讨论】:

    • 第一种方法效果很好,感谢您对requerySuggested的额外建议
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-31
    • 2019-07-18
    • 2019-08-18
    • 2011-12-29
    • 2019-07-02
    相关资源
    最近更新 更多