Commands

  • 11 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, you need to 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

Methods whose names end with "Command" raise an exception - rename such methods or decorate them with the Command attribute. See this help topic for more details: 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 whose name starts 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;
    }
}

If a CanExecute condition returns false, the Framework disables the UI element linked to this command. 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;
}

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() {
    //. . .
}

Commands with Parameters

The DevExpress MVVM Framework accepts public void methods with zero or one parameter as 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);

Asynchronous Commands

If you need to perform a delayed or continuous operation, use asynchronous commands. To create an asynchronous command, declare a public async method of the System.Threading.Tasks.Task type. 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. There are three trigger types, depending on the condition that sets off the trigger.

  • "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"));

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.

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);
    }
}