Agile UI Development in .NET: UI Commands

In this post, we are going to have a look at UI commands. UI commands are responsible for reacting to user input, for example the send button click in the sample I use throughout this series of agile user interface development in .NET series. For other posts in this series look here: table of contents.

We have seen in the last post that the view binds a command to the send button that it gets from the view-model:

<Button Command="{Binding SendMessageCommand}" IsDefault="True">Send</Button>

The following code shows the complete command to handle the click on the Send button.

/// <summary>
/// Invokes a <see cref="ISendMessageModelCommand"/> to send a message.
/// </summary>
public class SendMessageUiCommand : UiCommand
{
    /// <summary>
    /// The view model associated with the message entry view.
    /// </summary>
    private readonly IMessageEntryViewModel messageEntryViewModel;

    /// <summary>
    /// The command processor.
    /// </summary>
    private readonly IModelCommandProcessor modelCommandProcessor;

    /// <summary>
    /// The command executed when this command is executed.
    /// </summary>
    private readonly ISendMessageModelCommand sendMessageModelCommand;

    /// <summary>
    /// Initializes a new instance of the <see cref="SendMessageUiCommand"/> class.
    /// </summary>
    /// <param name="messageEntryViewModel">The message entry view model.</param>
    /// <param name="modelCommandProcessor">The model command processor.</param>
    /// <param name="sendMessageModelCommand">The send message model command.</param>
    public SendMessageUiCommand(
        IMessageEntryViewModel messageEntryViewModel,
        IModelCommandProcessor modelCommandProcessor,
        ISendMessageModelCommand sendMessageModelCommand)
    {
        Contract.Requires<ArgumentNullException>(messageEntryViewModel != null, "messageEntryViewModel");
        Contract.Requires<ArgumentNullException>(modelCommandProcessor != null, "modelCommandProcessor");
        Contract.Requires<ArgumentNullException>(sendMessageModelCommand != null, "sendMessageModelCommand");

        this.sendMessageModelCommand = sendMessageModelCommand;
        this.modelCommandProcessor = modelCommandProcessor;
        this.messageEntryViewModel = messageEntryViewModel;
    }

    /// <summary>
    /// Defines the method to be called when the command is invoked.
    /// </summary>
    /// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
    public override void Execute(object parameter)
    {
        this.SetupModelCommandFromViewModel();
        this.ExecuteModelCommand();
        this.WriteResultFromModelCommandToViewModel();
    }

    /// <summary>
    /// Defines the method that determines whether the command can execute in its current state.
    /// </summary>
    /// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
    /// <returns>
    /// true if this command can be executed; otherwise, false.
    /// </returns>
    public override bool CanExecute(object parameter)
    {
        return !string.IsNullOrEmpty(this.messageEntryViewModel.Text);
    }

    /// <summary>
    /// Setups the model command with the data from the view model.
    /// </summary>
    private void SetupModelCommandFromViewModel()
    {
        this.sendMessageModelCommand.Message = this.messageEntryViewModel.Text;
        this.sendMessageModelCommand.Channels = this.messageEntryViewModel.Channels;
    }

    /// <summary>
    /// Executes the model command on the command processor.
    /// </summary>
    private void ExecuteModelCommand()
    {
        this.modelCommandProcessor.Execute(this.sendMessageModelCommand);
    }

    /// <summary>
    /// Writes the results from model command to the view model.
    /// </summary>
    private void WriteResultFromModelCommandToViewModel()
    {
        this.messageEntryViewModel.Text = null;
    }
}

Dependencies

The SendMessageUiCommand needs several dependencies to do its job:

  • Message entry view model to access whether there is a message to send or not
  • Model command processor to execute a command that will send the message
  • Model command that sends the message

All these dependencies are injected through the constructor. The code contracts ensure that only valid values are allowed.

Execute

In the execute method the real stuff goes on. To send a message, a corresponding model command has to be created and passed to the command processor to execute it. The model command is created with the message entered by the user and the channels the user selected.

I’ll go into details about model-commands and command processor later in this series.

Finally, the message is cleared on the view-model. That will be reflected on the view due to data-binding.

Can Execute

The CanExecute method is used to determine whether the command is enabled or not. This is also reflected on the view by enabling/disabling the Send button (data-binding).

The command can be executed if the user entered a message. Therefore the command checks whether there is a message present on the view-model.

UI Command Base Class

The SendMessageUiCommand is derived from the UiCommand class, which handles the basic features of an UI command:

/// <summary>
/// Abstract base class for UI commands.
/// Takes care of registering this command with the WPF command infrastructure.
/// </summary>
public abstract class UiCommand : ICommand
{
    /// <summary>
    /// Occurs when changes occur that affect whether or not the command should execute.
    /// </summary>
    public virtual event EventHandler CanExecuteChanged
    {
        add
        {
            CommandManager.RequerySuggested += value;
        }

        remove
        {
            CommandManager.RequerySuggested -= value;
        }
    }

    /// <summary>
    /// Executes this command.
    /// </summary>
    /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
    public abstract void Execute(object parameter);

    /// <summary>
    /// Defines whether the command can execute in its current state.
    /// </summary>
    /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
    /// <returns>
    /// true if this command can be executed; otherwise, false.
    /// </returns>
    public virtual bool CanExecute(object parameter)
    {
        return true;
    }
}

The UiCommand registers the CanExecuteChanged event with the eventing infrastructure of WPF. This is needed in order to get the disabling/enabling of the button to work.

Conclusions

I showed you how to use a UI command to encapsulate the reaction to user input in a dedicated class. This approach decouples the actions taken on a user input from its visualization. You have also seen that the actual task of sending a message is delegated to another command in order to be reusable in other scenarios in the application.

About the author

Urs Enzler

Add comment

By Urs Enzler

Recent Posts