This article is a reprint
Original Author: RyzenAdorer
Original Title: .NET Core 3 WPF MVVM Framework Prism Series – Commands
Original Link: https://www.cnblogs.com/ryzen/p/12143825.html
This article will introduce how to use Prism's commands in the MVVM framework within a .NET Core 3 environment.
1. Creating DelegateCommand
In the previous article .NET Core 3 WPF MVVM Framework Prism Series – Data Binding, we learned about Prism's data binding approach. Following the standard pattern, we create Views and ViewModels folders, place MainWindow in the Views folder, and create a MainWindowViewModel class under the ViewModels folder, as shown below:

XAML code:
<Window
x:Class="CommandSample.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CommandSample"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="450"
prism:ViewModelLocator.AutoWireViewModel="True"
>
<StackPanel>
<TextBox Margin="10" Text="{Binding CurrentTime}" FontSize="32" />
<button
x:Name="mybtn"
FontSize="30"
Content="Click Me"
Margin="10"
Height="60"
Command="{Binding GetCurrentTimeCommand}"
/>
<Viewbox Height="80">
<CheckBox
IsChecked="{Binding IsCanExcute}"
Content="CanExcute"
Margin="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
/>
</Viewbox>
</StackPanel>
</Window>
MainWindowViewModel class code:
using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Windows.Controls;
namespace CommandSample.ViewModels
{
public class MainWindowViewModel: BindableBase
{
private bool _isCanExcute;
public bool IsCanExcute
{
get { return _isCanExcute; }
set
{
SetProperty(ref _isCanExcute, value);
GetCurrentTimeCommand.RaiseCanExecuteChanged();
}
}
private string _currentTime;
public string CurrentTime
{
get { return _currentTime; }
set { SetProperty(ref _currentTime, value); }
}
private DelegateCommand _getCurrentTimeCommand;
public DelegateCommand GetCurrentTimeCommand =>
_getCurrentTimeCommand ?? (_getCurrentTimeCommand = new DelegateCommand(ExecuteGetCurrentTimeCommand, CanExecuteGetCurrentTimeCommand));
void ExecuteGetCurrentTimeCommand()
{
this.CurrentTime = DateTime.Now.ToString();
}
bool CanExecuteGetCurrentTimeCommand()
{
return IsCanExcute;
}
}
}
Running effect:

In the code, we import using Prism.Mvvm to inherit BindableBase so we can use the property change notification method SetProperty, which we already learned in the previous article. Then, using Prism.Commands brings in the DelegateCommand type we are using. As we know, the ICommand interface has three members: the event CanExecuteChanged, a method CanExecute that returns bool and takes an object parameter, and a method Execute that returns void and takes an object parameter. Obviously, the GetCurrentTimeCommand we implemented is a command without parameters.
Another notable point is that we bind the CheckBox's IsChecked to a bool property IsCanExcute, and in the CanExecute method we return IsCanExcute. We all know that CanExecute controls whether the Execute method can run and also controls the Button's IsEnable state. In the set method of IsCanExcute, we added the line:
GetCurrentTimeCommand.RaiseCanExecuteChanged();
Looking at Prism's source code, we can see that RaiseCanExecuteChanged internally invokes the CanExecuteChanged event to trigger the CanExecute method.
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged();
}
protected virtual void OnCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChanged;
if (handler != null)
{
if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current)
{
_synchronizationContext.Post(delegate
{
handler(this, EventArgs.Empty);
}, null);
}
else
{
handler(this, EventArgs.Empty);
}
}
}
Prism also provides a more concise and elegant way to write this:
private bool _isCanExcute;
public bool IsCanExcute
{
get { return _isCanExcute; }
set { SetProperty(ref _isCanExcute, value);}
}
private DelegateCommand _getCurrentTimeCommand;
public DelegateCommand GetCurrentTimeCommand =>
_getCurrentTimeCommand ?? (_getCurrentTimeCommand = new DelegateCommand(ExecuteGetCurrentTimeCommand).ObservesCanExecute(()=> IsCanExcute));
void ExecuteGetCurrentTimeCommand()
{
this.CurrentTime = DateTime.Now.ToString();
}
Here, the ObservesCanExecute method is used, which internally also calls RaiseCanExecuteChanged.
From the code above, we can identify two questions:
- How to create a
DelegateCommandwith parameters? - If a control doesn't have a
Commanddependency property, how can we convert its event into a command?
2. Creating a Parameterized DelegateCommand
Before creating a parameterized command, let's look at the inheritance chain and public methods of DelegateCommand. For detailed implementation, you can check the source code.

