XAML CanExecute应该永远有效还是只在属性更改时有效?

ar7v8xwq  于 2022-12-07  发布在  其他
关注(0)|答案(1)|浏览(120)

我正在使用计算器。我的检视包含16个Button和1个TextBox

TextBoxText属性绑定到ViewModel中的UserInput属性:
第一个
我有3个命令:一个用于向TextBox添加文本,另一个用于显示结果,最后一个用于清除TextBoxAC和**"="**按钮(请参见屏幕截图)绑定到ClearTextCommandShowResultCommand
第一次
程序运行的很好,但是有两个命令有一个共同的问题。我不知道是不是我的无知,但是我在谷歌上没有找到任何东西。我以为我的CanExecute方法只有在UserInput改变的时候才能工作(属性已更改),但我在命令的CanExecute方法上设置了一个断点,并看到它们在调试中始终有效,比如一旦UserInput被修改,ShowResultCommandClearTextCommandCanExecute方法就永远有效,它们在循环中被一次又一次地调用。我想知道它们是否应该这样工作?它们不应该在UserInput被修改时被调用吗?这不会在我的程序中引起任何错误,但我觉得我的命令有问题。
所以基本上问题是:

CanExecute是应该在我的应用运行时循环工作,还是应该在与方法相关的属性更改时工作?如果应该循环工作,则一切正常,但如果不应该循环工作,则我的命令有什么问题?

snz8szmq

snz8szmq1#

我以为我的CanExecute方法只有在UserInput变更(属性变更)时才能运作。

是和否。您的命令将CanExecuteChanged事件委托给CommandManagerRequerySuggested事件。这是一种常见的方法。CommandManager是一种WPF框架类型,负责:
提供与命令相关的实用工具方法,这些方法为类所有者和命令注册CommandBindingInputBinding对象,添加和移除命令事件处理程序,并提供用于查询命令状态的服务。
RequerySuggested事件仅在少数未详细记录的情况下引发:
CommandManager在确定命令目标何时发生变化时仅关注 * 某些条件 *,例如键盘焦点的变化
正如您所看到的,这是一个非常复杂的问题,在某些情况下,CommandManager根本无法知道:
CommandManager无法充分确定导致命令无法执行的条件更改的情况下,可以调用InvalidateRequerySuggested来强制CommandManager引发RequerySuggested事件。
总而言之,是的,当用户输入发生变化以及其他与输入相关的事件(例如,在TextBox中或绑定属性发生变化)时,会引发RequerySuggested事件,但并非在所有情况下都是如此。从另一个Angular 来看,CommandManager仅在 * 常规触发器 * 上确定何时需要引发事件,因此通常它会使 * 所有 * 可执行状态无效。但它并不是针对特定的情况,就像你想观察一个不同的属性的变化,而是像一个水壶。这当然会对性能产生影响,尽管在大多数应用程序中可以忽略不计。

我将断点放在命令的CanExecute方法上,并看到它们在调试中始终有效

是的,现在您已经知道确切的原因了。在调试模式中,当遇到断点时,调试器或IDE将进入前台,这意味着键盘焦点将发生变化。当您切换回正在调试的应用程序时,键盘焦点将再次发生变化......一次又一次......一次又一次。由于CommandManager会在键盘焦点上引发RequerySuggested事件,你将不断地触发CanExecute并命中断点。同样的情况也可能发生在窗口激活上。

不同的命令方法

还有另一种非常常见的方法来通知can execute已更改。在本示例中,您依赖CommandManager来最好地处理所有命令。但是,您也可以自己负责,并通过另一个公共方法显式地使can execute无效。

public class RelayCommand<T> : ICommand
{
   private readonly Predicate<T> _canExecute;
   private readonly Action<T> _execute;

   public RelayCommand(Action<T> execute) : this(execute, null)
   {
      _execute = execute;
   }

   public RelayCommand(Action<T> execute, Predicate<T> canExecute)
   {
      _execute = execute ?? throw new ArgumentNullException(nameof(execute));
      _canExecute = canExecute;
   }

   public bool CanExecute(object parameter)
   {
      return _canExecute == null || _canExecute((T)parameter);
   }

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

   public event EventHandler CanExecuteChanged;

   public void RaiseCanExecuteChanged()
   {
      CanExecuteChanged?.Invoke(this, EventArgs.Empty);
   }
}

这里你假设你知道什么时候更新你自己的命令,而不是问"亲爱的命令管理器,请告诉我什么时候更新",你说"现在更新这些特定的命令"。为此你必须为属性中的每个 * 受影响的 * 命令调用RaiseCanExecuteChanged

public string UserInput
{
   get { return _userInput; }
   set
   {
      if (UserInput != value)
      {
         _userInput = value;
         OnPropertyChanged("UserInput");
         AddTextCmnd.RaiseCanExecuteChanged();
         ClearTextCmnd.RaiseCanExecuteChanged();
         ShowResultCmnd.RaiseCanExecuteChanged();
      }
   }
}

RequerySuggested事件相比,此机制具有一些优点。

  • 您可以决定更新指令的确切时间。
  • 仅更新真正受影响的命令。
  • 理解视图模型中的依赖关系要容易得多。
  • 减少不必要的更新可能带来性能优势。
  • 不依赖于外部组件和隐藏的魔术或vage假设。

关于干燥和可重用性的提示

到目前为止,你所有的命令都包含重复的逻辑,违反了Don't repeat yourself principle,简称DRY。这使得维护变得更加困难。相反,你至少应该提取一个通用的、可重用的命令类型,如上所示,以改进你的代码。你甚至不需要自己实现一个,已经有很多框架、库和NuGet包可用,例如Microsoft.Toolkit.Mvvm。请在此处查看其documentation
以下是明文命令的示例:

public ViewModel()
{
   ClearTextCmnd = new RelayCommand(ExecuteClearText, CanExecuteClearText);
   // ...other commands.
}

public ICommand ClearTextCmnd { get; set; }

public bool CanExecuteClearText()
{
   return UserInput != "0";
}

public void ExecuteClearText()
{
   UserInput = "0";
}

为了简单起见,我使用了一个没有额外参数的RelayCommand实现,因为您目前没有使用它。它与上面的命令相同,只是每个方法都没有参数。
正如注解中所要求的,这将是没有参数的RelayCommand实现。

public class RelayCommand : ICommand
{
   private readonly Func<bool> _canExecute;
   private readonly Action _execute;

   public RelayCommand(Action execute) : this(execute, null)
   {
      _execute = execute;
   }

   public RelayCommand(Action execute, Func<bool> canExecute)
   {
      _execute = execute ?? throw new ArgumentNullException(nameof(execute));
      _canExecute = canExecute;
   }

   public bool CanExecute(object parameter)
   {
      return _canExecute == null || _canExecute();
   }

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

   public event EventHandler CanExecuteChanged;

   public void RaiseCanExecuteChanged()
   {
      CanExecuteChanged?.Invoke(this, EventArgs.Empty);
   }
}

相关问题