Why RelayCommand

61

最近我在大量使用WPF进行编程,但我的视图(View)和视图模型(ViewModel)尚未分离,或者说只是部分分离。所有与文本框中的文本、标签的内容以及DataGrid中的列表有关的绑定都是通过带有NotifyPropertyChanged事件的常规属性完成的。

我处理按钮单击或文本更改等事件的所有代码都是通过事件链接完成的。现在,我想开始使用命令并找到了这篇文章:http://www.codeproject.com/Articles/126249/MVVM-Pattern-in-WPF-A-Simple-Tutorial-for-Absolute。它解释了如何设置MVVM,但我对RelayCommand感到困惑。

它的作用是什么? 它是否可用于表单中的所有命令? 当某些文本框未填充时,如何使按钮失效?


编辑 1:

"是否可用于表单中的所有命令?"的一个好的解释已经在这里回答:https://dev59.com/dGEh5IYBdhLWcg3wjEDn#22286816

这是我目前拥有的代码:https://stackoverflow.com/a/22289358/3357699


这里指的是所有表单命令吗? - Rohit Vats
ClickTextChanged - Krowi
对于 Click,您可以直接将按钮的 Command DP 绑定到 ViewModel 中的某个 ICommand。但是要绑定 TextChanged,您需要使用交互触发器来绑定 ViewModel 中的 ICommand - Rohit Vats
2
RelayCommand 实现了 ICommand 接口,让你定义一个在执行时将被使用的操作。这就是你如何使用一个类来处理所有命令,而不是为每个实现 ICommand 的命令创建一个类。在 WPF 中使用命令需要 ICommand。 - dev hedgehog
我自己尝试了一些东西并发布了我得到的结果。我让它工作了,但仍有两个问题,我希望在这里得到答案,而不是提出一个新问题。 - Krowi
@devhedgehog对为什么需要这样一个类有最好的解释(OP中的第一个问题)。 - John Jesus
2个回答

97
命令被用于将调用命令的语义和对象与执行命令的逻辑分离,即将UI组件与需要在命令调用时执行的逻辑分离。这样,您就可以使用测试用例单独测试业务逻辑,并且您的UI代码与业务逻辑松散耦合。现在,既然这样说了,我们来一个一个回答你的问题:“它是做什么工作的?”我已经在上面添加了详细信息。希望它能清楚地说明命令的使用。

这适用于表单中的所有命令吗?

一些控件暴露了命令依赖属性,如Button、MenuItem等,它们有一些默认事件注册在其中。对于Button来说,它是Click事件。因此,如果将ViewModel中声明的ICommand与Button的Command DP绑定,每当单击按钮时都会调用它。

对于其他事件,您可以使用交互触发器进行绑定。请参考示例here,了解如何使用它们绑定到ViewModel中的ICommand。


当某些文本框未填写时,如何使按钮失效?
您发布的链接没有提供完整的RelayCommand实现。缺少重载构造函数来设置CanExecute谓词,它在启用/禁用绑定到命令的UI控件中起着关键作用。
将TextBox与ViewModel中的一些属性绑定,并在CanExecute委托中返回false,如果任何绑定的属性为null或为空,则自动禁用命令绑定的控件。

RelayCommand的完整实现:

public class RelayCommand<T> : ICommand
{
    #region Fields

    readonly Action<T> _execute = null;
    readonly Predicate<T> _canExecute = null;

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes a new instance of <see cref="DelegateCommand{T}"/>.
    /// </summary>
    /// <param name="execute">Delegate to execute when Execute is called on the command.  This can be null to just hook up a CanExecute delegate.</param>
    /// <remarks><seealso cref="CanExecute"/> will always return true.</remarks>
    public RelayCommand(Action<T> execute)
        : this(execute, null)
    {
    }

    /// <summary>
    /// Creates a new command.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    /// <param name="canExecute">The execution status logic.</param>
    public RelayCommand(Action<T> execute, Predicate<T> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }

    #endregion

    #region ICommand Members

    ///<summary>
    ///Defines the method that determines whether the command can execute in its current state.
    ///</summary>
    ///<param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
    ///<returns>
    ///true if this command can be executed; otherwise, false.
    ///</returns>
    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute((T)parameter);
    }

    ///<summary>
    ///Occurs when changes occur that affect whether or not the command should execute.
    ///</summary>
    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    ///<summary>
    ///Defines the method to be called when the command is invoked.
    ///</summary>
    ///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null" />.</param>
    public void Execute(object parameter)
    {
        _execute((T)parameter);
    }

    #endregion
}

2
这是一个非常好的解释和示例,但我还是有点困惑。我以前用C#编程,但为了学校不得不转到VB.NET,所以这不是问题,但是,现在我该如何链接一个命令并在我的视图模型中说明它要做什么,比如显示一个带有“TextBox”内容的“MessageBox”。 - Krowi
1
使用在您提供的链接中已经非常清晰了。还是让我简要解释一下 - 在ViewModel中创建一个RelayCommand实例,在execute方法中,您可以编写MessageBox.Show(parameter),其中参数将通过使用CommandParameter从GUI传递。在网络上搜索,您会找到数百万个示例。 - Rohit Vats
1
我自己尝试了一些东西并发布了我得到的结果。它可以工作,但我还有两个问题,希望在这里得到答案,而不是提出一个新问题。 - Krowi
3
我刚在YouTube上观看了这个视频,链接是https://www.youtube.com/watch?v=ysWK4e2Mtco,在视频`19:30`的时候,他建议不要使用CommandManager.RequerySuggested。 - Timeless
2
微小的问题:Microsoft 建议使用 Func<T, bool> 代替 Predicate<T> - NathanAldenSr

12
使用中继命令的好处在于您可以直接将命令绑定到ViewModels上。通过这种方式使用命令,避免编写视图代码behind(即视图代码文件)。使用中继命令时,您需要提供两个方法。第一个方法提供值来判断是否可执行该命令(例如“CanExecuteSave”),而另一个方法则负责执行命令(“ExecuteSave”)。

能否给我提供一个简单的例子,包含一个按钮和一个文本框?只有当文本框中有文本时,按钮才能激活。在我之前发布的链接中,如何实现这一点有点令人困惑。编辑:到目前为止,您的答案已经解决了一个问题 :) - Krowi
想象一下一个带有“Text”属性的ViewModel,该属性绑定到按钮的文本属性。由于中继命令逻辑完全在ViewModel中,您可以直接访问按钮的文本并验证其值。 - Dennis Kassel
我自己尝试了一些东西并发布了我得到的结果。我让它工作了,但仍有两个问题,我希望在这里得到答案,而不是提出一个新问题。 - Krowi
1
将命令的代码隔离在 Command 类本身中,而不是与 ViewModel 耦合,这样不是更好吗? - vargonian

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