It's clear: we previously created a non-generic DelegateCommand. When we create a generic version DelegateCommand<T>, T is the type of the command parameter. So now we can pass the button that triggers the command itself as the command parameter.
XAML code:
<button
x:Name="mybtn"
FontSize="30"
Content="Click Me"
Margin="10"
Height="60"
Command="{Binding GetCurrentTimeCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}"
/>
GetCurrentTimeCommand command code changed to:
private DelegateCommand<object> _getCurrentTimeCommand;
public DelegateCommand<object> GetCurrentTimeCommand =>
_getCurrentTimeCommand ?? (_getCurrentTimeCommand = new DelegateCommand<object>(ExecuteGetCurrentTimeCommand).ObservesCanExecute(()=> IsCanExcute));
void ExecuteGetCurrentTimeCommand(object parameter)
{
this.CurrentTime =((Button)parameter)?.Name+ DateTime.Now.ToString();
}
Let's see the effect:

3. Converting Events to Commands
Most controls that have a Command dependency property implement the ICommandSource interface. ICommandSource has three members: an ICommand property Command, an object property CommandParameter, and an IInputElement property CommandTarget. The two base classes that implement ICommandSource are ButtonBase and MenuItem. Therefore, controls like Button, CheckBox, RadioButton (inheriting from ButtonBase) and MenuItem have the Command dependency property. But common controls like TextBox do not.
Suppose we have a requirement: on the existing interface, add a second TextBox. When its text changes, we want to merge the button's name and the second text box's text string and update it to the first text box. Our first thought would be to use the TextChanged event of the TextBox. How do we convert TextChanged into a command?
First, add the namespace in XAML:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
This assembly System.Windows.Interactivity.dll is part of the Expression Blend SDK, and Prism's package includes it, so we can directly use it. Then we add the second TextBox code:
<TextBox
Margin="10"
FontSize="32"
Text="{Binding Foo,UpdateSourceTrigger=PropertyChanged}"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction
Command="{Binding TextChangedCommand}"
CommandParameter="{Binding ElementName=mybtn}"
/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
Add code in MainWindowViewModel:
private string _foo;
public string Foo
{
get { return _foo; }
set { SetProperty(ref _foo, value); }
}
private DelegateCommand<object> _textChangedCommand;
public DelegateCommand<object> TextChangedCommand =>
_textChangedCommand ?? (_textChangedCommand = new DelegateCommand<object>(ExecuteTextChangedCommand));
void ExecuteTextChangedCommand(object parameter)
{
this.CurrentTime = Foo + ((Button)parameter)?.Name;
}
Effect:

In the XAML above, we added a Blend EventTrigger listener for the TextBox's TextChanged event. Each time the event fires, InvokeCommandAction calls the TextChangedCommand.
3.1. Passing EventArgs Parameters to the Command
We know that the TextChanged event has a RoutedEventArgs parameter TextChangedEventArgs. If we want to access properties of TextChangedEventArgs or RoutedEventArgs, the standard InvokeCommandAction from System.Windows.Interactivity cannot do it directly. Here we use Prism's own InvokeCommandAction and its TriggerParameterPath property. Suppose we want to display in the first TextBox the string entered in the second TextBox plus the name of the control that triggered the event. We can use the Source property from the parent RoutedEventArgs, which refers to the control that raised the event (the second TextBox).
Modified XAML:
<TextBox
x:Name="myTextBox"
Margin="10"
FontSize="32"
Text="{Binding Foo,UpdateSourceTrigger=PropertyChanged}"
TextChanged="TextBox_TextChanged"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<prism:InvokeCommandAction
Command="{Binding TextChangedCommand}"
TriggerParameterPath="Source"
/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
Modified MainWindowViewModel:
void ExecuteTextChangedCommand(object parameter)
{
this.CurrentTime = Foo + ((TextBox)parameter)?.Name;
}
Result:

