RelayCommand¶
The RelayCommand is a lightweight implementation of the ICommand interface that enables MVVM-style command binding for user interface controls. It supports execution guards (predicates to determine if the command can execute) and provides integrated logging capabilities.
Overview¶
The RelayCommand pattern allows you to bind UI button clicks, menu selections, and other user interactions directly to view model methods without requiring custom code-behind. The command automatically handles thread marshalling to the UI thread and can notify the UI when its executability state changes.
Features¶
- Action-based execution: Execute parameterless actions via
RelayCommand - Typed parameters: Execute actions with typed parameters via
RelayCommand<TParam> - Execution guards: Optional predicates to control when commands can execute
- Can Execute notifications: Notify the UI to re-check command executability via
RaiseCanExecuteChanged() - Integrated logging: Built-in diagnostic logging for command execution
- UI thread dispatch: Commands automatically marshal execution to the UI thread
Types¶
RelayCommand¶
A command that executes an Action without parameters.
var command = new RelayCommand(
execute: () => Console.WriteLine("Executed!"),
canExecute: null, // Always executable
name: "SayHello"
);
RelayCommand\<TParam>¶
A command that accepts a typed parameter.
var command = new RelayCommand<string>(
execute: (name) => Console.WriteLine($"Hello, {name}!"),
canExecute: (name) => !string.IsNullOrEmpty(name),
name: "Greet"
);
Basic Usage¶
Parameterless Commands¶
using TA.Utils.Core.MVVM;
public class MyViewModel : ViewModelBase
{
private string _message = "Click the button";
private RelayCommand _clickCommand;
public string Message
{
get => _message;
set => SetField(ref _message, value);
}
public RelayCommand ClickCommand => _clickCommand ??= new RelayCommand(
execute: OnButtonClicked,
canExecute: CanClickButton,
name: "ClickButton"
);
private void OnButtonClicked()
{
Message = "Button was clicked!";
}
private bool CanClickButton()
{
return !string.IsNullOrEmpty(Message);
}
}
XAML binding:
Parameterized Commands¶
public class MyViewModel : ViewModelBase
{
private RelayCommand<string> _greetCommand;
public RelayCommand<string> GreetCommand => _greetCommand ??= new RelayCommand<string>(
execute: OnGreet,
canExecute: CanGreet,
name: "GreetPerson"
);
private void OnGreet(string name)
{
MessageBox.Show($"Hello, {name}!");
}
private bool CanGreet(string name)
{
return !string.IsNullOrEmpty(name) && name.Length > 2;
}
}
XAML binding with command parameter:
<Button Command="{Binding GreetCommand}"
CommandParameter="{Binding TextBoxName.Text}"
Content="Greet" />
Constructor Parameters¶
RelayCommand¶
public RelayCommand(
Action execute,
Func<bool>? canExecute = null,
string? name = null,
ILog? log = null)
- execute (required): The action to execute
- canExecute (optional): Predicate determining if the command can execute; defaults to always returning
true - name (optional): Display name for diagnostics; defaults to "unnamed"
- log (optional): Logger instance; defaults to a degenerate (no-op) logger
RelayCommand\<TParam>¶
public RelayCommand(
Action<TParam> execute,
Func<TParam, bool>? canExecute = null,
string? name = null,
ILog? log = null)
Methods¶
CanExecute(object? parameter)¶
Determines whether the command can currently execute. Called by the UI framework.
Execute(object? parameter)¶
Executes the command. Called when the user invokes the command (e.g., clicks a button).
RaiseCanExecuteChanged()¶
Notifies the UI that the CanExecute state may have changed. The UI will re-query CanExecute and enable/disable accordingly.
private bool _isProcessing = false;
public MyViewModel()
{
_clickCommand = new RelayCommand(
execute: DoWork,
canExecute: () => !_isProcessing,
name: "DoWorkCommand"
);
}
public async void DoWork()
{
_isProcessing = true;
_clickCommand.RaiseCanExecuteChanged(); // Disable UI control
await Task.Delay(1000); // Simulate work
_isProcessing = false;
_clickCommand.RaiseCanExecuteChanged(); // Enable UI control
}
Events¶
CanExecuteChanged¶
Raised to notify the UI that CanExecute should be re-evaluated.
command.CanExecuteChanged += (sender, e) =>
{
Console.WriteLine("Command executability changed");
};
Logging¶
When a logger is provided, RelayCommand logs:
- Trace level:
CanExecutequeries andExecutecalls - Warn level: Parameter type mismatches
- Error level: Exceptions during execution
var logger = new MyLoggerService();
var command = new RelayCommand(
execute: () => { /* ... */ },
name: "MyCommand",
log: logger
);
Thread Safety¶
- Commands automatically marshal execution to the UI thread via
IUiThreadDispatcher - The
executeandcanExecutecallbacks should not block for long periods - For long-running operations, use
AsyncRelayCommandinstead
Best Practices¶
-
Use lazy initialization with the null-coalescing operator:
-
Name your commands for better diagnostics:
-
Use guard predicates to prevent invalid operations:
-
For async operations, use
AsyncRelayCommandto prevent overlapping executions -
Provide logging in production code for diagnostics:
See Also¶
- AsyncRelayCommand - For commands that execute asynchronously
- ViewModelBase - Base class for view models
- UI Thread Dispatcher - Thread dispatch abstraction