-
Notifications
You must be signed in to change notification settings - Fork 104
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".
There are different types of Commands:
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 aService
and takes anAction
which is an extension ofTask
new DelegateCommand(() -> new Action() {
@Override
protected void action() throws Exception {
System.out.println("hello World");
}
});
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);
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);
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);
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();
}
}