Упрощение RelayCommand / DelegateCommand в WPF MVVM ViewModels - PullRequest
18 голосов
/ 24 июня 2010

Если вы используете MVVM и используете команды, вы часто будете видеть свойства ICommand в ViewModel, которые поддерживаются частными полями RelayCommand или DelegateCommand, как, например, в этом примере из исходной статьи MVVM на MSDN :

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

Тем не менее, это много беспорядка и делает настройку новых команд довольно утомительной (я работаю с некоторыми опытными разработчиками WinForms, которые возражают против такой типизации). Поэтому я хотел упростить это и немного покопался. Я установил точку останова в первой строке блока get {} и увидел, что он получил удар только при первой загрузке моего приложения - позже я могу запустить столько команд, сколько захочу, и эта точка останова никогда не попадет - поэтому я хотел упростить это, чтобы убрать помехи из моих ViewModels и заметил, что следующий код работает так же:

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

Однако я не знаю достаточно о C # или сборщике мусора, чтобы знать, может ли это вызвать проблемы, такие как генерация избыточного мусора в некоторых случаях. Это создаст проблемы?

Ответы [ 6 ]

17 голосов
/ 25 июня 2010

Это точно так же, как если бы вы предложили, скажем, целочисленное свойство, которое вычисляет некоторое постоянное значение.Вы можете либо рассчитать его для каждого вызова метода get, либо создать его при первом вызове, а затем кэшировать его, чтобы вернуть кэшированное значение для последующих вызовов.Таким образом, если метод get вызывается не более одного раза, это вообще не имеет значения, если он вызывается часто, вы потеряете некоторую (не очень) производительность, но у вас не будет реальных проблем.лично мне нравится сокращать MSDN-путь так:

RelayCommand _saveCommand;
public ICommand SaveCommand
{
  get
  {
    return _saveCommand ?? (_saveCommand = new RelayCommand(param => this.Save(),
                                                            param => this.CanSave ));
  }
}
8 голосов
/ 24 июня 2010

Я обнаружил, что вам нужен оригинальный путь от MSDN, если у вас есть несколько элементов управления, которые вызывают одни и те же команды, в противном случае каждый элемент управления будет иметь свою собственную команду RelayCommand. Я не осознавал этого, потому что мое приложение имеет только один элемент управления на команду.

Таким образом, чтобы упростить код в ViewModels, я создам класс оболочки команд, который хранит (и лениво создает экземпляры) все команды RelayCommands и выбросит его в мой класс 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;
    }
}

Этот класс также лениво создается классом ViewModelBase, поэтому ViewModels, не имеющие каких-либо команд, позволит избежать дополнительных выделений.

7 голосов
/ 25 июня 2010

Одна вещь, которую я делаю, это позволяет Visual Studio печатать за меня.Я только что создал фрагмент кода, который позволяет мне создавать RelayCommand, набрав

rc Tab Сохранить Enter

rcэто вкладка ярлыка фрагмента кода загружает текст, который вы вводите, что вы хотите, и он создает все другие формулировки.

Как только вы посмотрите на один фрагмент кода и создадите свой собственный, вы никогда не вернетесь:)

Для получения дополнительной информации о создании фрагментов кода: http://msdn.microsoft.com/en-us/library/ms165394.aspx

1 голос
/ 04 апреля 2012

Когда вы выставляете свойство ICommand для вашей модели представления, и у него нет поля поддержки, это нормально, если вы только привязываетесь к этому полю один раз.

Метод GetCommand CommandWrapper вернет команду, если она уже создана.

1 голос
/ 26 июня 2010

Почему бы тебе не написать просто:

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

public ICommand SaveCommand { get { return _saveCommand; } }
0 голосов
/ 29 марта 2011

Когда вы выставляете свойство ICommand на вашей модели представления, и у него нет вспомогательного поля, это нормально, если вы только связываетесь с этим полем один раз. По сути, когда ваша форма загружается и выполняет начальные привязки, это единственный раз, когда она получит доступ к свойству get вашей команды.

Существует множество случаев, когда вы связываете команду только один раз.

Если вы привязали одну и ту же команду к нескольким элементам управления, вам понадобится поле поддержки.

...