An interesting observation: if you remove TriggerParameterPath from the above XAML, you will actually receive the TextChangedEventArgs itself.
4. Implementing Task-Based Commands
First, add a new button on the interface to bind a new task-based command. We want to click this button, and after 5 seconds, the first TextBox displays "Hello Prism!" without blocking the UI.
Add the new button in XAML:
<button
x:Name="mybtn1"
FontSize="30"
Content="Click Me 1"
Margin="10"
Height="60"
Command="{Binding AsyncCommand}"
/>
Add code in MainWindowViewModel:
private DelegateCommand _asyncCommand;
public DelegateCommand AsyncCommand =>
_asyncCommand ?? (_asyncCommand = new DelegateCommand(ExecuteAsyncCommand));
async void ExecuteAsyncCommand()
{
await ExampleMethodAsync();
}
async Task ExampleMethodAsync()
{
await Task.Run(()=>
{
Thread.Sleep(5000);
this.CurrentTime = "Hello Prism!";
} );
}
A more concise version:
private DelegateCommand _asyncCommand;
public DelegateCommand AsyncCommand =>
_asyncCommand ?? (_asyncCommand = new DelegateCommand( async()=>await ExecuteAsyncCommand()));
Task ExecuteAsyncCommand()
{
return Task.Run(() =>
{
Thread.Sleep(5000);
this.CurrentTime = "Hello Prism!";
});
}
Check the effect:

5. Creating Composite Commands
Prism provides the CompositeCommand class to support composite commands. What is a composite command? Consider a scenario: a main interface has multiple child forms, each with its own business logic. Let's modify the previous example to have three child forms: one displays the current year, one displays month and day, and one displays hours, minutes, and seconds. We want to have a button on the main form that, when clicked, makes all three display simultaneously. This creates a dependency: the main form button depends on the three child form buttons, but the child buttons do not depend on the main button.
The process for creating and using a standard Prism composite command:
- Create a global composite command
- Register it as a singleton via the IoC container
- Register child commands with the composite command
- Bind the composite command
5.1. Creating a Global Composite Command
First, create a class library project and add an ApplicationCommands class as a global command class:
public interface IApplicationCommands
{
CompositeCommand GetCurrentAllTimeCommand { get; }
}
public class ApplicationCommands : IApplicationCommands
{
private CompositeCommand _getCurrentAllTimeCommand = new CompositeCommand();
public CompositeCommand GetCurrentAllTimeCommand
{
get { return _getCurrentAllTimeCommand; }
}
}
We created the IApplicationCommands interface and implemented it with ApplicationCommands to register it as a global singleton via the IoC container in the next step.
5.2. Registering as Singleton via IoC Container
Create a new project as the main form to display child forms and use the composite command. Key code:
App.cs:
using Prism.Unity;
using Prism.Ioc;
using System.Windows;
using CompositeCommandsSample.Views;
using Prism.Modularity;
using CompositeCommandsCore;
namespace CompositeCommandsSample
{
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
// Register IApplicationCommands as singleton via IoC container
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<IApplicationCommands, ApplicationCommands>();
}
// Register child form modules
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<CommandSample.CommandSampleMoudle>();
}
}
}
5.3. Registering Child Commands with the Composite Command
In the previous CommandSample solution, under the Views folder, add two new UserControls to display month-day and hour-minute-second respectively. Under the ViewModels folder, add ViewModels for the two UserControls, and also change the original MainWindow to a UserControl. The structure roughly looks like this:

