MVVM Commands
- 12 minutes to read
In a standard WinForms application, actions are often performed within event handlers. For instance, to refresh data when a user clicks a button, handle the ButtonClick
event and retrieve data source records.
This standard technique does not fit the MVVM concept of separated layers. The code that pulls data from a data source should belong to a ViewModel layer, not a View. In MVVM, these tasks are accomplished with commands - ViewModel objects that encapsulate actions. Bind a UI element to this object to achieve the required layer separation: the View code now has only the binding code, while all business logic remains in the ViewModel and can be safely changed.
The DevExpress MVVM Framework treats all public void methods as bindable commands. The code below illustrates how to declare a command that uses a Service to show a message box. You can view the full demo in the DevExpress Demo Center via the following link: Simple Command.
//POCO ViewModel
public class ViewModelWithSimpleCommand {
//command
public void DoSomething() {
var msgBoxService = this.GetService<IMessageBoxService>();
msgBoxService.ShowMessage("Hello!");
}
}
Important
~Command
methods raise an exception. Rename these methods or decorate them with the Command
attribute. See the following help topic for more information: Conventions and Attributes.
To link a button to this command, use the BindCommand
or WithCommand
methods.
//View code
mvvmContext.ViewModelType = typeof(ViewModelWithSimpleCommand);
var fluent = mvvmContext.OfType<ViewModelWithSimpleCommand>();
fluent.BindCommand(commandButton, x => x.DoSomething);
//or
fluent.WithCommand(x => x.DoSomething)
.Bind(commandButton1);
The WithCommand
method allows you to bind multiple buttons at the same time.
Run the demo: Bind to Multiple UI Elements.
//View
var fluent = mvvmContext.OfType<ViewModelWithSimpleCommand>();
fluent.WithCommand(x => x.DoSomething)
.Bind(commandButton1)
.Bind(commandButton2);
CanExecute Conditions
To specify a condition that determines whether a command should or should not run, declare a Boolean method. The method name should start with “Can”, followed by the related command name. These methods are called CanExecute conditions.
//ViewModel
public class ViewModelWithConditionalCommand {
//Command
public void DoSomething() {
var msgBoxService = this.GetService<IMessageBoxService>();
msgBoxService.ShowMessage("Hello!");
}
//CanExecute condition
public bool CanDoSomething() {
return (2 + 2) == 4;
}
}
You can also ignore CanExecute name requirements and use the Command
attribute to manually assign a command condition.
[Command(CanExecuteMethodName = "DoSomethingCriteria")]
public void DoSomething(int p) {
//command
}
If a CanExecute condition returns false, the Framework changes the state of the UI element linked to this command (disables, unchecks, or hides this element). The code sample above is from the following demo: Command with CanExecute condition. Run this demo and change the condition so that it always returns false. The “Execute Command” button becomes disabled because its related command can no longer run.
The Framework checks the CanExecute condition when:
- a UI command binding initializes;
- the
RaiseCanExecuteChanged
method is called. In the sample below, the return value of theCanDoSomething
condition is re-checked every time theSelectedEntity
property changes.
//Bindable Property
public virtual MyEntity SelectedEntity{ get; set; }
//OnChanged callback for the bindable property
protected void OnSelectedEntityChanged(){
this.RaiseCanExecuteChanged(x=>x.DoSomething());
}
//Command
public void DoSomething() {
//. . .
}
//CanExecute condition
public bool CanDoSomething() {
//. . .
}
Commands with Parameters
The DevExpress MVVM Framework accepts public void methods one parameter as parameterized commands. You can use this parameter to pass data between View and ViewModel.
Run the demo: Parameterized Command.
//ViewModel
public class ViewModelWithParametrizedCommand {
public void DoSomething(object p) {
var msgBoxService = this.GetService<IMessageBoxService>();
msgBoxService.ShowMessage(string.Format("The parameter is {0}.", p));
}
}
//View
mvvmContext.ViewModelType = typeof(ViewModelWithParametrizedCommand);
var fluent = mvvmContext.OfType<ViewModelWithParametrizedCommand>();
object parameter = 5;
fluent.BindCommand(commandButton, x => x.DoSomething, x => parameter);
You can also add a parameter to the CanExecute condition.
Run the demo: Parameterized Command with CanExecute Condition.
//ViewModel
public class ViewModelWithParametrizedConditionalCommand {
public void DoSomething(int p) {
var msgBoxService = this.GetService<IMessageBoxService>();
msgBoxService.ShowMessage(string.Format(
"The parameter is {0}.", p));
}
public bool CanDoSomething(int p) {
return (2 + 2) == p;
}
}
//View
mvvmContext.ViewModelType = typeof(ViewModelWithParametrizedConditionalCommand);
var fluent = mvvmContext.OfType<ViewModelWithParametrizedConditionalCommand>();
int parameter = 4;
fluent.BindCommand(commandButton, x => x.DoSomething, x => parameter);
Multiple Parameters
Use an object or a tuple data structure to pass multiple parameters. See the following example for an example.
class Parameters{
public int Parameter1 { get; set }
public string Parameter2 { get; set }
...
}
// ...
mvvmContext.OfType<MouseDownAwareViewModel>()
.WithEvent<MouseEventArgs>(label, "MouseDown")
.EventToCommand(x => x.ReportLocation, args => new Parameters{ Parameter1 = 1, Parameter2 = "2" });
Asynchronous Commands
To delay or execute a continuous operation, use asynchronous commands. To create an asynchronous command, declare a public method of the System.Threading.Tasks.Task
type (you can use async
/await
syntax). The code that binds a UI element to the command remains the same. The Framework disables this element while the command is running.
Run the demo: Async Command.
//ViewModel
public class ViewModelWithAsyncCommand {
public async Task DoSomethingAsync() {
// do some work here
await Task.Delay(1000);
}
}
//View
mvvmContext.ViewModelType = typeof(ViewModelWithAsyncCommand);
var fluent = mvvmContext.OfType<ViewModelWithAsyncCommand>();
fluent.BindCommand(commandButton, x => x.DoSomethingAsync);
Tasks support cancellation tokens that allow you to check the IsCancellationRequested
property and abort the task when this property returns true. If you add this code to your async command, use the BindCancelCommand
method to create a UI element that stops an ongoing async command. The DevExpress MVVM Framework locks this cancel button, and enables it only when a related async command is running.
Run the demo: Async Command with Cancellation.
//ViewModel
public class ViewModelWithAsyncCommandAndCancellation {
public async Task DoSomethingAsynchronously() {
var dispatcher = this.GetService<IDispatcherService>();
var asyncCommand = this.GetAsyncCommand(x => x.DoSomethingAsynchronously());
for(int i = 0; i <= 100; i++) {
if(asyncCommand.IsCancellationRequested)
break;
// do some work here
await Task.Delay(25);
await UpdateProgressOnUIThread(dispatcher, i);
}
await UpdateProgressOnUIThread(dispatcher, 0);
}
public int Progress {
get;
private set;
}
//update the "Progress" property bound to the progress bar within a View
async Task UpdateProgressOnUIThread(IDispatcherService dispatcher, int progress) {
await dispatcher.BeginInvoke(() => {
Progress = progress;
this.RaisePropertyChanged(x => x.Progress);
});
}
}
//View
mvvmContext.ViewModelType = typeof(ViewModelWithAsyncCommandAndCancellation);
var fluent = mvvmContext.OfType<ViewModelWithAsyncCommandAndCancellation>();
fluent.BindCommand(commandButton, x => x.DoSomethingAsynchronously);
fluent.BindCancelCommand(cancelButton, x => x.DoSomethingAsynchronously);
fluent.SetBinding(progressBar, p => p.EditValue, x => x.Progress);
The WithCommand
Fluent API method also supports cancelable asynchronous commands.
mvvmContext.ViewModelType = typeof(ViewModelWithAsyncCommandAndCancellation);
// Initialize the Fluent API
var fluent = mvvmContext.OfType<ViewModelWithAsyncCommandAndCancellation>();
// Binding for buttons
fluent.WithCommand(x => x.DoSomethingAsynchronously)
.Bind(commandButton)
.BindCancel(cancelButton);
Command Triggers
Triggers allow you to perform additional View actions associated with a command. Trigger types depend on the condition that sets off the trigger. These include:
- “Before” trigger - allows you to perform actions before a target command executes.
mvvmContext.ViewModelType = typeof(ViewModelWithSimpleCommand); var fluent = mvvmContext.OfType<ViewModelWithSimpleCommand>(); fluent.BindCommand(commandButton, x => x.DoSomething); fluent.WithCommand(x => x.DoSomething) .Before(() => XtraMessageBox.Show("The target command is about to be executed"));
- “After” trigger - allows you to perform actions after a target command finishes.
mvvmContext.ViewModelType = typeof(ViewModelWithSimpleCommand); var fluent = mvvmContext.OfType<ViewModelWithSimpleCommand>(); fluent.BindCommand(commandButton, x => x.DoSomething); fluent.WithCommand(x => x.DoSomething) .After(() => XtraMessageBox.Show("The target command has been executed"));
- “CanExecute” condition trigger - allows you to perform actions when a CanExecute condition for the target command changes.
var fluent = mvvmContext.OfType<ViewModelWithSimpleCommandAndCanExecute>(); fluent.BindCommand(commandButton, x => x.DoSomething); // When the CanExecute condition changes, the message shows up fluent.WithCommand(x => x.DoSomething) .OnCanExecuteChanged(() => XtraMessageBox.Show("The CanExecute condition has changed"));
Triggers are executed for every UI element bound to the target command. The following code snippet displays a message box when any of the buttons is clicked:
mvvmContext1.OfType<BulkEditViewModel>()
.WithCommand(vm => vm.RemoveFields())
.Bind(button1)
.Bind(button2)
.After(() => MessageBox.Show("Test"));
Non-POCO Commands
The POCO class commands described above allow you to use the most straightforward and fail-free syntax. The DevExpress MVVM Framework also supports other command types - to ensure hassle-free migration for legacy projects.
DevExpress DelegateCommand Objects
Delegate commands are an implementation of the System.Windows.Input.ICommand interface.
Run the demo: Simple Commands
DelegateCommand command = new DelegateCommand(() => {
XtraMessageBox.Show("Hello!");
});
commandButton.BindCommand(command);
Run the demo: Commands with CanExecute Conditions
Func<bool> canExecute = () => (2 + 2 == 4);
DelegateCommand command = new DelegateCommand(() => {
XtraMessageBox.Show("Hello!");
}, canExecute);
commandButton.BindCommand(command);
Run the demo: Commands with Parameters
DelegateCommand<object> command = new DelegateCommand<object>((v) => {
XtraMessageBox.Show(string.Format("The parameter is {0}.", v));
});
object parameter = 5;
commandButton.BindCommand(command, () => parameter);
Run the demo: Commands with Parameterized CanExecute Conditions
Func<int, bool> canExecute = (p) => (2 + 2 == p);
DelegateCommand<int> command = new DelegateCommand<int>((v) => {
XtraMessageBox.Show(string.Format("The parameter is {0}.", v));
}, canExecute);
int parameter = 4;
commandButton.BindCommand(command, () => parameter);
Custom Command Classes
These are objects of any custom type that have at least one Execute method. If needed, you can add the CanExecute
method and CanExecuteChanged
event.
Run the demo: Simple Commands
CommandObject command = new CommandObject();
commandButton.BindCommand(command);
public class CommandObject {
public void Execute(object parameter) {
XtraMessageBox.Show("Hello!");
}
}
Run the demo: Commands with Parameters
CommandObjectWithParameter command = new CommandObjectWithParameter();
int parameter = 4;
commandButton.BindCommand(command, () => parameter);
public class CommandObjectWithParameter {
public void Execute(object parameter) {
XtraMessageBox.Show(string.Format(
"The parameter is {0}.", parameter));
}
public bool CanExecute(object parameter) {
return object.Equals(2 + 2, parameter);
}
}
Command Binding for Calling Methods inside the UI
If you have a command defined in your ViewModel, use the following command-related properties and events of button-based UI elements for binding:
Command
CommandParameter
CommandCanExecuteChanged
CommandChanged
These APIs are available in the following classes: BaseButton, ContextButtonBase, DXMenuItem, JumpListItemTask, AdornerElement, BarButtonItem, BarCheckItem, BarToggleSwitchItem, WindowsUIButton, AccordionControlElement, NavButton, NavigationBarItem, TileNavElement, BackstageViewButtonItem, GalleryItem, RecentItemBase, NavigatorButtonBase, TileItem, EditorButton, NavBarItem.
Example
// Creates a simple command that displays a message.
DelegateCommand command = new DelegateCommand(() => {
XtraMessageBox.Show("Hello World!");
});
// Binds the Command property of a button.
commandButton.Command = command;
Read the following topics for additional information: