Commands

Manuel Mauky edited this page Mar 7, 2017 · 14 revisions

Commands

package: de.saxsys.mvvmfx.utils.commands

With Commands you can encapsulate actions that can be triggered from the View. The command itself is defined in the ViewModel. Every command has a property isExecutable, isRunning and getProgress. The View can use these properties to visualize the state of an action. For example when an action is not executable at the moment a button can be set to "disabled".

Types of Commands

There are different types of Commands:

DelegateCommand

A DelegateCommand takes an Action which will be called (synchronous or asynchronous) if the execute method of the Command is called. To create a DelegateCommand you have to pass a Supplier that creates an Action (can be written as lambda).

Hint: A DelegateCommand is an extension of a Service and takes an Action which is an extension of Task

new DelegateCommand(() -> new Action() {
			@Override
			protected void action() throws Exception {
				System.out.println("hello World");
			}
});
Executable conditions

In some cases a Command isn't executable under specific conditions. A typical use case is a form that has an OK button that can only be pressed when there is no validation error in the form. For this purpose every command has a executableProperty that can be used to bind the disabledProperty of the button.

To define under which conditions a command is executable you have to provide an ObservableBooleanValue to the constructor. If no such argument is used then the command is always executable.

ObservableBooleanValue condition = new SimpleBooleanProperty();
condition.setValue(false);

new DelegateCommand(() -> new Action() {
			@Override
			protected void action() throws Exception {
				System.out.println("hello World");
			}
},condition);
Run in background

Normally a DelegateCommand is executed on the same thread as it is triggered from which typically will be the JavaFX thread. This is convenient for small actions. The drawback of this approach is that long-running actions will block the UI. For this use cases you can provide an additional constructor argument of type boolean to define whether the command should be executed in the background (in a new thread). In this case the command will take care for the updating of the runningProperty on the UI thread. You can use the whole Task interface in the implementation of the action method, because the Action is an extension of Task, .

Command command = new DelegateCommand(() -> new Action() {
			@Override
			protected void action() throws Exception {
				System.out.println("hello World");
				updateProgress(0.5,1.0); //Task functionality
			}
}, true);

CompositeCommand

The second type of command is the CompositeCommand. The purpose of this class is to aggregate other commands. When the composite command is triggered it will trigger all registered commands. The executableProperty of a composite command will only be true if all registered commands are executable. The runningProperty will be true until the last command is finished.

For a typical use case of the composite command think for example of a text editor application that has several tabs for currently opened documents. There is a DelegateCommand for each tab that will save (write to the opened file) the document of the tab. Additionally you have a "Save All" button. This button will trigger a composite command that aggregates all save commands from all tabs. When the "Save All" button is pressed every document will be saved.

Command saveTab1 = new DelegateCommand(() -> new Action() {
			@Override
			protected void action() throws Exception {
				save()
			}
});

Command saveTab2 = new DelegateCommand(() -> new Action() {
			@Override
			protected void action() throws Exception {
				save();
			}
});
...

Command saveAll = new CompositeCommand(saveTab1, saveTab2);

How are commands used in the ViewModel and the View?

The commands are part of the ViewModel and are created there. The ViewModel will provide getter for every command so that the View can use them. This way the logic for the commands is hidden from the View.

public class MyViewModel implements ViewModel() {
    private Command saveCommand;
    private BooleanProperty precondition = new SimpleBooleanProperty();

    public MyViewModel() {
        saveCommand = new DelegateCommand(() -> new Action() {
			@Override
			protected void action() throws Exception {
				save();
			}
        }, precondition, true); //Async

        precondition.bind(...); // some conditional bindings.
    }
    
    private void save() {
        // some logic
    }

    public Command getSaveCommand() {
        return saveCommand;
    }
}

public class MyView implements FxmlView<MyViewModel> {
    @FXML
    Button saveButton;

    @FXML
    LoadingIndicator loadingIndicator;

    @InjectViewModel
    MyViewModel viewModel;

    public initialize() {
        saveButton.disableProperty().bind(viewModel.getSaveCommand().executableProperty().not());
        loadingIndicator.visibleProperty(bind.getSaveCommand().runningProperty());
    }

    @FXML //Method that is called if the button is clicked
    public void saveAction() {
        viewModel.getSaveCommand().execute();
    }
}