Key code:
GetHourTabViewModel.cs:
IApplicationCommands _applicationCommands;
public GetHourTabViewModel(IApplicationCommands applicationCommands)
{
_applicationCommands = applicationCommands;
// Register child command GetHourCommand with the composite command GetCurrentAllTimeCommand
_applicationCommands.GetCurrentAllTimeCommand.RegisterCommand(GetHourCommand);
}
private DelegateCommand _getHourCommand;
public DelegateCommand GetHourCommand =>
_getHourCommand ?? (_getHourCommand = new DelegateCommand(ExecuteGetHourCommand).ObservesCanExecute(() => IsCanExcute));
void ExecuteGetHourCommand()
{
this.CurrentHour = DateTime.Now.ToString("HH:mm:ss");
}
GetMonthDayTabViewModel.cs:
IApplicationCommands _applicationCommands;
public GetMonthDayTabViewModel(IApplicationCommands applicationCommands)
{
_applicationCommands = applicationCommands;
// Register child command GetMonthCommand with the composite command GetCurrentAllTimeCommand
_applicationCommands.GetCurrentAllTimeCommand.RegisterCommand(GetMonthCommand);
}
private DelegateCommand _getMonthCommand;
public DelegateCommand GetMonthCommand =>
_getMonthCommand ?? (_getMonthCommand = new DelegateCommand(ExecuteCommandName).ObservesCanExecute(()=>IsCanExcute));
void ExecuteCommandName()
{
this.CurrentMonthDay = DateTime.Now.ToString("MM:dd");
}
MainWindowViewModel.cs:
IApplicationCommands _applicationCommands;
public MainWindowViewModel(IApplicationCommands applicationCommands)
{
_applicationCommands = applicationCommands;
// Register child command GetYearCommand with the composite command GetCurrentAllTimeCommand
_applicationCommands.GetCurrentAllTimeCommand.RegisterCommand(GetYearCommand);
}
private DelegateCommand _getYearCommand;
public DelegateCommand GetYearCommand =>
_getYearCommand ?? (_getYearCommand = new DelegateCommand(ExecuteGetYearCommand).ObservesCanExecute(()=> IsCanExcute));
void ExecuteGetYearCommand()
{
this.CurrentTime =DateTime.Now.ToString("yyyy");
}
CommandSampleMoudle.cs:
using CommandSample.ViewModels;
using CommandSample.Views;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;
namespace CommandSample
{
public class CommandSampleMoudle : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
IRegion region= regionManager.Regions["ContentRegion"];
var mainWindow = containerProvider.Resolve<MainWindow>();
(mainWindow.DataContext as MainWindowViewModel).Title = "GetYearTab";
region.Add(mainWindow);
var getMonthTab = containerProvider.Resolve<GetMonthDayTab>();
(getMonthTab.DataContext as GetMonthDayTabViewModel).Title = "GetMonthDayTab";
region.Add(getMonthTab);
var getHourTab = containerProvider.Resolve<GetHourTab>();
(getHourTab.DataContext as GetHourTabViewModel).Title = "GetHourTab";
region.Add(getHourTab);
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
}
5.4. Binding the Composite Command
Main form XAML code:
<Window
x:Class="CompositeCommandsSample.Views.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:prism="http://prismlibrary.com/"
xmlns:local="clr-namespace:CompositeCommandsSample"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="MainWindow"
Height="650"
Width="800"
>
<Window.Resources>
<style TargetType="TabItem">
<Setter Property="Header" Value="{Binding DataContext.Title}"/>
</style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<button
Content="GetCurrentTime"
FontSize="30"
Margin="10"
Command="{Binding ApplicationCommands.GetCurrentAllTimeCommand}"
/>
<TabControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion" />
</Grid>
</Window>
MainWindowViewModel.cs:
using CompositeCommandsCore;
using Prism.Mvvm;
namespace CompositeCommandsSample.ViewModels
{
public class MainWindowViewModel:BindableBase
{
private IApplicationCommands _applicationCommands;
public IApplicationCommands ApplicationCommands
{
get { return _applicationCommands; }
set { SetProperty(ref _applicationCommands, value); }
}
public MainWindowViewModel(IApplicationCommands applicationCommands)
{
this.ApplicationCommands = applicationCommands;
}
}
}
Finally, check the actual effect:

To conclude, the composite command validates the relationship we mentioned earlier: the composite command depends on the child commands, but child commands do not depend on the composite command. Therefore, the composite command can only be executed when all three child commands are executable. The modular knowledge used here will be discussed in the next article.