windowsforms-113965-cross-platform-app-development-winforms-mvvm-concepts-commands.md
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!");
}
}
'POCO ViewModel
Public Class ViewModelWithSimpleCommand
'command
Public Sub DoSomething()
Dim msgBoxService = Me.GetService(Of IMessageBoxService)()
msgBoxService.ShowMessage("Hello!")
End Sub
End Class
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);
'View code
mvvmContext.ViewModelType = GetType(ViewModelWithSimpleCommand)
Dim fluent = mvvmContext.OfType(Of ViewModelWithSimpleCommand)()
fluent.BindCommand(commandButton, Sub(x) x.DoSomething)
'or
fluent.WithCommand(Sub(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);
'View
Dim fluent = mvvmContext.OfType(Of ViewModelWithSimpleCommand)()
fluent.WithCommand(Sub(x) x.DoSomething)
.Bind(commandButton1)
.Bind(commandButton2)
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;
}
}
'ViewModel
Public Class ViewModelWithConditionalCommand
'Command
Public Sub DoSomething()
Dim msgBoxService = Me.GetService(Of IMessageBoxService)()
msgBoxService.ShowMessage("Hello!")
End Sub
'CanExecute condition
Public Function CanDoSomething() As Boolean
Return (2 + 2) = 4
End Function
End Class
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
}
<Command(CanExecuteMethodName := "DoSomethingCriteria")>
Public Sub DoSomething(ByVal p As Integer)
'command
End Sub
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.
//ViewModel
public bool CanDoSomething() {
//always "false"
return (2 + 2) == 5;
}
'ViewModel
Public Function CanDoSomething() As Boolean
'always "False"
Return (2 + 2) = 5
End Function
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 the CanDoSomething condition is re-checked every time the SelectedEntity 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() {
//. . .
}
'Bindable Property
Public Overridable Property SelectedEntity() As MyEntity
'OnChanged callback for the bindable property
Protected Sub OnSelectedEntityChanged()
Me.RaiseCanExecuteChanged(Function(x) x.DoSomething())
End Sub
'Command
Public Sub DoSomething()
'. . .
End Sub
'CanExecute condition
Public Function CanDoSomething() As Boolean
'. . .
End Function
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);
'ViewModel
Public Class ViewModelWithParametrizedCommand
Public Sub DoSomething(ByVal p As Object)
Dim msgBoxService = Me.GetService(Of IMessageBoxService)()
msgBoxService.ShowMessage(String.Format("The parameter is {0}.", p))
End Sub
End Class
'View
mvvmContext.ViewModelType = GetType(ViewModelWithParametrizedCommand)
Dim fluent = mvvmContext.OfType(Of ViewModelWithParametrizedCommand)()
Dim parameter As Object = 5
fluent.BindCommand(commandButton, Sub(x) x.DoSomething(Nothing), Function(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);
'ViewModel
Public Class ViewModelWithParametrizedConditionalCommand
Public Sub DoSomething(ByVal p As Integer)
Dim msgBoxService = Me.GetService(Of IMessageBoxService)()
msgBoxService.ShowMessage(String.Format("The parameter is {0}.", p))
End Sub
Public Function CanDoSomething(ByVal p As Integer) As Boolean
Return (2 + 2) = p
End Function
End Class
'View
mvvmContext.ViewModelType = GetType(ViewModelWithParametrizedConditionalCommand)
Dim fluent = mvvmContext.OfType(Of ViewModelWithParametrizedConditionalCommand)()
Dim parameter As Integer = 4
fluent.BindCommand(commandButton, Sub(x) x.DoSomething(Nothing), Function(x) parameter)
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" });
Friend Class Parameters
Public Property Parameter1() As Integer
Get
Set(ByVal value As Integer)
End Set
End Get
public String Parameter2
Get
Set(ByVal value As Integer)
End Set
End Get
...
' ...
mvvmContext.OfType(Of MouseDownAwareViewModel)().WithEvent(Of MouseEventArgs)(label, "MouseDown").EventToCommand(Function(x) x.ReportLocation, Function(args) New Parameters With {.Parameter1 = 1, .Parameter2 = "2"})
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);
'ViewModel
Public Class ViewModelWithAsyncCommand
Public Async Sub DoSomethingAsync() As Task
' do some work here
Await Task.Delay(1000)
End Sub
End Class
'View
mvvmContext.ViewModelType = GetType(ViewModelWithAsyncCommand)
Dim fluent = mvvmContext.OfType(Of ViewModelWithAsyncCommand)()
fluent.BindCommand(commandButton, Sub(x) x.DoSomethingAsync(Nothing))
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);
'ViewModel
Public Class ViewModelWithAsyncCommandAndCancellation
Public Async Sub DoSomethingAsynchronously() As Task
Dim dispatcher = Me.GetService(Of IDispatcherService)()
Dim asyncCommand = Me.GetAsyncCommand(Sub(x) x.DoSomethingAsynchronously())
For i As Integer = 0 To 100
If asyncCommand.IsCancellationRequested Then
Exit For
End If
' do some work here
Await Task.Delay(25)
Await UpdateProgressOnUIThread(dispatcher, i)
Next i
Await UpdateProgressOnUIThread(dispatcher, 0)
End Sub
Private privateProgress As Integer
Public Property Progress() As Integer
Get
Return privateProgress
End Get
Private Set(ByVal value As Integer)
privateProgress = value
End Set
End Property
'update the "Progress" property bound to the progress bar within a View
Private Async Sub UpdateProgressOnUIThread(ByVal dispatcher As IDispatcherService, ByVal progress As Integer) As Task
Await dispatcher.BeginInvoke(Sub()
Me.Progress = progress
Me.RaisePropertyChanged(Sub(x) x.Progress)
End Sub)
End Sub
End Class
'View
mvvmContext.ViewModelType = GetType(ViewModelWithAsyncCommandAndCancellation)
Dim fluent = mvvmContext.OfType(Of ViewModelWithAsyncCommandAndCancellation)()
fluent.BindCommand(commandButton, Sub(x) x.DoSomethingAsynchronously)
fluent.BindCancelCommand(cancelButton, Sub(x) x.DoSomethingAsynchronously)
fluent.SetBinding(progressBar, Sub(p) p.EditValue, Sub(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);
mvvmContext.ViewModelType = GetType(ViewModelWithAsyncCommandAndCancellation)
' Initialize the Fluent API
Dim fluent = mvvmContext.OfType(Of ViewModelWithAsyncCommandAndCancellation)()
' Binding for buttons
fluent.WithCommand(Sub(x) x.DoSomethingAsynchronously).Bind(commandButton).BindCancel(cancelButton)
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:
csharpmvvmContext.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"));vbmvvmContext.ViewModelType = GetType(ViewModelWithSimpleCommand) Dim fluent = mvvmContext.OfType(Of ViewModelWithSimpleCommand)() fluent.BindCommand(commandButton, Function(x) x.DoSomething) fluent.WithCommand(Sub(x) x.DoSomething) .Before(Function() XtraMessageBox.Show("The target command is about to be executed"))
csharpmvvmContext.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"));vbmvvmContext.ViewModelType = GetType(ViewModelWithSimpleCommand) Dim fluent = mvvmContext.OfType(Of ViewModelWithSimpleCommand)() fluent.BindCommand(commandButton, Function(x) x.DoSomething) fluent.WithCommand(Function(x) x.DoSomething).After(Function() XtraMessageBox.Show("The target command has been executed"))
csharpvar 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"));vbDim fluent = mvvmContext.OfType(Of ViewModelWithSimpleCommandAndCanExecute)() fluent.BindCommand(commandButton, Function(x) x.DoSomething) ' When the CanExecute condition changes, the message shows up fluent.WithCommand(Function(x) x.DoSomething) .OnCanExecuteChanged(Function() 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"));
mvvmContext1.OfType(Of BulkEditViewModel)()
.WithCommand(Function(vm) vm.RemoveFields())
.Bind(button1)
.Bind(button2)
.After(Function() MessageBox.Show("Test"))
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.
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);
Dim command As New DelegateCommand(Sub() 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);
Dim canExecute As Func(Of Boolean) = Function() (2 + 2 = 4)
Dim command As New DelegateCommand(Sub() 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);
Dim command As New DelegateCommand(Of Object)(Sub(v) XtraMessageBox.Show(String.Format("The parameter is {0}.", v)))
Dim parameter As Object = 5
commandButton.BindCommand(command, Function() 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);
Dim canExecute As Func(Of Integer, Boolean) = Function(p) (2 + 2 = p)
Dim command As New DelegateCommand(Of Integer)(Sub(v) XtraMessageBox.Show(String.Format("The parameter is {0}.", v)), canExecute)
Dim parameter As Integer = 4
commandButton.BindCommand(command, Function() parameter)
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!");
}
}
Private command As New CommandObject()
commandButton.BindCommand(command)
Public Class CommandObject
Public Sub Execute(ByVal parameter As Object)
XtraMessageBox.Show("Hello!")
End Sub
End Class
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);
}
}
Dim command As New CommandObjectWithParameter()
Dim parameter As Integer = 4
commandButton.BindCommand(command, Sub() parameter)
Public Class CommandObjectWithParameter
Public Sub Execute(ByVal parameter As Object)
XtraMessageBox.Show(String.Format("The parameter is {0}.", parameter))
End Sub
Public Function CanExecute(ByVal parameter As Object) As Boolean
Return Object.Equals(2 + 2, parameter)
End Function
End Class
If you have a command defined in your ViewModel, use the following command-related properties and events of button-based UI elements for binding:
CommandCommandParameterCommandCanExecuteChangedCommandChangedThese 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.
// 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;
' This is a simple command that displays a message.
Dim command As DelegateCommand = New DelegateCommand(Sub()
XtraMessageBox.Show("Hello! I'm running!")
End Sub)
' Binds the Command property of a button.
commandButton.Command = command
Run Demo: Simple Command Run Demo: Parameterized Command
Read the following topics for additional information: