Why Relaycommand

Why RelayCommand

Commands are used to separate the semantics and the object that invokes a command from the logic that executes the command i.e. it separates UI component from the logic that needs to be executed on command invocation. So, that you can test business logic separately using test cases and also your UI code is loosely coupled to business logic.

Now, that being said let's pick your questions one by one:

What job does it do?

I have added the details above. Hope it clears the usage of commands.



Is it usable for all commands in my form?

Some controls exposed Command DependencyProperty like Button, MenuItem etc. which have some default event registered to it. For Button it's Click event. So, if you bind ICommand declared in ViewModel with Button's Command DP, it will be invoked whenever button is clicked on.

For other events, you can bind using Interactivity triggers. Refer to the sample here how to use them to bind to ICommand in ViewModel.



How do I make the button disable when (a) certain text box(es) are not
filled in?

The link you posted doesn't provide complete implementation of RelayCommand. It lacks the overloaded constructor to set CanExecute predicate which plays a key role in enabling/disabling the UI control to which your command is bound to.

Bound TextBoxes with some properties in ViewModel and in CanExecute delegate returns false if any of the bound properties are null or empty which automatically disabled the control to which command is bound to.


Full implementation of 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
}

Why use a RelayCommand or DelegateCommand instead of just implementing ICommand?

If you won't use some base command (of a framework or your own custom command) you'll find yourself writing the same code over and over again. For example: you don't raise CanExecuteChanged event in your own command. The same goes for implementing INotifyPropertyChanged. That's why everyone's using one or another MVVM framework.

RelayCommand best practice

Refer below answers.

  1. What is the best approach to declaring and constructing ICommands?
    Is it one of my approaches or is there an easier way? Ans:You can
    take combined approach. If your execute method is very minimal, you
    can use RelayCommnd otherwise you can implement your own ICommand in
    a separate class. This will improve readability of your viewmodel as
    well as modularity of your code.
  2. How hard is it to maintain the overview with each approach considering there are over 50 ICommands in a single ViewModel.
    Ans: Cover in Ans 1
  3. I'm hoping to release my application on both Windows 7, 8 and 10 in the future. Are there any limitations to RelayCommands I have to take in account if I'm only using .NET4.5?
    Ans: I am not seeing any limitation in Windows 7 or 8 but not sure about Windows 10.
  4. Besides RelayCommands I also found this project: Caliburn-Micro. It allows you to do something like the code below. Does anyone have an idea how good this works performance wise in comparison to RelayCommands? This is just an extra question and not required to be answered to have a post marked as an answer.
    Ans: I am not sure about source code of RelayCommands in Caliburn-Micro. But if it using CommandManager to achieve the CanExecute functionality. Then command manager will get triggered for all the user inputs change and hence that will cause performance issues if have some heavy logic in the CanExecute method. Refer Are there any performance implications with CanExecuteCommand?

Full implementation of Relay Command - can it be applied to all cases?

1) ICommand only has methods that include a parameter. If you don't specify a parameter in XAML, null is used.

https://msdn.microsoft.com/en-us/library/system.windows.input.icommand(v=vs.110).aspx

2) Yes, you can effect CanExecute without a CommandParameter. See below, it uses the viewmodel's string property "MyData" in CanExecute.

MainWindow.xaml

<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication8"
mc:Ignorable="d"
FocusManager.FocusedElement="{Binding ElementName=tb}"
SizeToContent="Height"
Title="MainWindow" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<StackPanel>
<Label Content="MyData (CanExecute returns false if this is whitespace)" />
<TextBox Name="tb" Text="{Binding MyData, UpdateSourceTrigger=PropertyChanged}" Margin="5" />
<Button Content="Without XAML CommandParameter" Margin="5" Command="{Binding Command1}" />
<Button Content="With XAML CommandParameter" Margin="5" Command="{Binding Command1}" CommandParameter="{Binding MyData}" />
</StackPanel>
</Window>

MainWindow.xaml.cs

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;

namespace WpfApplication8
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}

public class MainWindowViewModel : INotifyPropertyChanged
{
private ICommand command1;
public ICommand Command1 { get { return command1; } set { command1 = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Command1))); } }

private string myData;
public string MyData { get { return myData; } set { myData = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MyData))); } }

public event PropertyChangedEventHandler PropertyChanged;

public MainWindowViewModel()
{
Command1 = new RelayCommand<object>(Command1Execute, Command1CanExecute);
}

private bool Command1CanExecute(object obj)
{
// Only allow execute if MyData has data
return !string.IsNullOrWhiteSpace(MyData);
}

private void Command1Execute(object obj)
{
MessageBox.Show($"CommandParameter = '{obj}'");
}
}

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 ? true : _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
}
}

RelayCommand and delegates, trying to understand delegates

Does this mean that there is a delegate created when using ICommand like i shown in the code? Where the encapsulating method is "SearchCommand_DoWork"

You're creating a new object of type RelayCommand. As you can see in the class' constructor, you're passing in an Action object (delegate that returns no value) and a Func object (delegate that returns a value).

For the Action delegate you're passing in an object that encapsulates the void function SearchCommandDoWork, for the Func object you're passing in an lambda function that takes no parameter and always returns true.

The Action delegate encapsulates your SearchCommand_DoWork function (a delegate is basically a type safe function pointer).

Both Action and Func are predefined delegates. You can also define your own delegates, which is what

public delegate int MethodName(string name)

does.



Related Topics



Leave a reply



Submit