C# 简化WPF MVVM视图模型中的RelayCommand/DelegateCommand

C# 简化WPF MVVM视图模型中的RelayCommand/DelegateCommand,c#,wpf,mvvm,relaycommand,delegatecommand,C#,Wpf,Mvvm,Relaycommand,Delegatecommand,如果您正在执行MVVM并使用命令,则通常会在ViewModel上看到ICommand属性,这些属性由专用RelayCommand或DelegateCommand字段支持,如上的原始MVVM文章中的示例: 然而,这是一个很大的混乱,并且使设置新命令变得相当乏味(我与一些经验丰富的WinForms开发人员一起工作,他们对所有这些类型都不感兴趣)。所以我想把它简化一点,再深入一点。我在get{}块的第一行设置了一个断点,发现它只有在我的应用程序第一次加载时才被击中——我可以在以后发出我想要的任意多的命

如果您正在执行MVVM并使用命令,则通常会在ViewModel上看到ICommand属性,这些属性由专用RelayCommand或DelegateCommand字段支持,如上的原始MVVM文章中的示例:

然而,这是一个很大的混乱,并且使设置新命令变得相当乏味(我与一些经验丰富的WinForms开发人员一起工作,他们对所有这些类型都不感兴趣)。所以我想把它简化一点,再深入一点。我在get{}块的第一行设置了一个断点,发现它只有在我的应用程序第一次加载时才被击中——我可以在以后发出我想要的任意多的命令,而这个断点永远不会被击中——所以我想简化它,以从我的ViewModels中删除一些杂乱的内容,并注意到以下代码的工作原理是相同的:

public ICommand SaveCommand
{
    get
    {
        return new RelayCommand(param => this.Save(), param => this.CanSave );
    }
}

但是,我对C#或垃圾收集器了解不够,无法知道这是否会导致问题,例如在某些情况下生成过多的垃圾。这会带来任何问题吗?

我发现,如果有多个控件调用相同的命令,则需要使用MSDN的原始方式,否则每个控件将新建自己的RelayCommand。我没有意识到这一点,因为我的应用程序每个命令只有一个控件

因此,为了简化ViewModels中的代码,我将创建一个命令包装类,该类存储(并延迟实例化)所有RelayCommand,并将其放入我的ViewModelBase类中。这样,用户不必直接实例化RelayCommand或DelegateCommand对象,也不需要了解它们的任何信息:

    /// <summary>
    /// Wrapper for command objects, created for convenience to simplify ViewModel code
    /// </summary>
    /// <author>Ben Schoepke</author>
    public class CommandWrapper
    {
    private readonly List<DelegateCommand<object>> _commands; // cache all commands as needed

    /// <summary>
    /// </summary>
    public CommandWrapper()
    {
        _commands = new List<DelegateCommand<object>>();
    }

    /// <summary>
    /// Returns the ICommand object that contains the given delegates
    /// </summary>
    /// <param name="executeMethod">Defines the method to be called when the command is invoked</param>
    /// <param name="canExecuteMethod">Defines the method that determines whether the command can execute in its current state.
    /// Pass null if the command should always be executed.</param>
    /// <returns>The ICommand object that contains the given delegates</returns>
    /// <author>Ben Schoepke</author>
    public ICommand GetCommand(Action<object> executeMethod, Predicate<object> canExecuteMethod)
    {
        // Search for command in list of commands
        var command = (_commands.Where(
                            cachedCommand => cachedCommand.ExecuteMethod.Equals(executeMethod) &&
                                             cachedCommand.CanExecuteMethod.Equals(canExecuteMethod)))
                                             .FirstOrDefault();

        // If command is found, return it
        if (command != null)
        {
            return command;
        }

        // If command is not found, add it to the list
        command = new DelegateCommand<object>(executeMethod, canExecuteMethod);
        _commands.Add(command);
        return command;
    }
}
//
///命令对象的包装器,为简化ViewModel代码而创建
/// 
///本·舍普克
公共类命令包装器
{
私有只读列表_commands;//根据需要缓存所有命令
/// 
/// 
公共命令包装器()
{
_commands=newlist();
}
/// 
///返回包含给定委托的ICommand对象
/// 
///定义调用命令时要调用的方法
///定义确定命令是否可以在其当前状态下执行的方法。
///如果应始终执行该命令,则传递null。
///包含给定委托的ICommand对象
///本·舍普克
公共ICommand GetCommand(Action executeMethod、谓词canExecuteMethod)
{
//在命令列表中搜索命令
var command=(_commands.Where(
cachedCommand=>cachedCommand.ExecuteMethod.Equals(ExecuteMethod)&&
cachedCommand.CanExecuteMethod.Equals(CanExecuteMethod)))
.FirstOrDefault();
//如果找到命令,则返回它
if(命令!=null)
{
返回命令;
}
//如果未找到命令,请将其添加到列表中
command=新的DelegateCommand(executeMethod、canExecuteMethod);
_命令。添加(命令);
返回命令;
}
}

该类也由ViewModelBase类延迟实例化,因此没有任何命令的ViewModels将避免额外的分配。

我要做的一件事是让Visual Studio为我进行键入。我刚刚创建了一个代码段,它允许我通过键入

rc选项卡保存输入

rc是代码段快捷方式 选项卡加载您键入的文本,并创建所有其他文字

一旦你看了一段代码并创建了自己的代码片段,你就再也回不去了:)


有关创建代码段的详细信息:

这与提供计算某个常量值的-say integer-属性完全相同。 您可以为get方法的每次调用计算它,也可以在第一次调用时创建它,然后缓存它,以便为以后的调用返回缓存值。 因此,如果getter最多被调用一次,那么根本没有什么区别。如果经常调用,您将失去一些(不太多)性能,但不会遇到真正的麻烦

我个人喜欢这样缩写MSDN:

RelayCommand _saveCommand;
public ICommand SaveCommand
{
  get
  {
    return _saveCommand ?? (_saveCommand = new RelayCommand(param => this.Save(),
                                                            param => this.CanSave ));
  }
}

你为什么不写下:

private readonly RelayCommand _saveCommand = new RelayCommand(param => this.Save(),
                param => this.CanSave );;

public ICommand SaveCommand { get { return _saveCommand; } }

当您在viewmodel上公开ICommand属性并且该属性没有支持字段时,这是可以的,只要您只绑定到该字段一次。基本上,当表单加载并执行初始绑定时,这是它唯一一次访问命令的get属性

很多时候,您只需绑定一次命令

如果将同一命令绑定到多个控件,则需要backing字段


当您在viewmodel上公开ICommand属性并且该属性没有支持字段时,这是可以的,只要您只绑定到该字段一次


CommandWrapper的GetCommand方法将返回已创建的命令。

我猜是为了防止在不需要创建对象的情况下创建对象,但我不知道它使用的内存或创建命令的其他副作用……我们希望通过延迟实例化来节省内存。最后,我向一个ViewModelBase类添加了一些代码,该类存储RelayCommand列表,并有一个名为GetCommand()的方法。所以,当我们实现ViewModel时,创建命令所需做的就是创建一个ICommand属性,该属性使用execute和canExecute委托调用GetCommand(),ViewModel实现者不需要知道任何关于RelayCommand/Delegate命令的信息。我不会开始优化命令,因为命令的内存占用太少。明白了。我在一个嵌入式系统上工作,所以我必须最小化内存使用,特别是在像我们的ViewModelBase这样的大量使用的基类中。我不知道为什么没有人提到这一点。这就是我所做的,它是迄今为止处理命令样板代码的最佳方法。所有这些懒惰->懒惰的东西实际上并不能在长期内节省任何时间或代码
private readonly RelayCommand _saveCommand = new RelayCommand(param => this.Save(),
                param => this.CanSave );;

public ICommand SaveCommand { get { return _saveCommand; } }