
javafx.scene.command.package-info Maven / Gradle / Ivy
/*
* Copyright (c) 2022, JFXcore. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. JFXcore designates this
* particular file as subject to the "Classpath" exception as provided
* in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/**
* Provides the set of classes for the Commanding API.
*
* Contents
*
* - 1. Overview
*
- 2. Creating and using commands
*
* - 2.1. Event bindings
*
- 2.1. How commands affect the disabled state of a node
*
* - 3. Asynchronous commands
*
*
- 4. Command behaviors
*
* - 4.1. Command-based behaviors
*
- 4.2. Control-based behaviors
*
*
*
*
* 1. Overview
* Commanding enables applications to handle user input in a loosely coupled, object-oriented, and reusable way.
*
* A {@link javafx.scene.command.Command} represents an operation that can be invoked in various ways, such as
* by clicking a button, pressing a key, or typing a shortcut.
* Decoupling the command implementation from its mode of invocation allows applications to define an operation
* once, and use it in different places and with different input modalities.
*
* Commands also communicate their state to the user interface. For example, if a command is not executable,
* a button that invokes the command is automatically {@link javafx.scene.Node#disabledProperty() disabled}
* to reflect the state of the command.
*
*
*
2. Creating and using commands
* A command can be created by extending the {@link javafx.scene.command.Command} class.
* For ease of use, JFXcore comes with several predefined command implementations.
* In this example, {@link javafx.scene.command.RelayCommand} is used to create a simple command that prints
* some text when invoked:
* {@code
* Command myCommand = new RelayCommand<>(() -> {
* System.out.println("Command was executed.");
* });
* }
* In order to use this command, it must be bound to an event of a UI control:
* {@code
* var button = new Button();
* button.setOnAction(new ActionEventBinding(myCommand));
* }
* Now, clicking the button will invoke the command.
*
*
* 2.1. Event bindings
* Bindings between commands and events are represented by an {@link javafx.scene.command.EventBinding}.
* Since {@code EventBinding} implements the {@link javafx.event.EventHandler} interface, it can be assigned
* to event handler properties like {@link javafx.scene.Node#onKeyPressedProperty() onKeyPressed} or
* {@link javafx.scene.control.Button#onActionProperty() onAction}.
*
* JFXcore comes with {@code EventBinding} implementations for the most commonly used event types:
*
*
* Event type Event binding type
* {@link javafx.event.ActionEvent} {@link javafx.scene.command.ActionEventBinding}
* {@link javafx.scene.input.KeyEvent} {@link javafx.scene.command.KeyEventBinding}
* {@link javafx.scene.input.MouseEvent} {@link javafx.scene.command.MouseEventBinding}
* {@link javafx.scene.input.TouchEvent} {@link javafx.scene.command.TouchEventBinding}
*
* When {@code EventBinding} receives an event, it calls the {@link javafx.scene.command.Command#execute(Object)}
* method of the bound command with the value of its {@link javafx.scene.command.EventBinding#parameterProperty() parameter}
* property as an argument. The {@code parameter} property can be used to pass a value from the invoker of the
* command to the command implementation.
*
*
* 2.2. How commands affect the disabled state of a node
* When a command is bound to an event of a UI control using {@code EventBinding}, the control
* is automatically {@link javafx.scene.Node#disabledProperty() disabled} when the command is
* not {@link javafx.scene.command.Command#executableProperty() executable}.
* When multiple commands are bound to the same control, the control will be disabled when any
* of the bound commands are not executable.
*
* When the command is an instance of {@link javafx.scene.command.AsyncCommand}, the control will
* also be disabled while the operation is running (as indicated by {@code AsyncCommand}'s
* {@link javafx.scene.command.AsyncCommand#executingProperty() executing} property), preventing
* users from accidentally invoking a running command multiple times.
* This behavior can be configured by setting the {@code EventBinding}'s
* {@link javafx.scene.command.EventBinding#disabledWhenExecutingProperty() disabledWhenExecuting} property.
*
*
*
3. Asynchronous commands
* Commands that encapsulate asynchronous operations can be created by extending the
* {@link javafx.scene.command.AsyncCommand} class.
*
* JFXcore comes with two implementations of {@code AsyncCommand}:
*
* - {@link javafx.scene.command.TaskCommand}, which creates and executes a {@link javafx.concurrent.Task}
* when the command is invoked
*
- {@link javafx.scene.command.ServiceCommand}, which wraps a {@link javafx.concurrent.Service} instance
*
* In this example, {@code TaskCommand} is used to create a command that wraps a long-running asynchronous operation:
* {@code
* AsyncCommand myAsyncCommand = new TaskCommand<>(() -> new Task() {
* @Override
* protected Void call() throws InterruptedException {
* System.out.println("Task started");
* Thread.sleep(1000);
* System.out.println("Task completed");
* return null;
* }
* });
* }
*
*
* 3.1. Cancelling a running operation
* The {@link javafx.scene.command.AsyncCommand} class introduces the
* {@link javafx.scene.command.AsyncCommand#cancel()} method, which can be used to cancel a running operation.
*
* When {@link javafx.scene.command.EventBinding} receives an event that invokes its bound command, and
*
* - if the operation is not running, {@code EventBinding} calls {@link javafx.scene.command.Command#execute(Object)}
* to start the operation;
*
- if the operation is running, {@code EventBinding} calls {@link javafx.scene.command.AsyncCommand#cancel()}
* to cancel the operation.
*
*
* In this example, a user interface for a cancellable operation is implemented in two different ways:
*
* - Using a single button to start and stop the operation
*
{@code
* // Set disabledWhenExecuting to false, which keeps the button enabled when the command is running
* var binding = new ActionEventBinding(myAsyncCommand);
* binding.setDisabledWhenExecuting(false);
*
* var button = new Button();
* button.setOnAction(binding);
* }
* - Using separate buttons to start and stop the operation
*
{@code
* // Create a button that invokes the command
* var startButton = new Button("Start");
* var startBinding = new ActionEventBinding(myAsyncCommand);
* startButton.setOnAction(startBinding);
*
* // Create a button that cancels the command, and is only enabled when the command is running
* var cancelButton = new Button("Cancel");
* var cancelBinding = new ActionEventBinding(myAsyncCommand);
* cancelBinding.setDisabledWhenExecuting(false);
* cancelButton.setOnAction(cancelBinding);
* cancelButton.disableProperty().bind(myAsyncCommand.executingProperty().not());
* }
*
*
*
* 3.2. Progress reporting for asynchronous operations
* It is often desirable for asynchronous operations to report their progress back to the user interface.
* For example, a user interface might show a progress bar to visualize the progress of a long-running operation.
*
* The {@link javafx.scene.command.ProgressiveCommand} class extends {@link javafx.scene.command.AsyncCommand}
* to support this scenario by adding the {@link javafx.scene.command.ProgressiveCommand#progressProperty() progress}
* property.
*
* Note: {@link javafx.scene.command.TaskCommand} and {@link javafx.scene.command.ServiceCommand} implement
* the {@code ProgressiveCommand} class to support progress reporting.
*
*
*
4. Command behaviors
* Commands can communicate their state back to the user interface in various ways. By default, a control will
* be {@link javafx.scene.Node#disabledProperty() disabled} when one of its bound commands is either not executable
* (as indicated by the {@link javafx.scene.command.Command#executableProperty() executable} property) or
* currently executing (as indicated by the {@link javafx.scene.command.AsyncCommand#executingProperty() executing}
* property). Behaviors such as these are called command behaviors.
*
* Custom command behaviors can be implemented in two different ways, depending on the purpose of the behavior:
*
* - Command-based behaviors are implemented by overriding the command's
* {@link javafx.scene.command.Command#onAttached(javafx.scene.Node)} and
* {@link javafx.scene.command.Command#onDetached(javafx.scene.Node)} methods.
* Since the behavior is a part of the command itself, it generally applies to
* all controls to which the command is bound.
*
- Control-based behaviors are implemented as a {@link javafx.scene.command.CommandHandler}
* that is attached to a specific {@link javafx.scene.control.Control}.
* Since a control-based behavior only applies to a specific control, it allows
* more fine-grained customization when compared to a command-based behavior.
*
* While it is legal to have multiple behaviors affect a single control, care must be taken to ensure that
* the different implementations do not interfere with each other. For example, when a command-based behavior
* sets the control's {@link javafx.scene.control.Labeled#textProperty()}, no {@code CommandHandler} should
* be added that also sets the same property.
*
*
* 4.1. Command-based behavior
* A command-based behavior is implemented by overriding the
* {@link javafx.scene.command.Command#onAttached(javafx.scene.Node)} and
* {@link javafx.scene.command.Command#onDetached(javafx.scene.Node)} methods.
*
* In this example, the {@code MyLabeledCommand} class implements a behavior that binds the
* {@link javafx.scene.control.Labeled#textProperty() text} and
* {@link javafx.scene.control.Labeled#textProperty() graphic} properties of a {@link javafx.scene.control.Labeled}
* control to corresponding properties of the command.
*
*
{@code
* public class MyLabeledCommand extends RelayCommand {
* private final StringProperty text = new SimpleStringProperty(this, "text");
* public final StringProperty textProperty() { return text; }
*
* private final StringProperty iconUrl = new SimpleStringProperty(this, "iconUrl");
* public final StringProperty iconUrlProperty() { return iconUrl; }
*
* public MyLabeledCommand(Runnable execute) {
* super(execute);
* }
*
* @Override
* protected void onAttached(Node node) {
* if (node instanceof Labeled labeled) {
* labeled.textProperty().bind(text);
* labeled.graphicProperty().bind(
* Bindings.createObjectBinding(
* () -> {
* if (iconUrl.get() == null) return null;
* return new ImageView(iconUrl.get());
* },
* iconUrl));
* }
* }
*
* @Override
* protected void onDetached(Node node) {
* if (node instanceof Labeled labeled) {
* labeled.textProperty().unbind();
* labeled.graphicProperty().unbind();
* }
* }
* }
* }
*
*
* 4.2. Control-based behavior
* A control-based behavior is created by implementing the {@link javafx.scene.command.CommandHandler} interface.
* In this example, the {@code MyActivityBehavior} class adds an activity indicator graphic to a button,
* visualizing a running {@link javafx.scene.command.AsyncCommand}.
* {@code
* public class MyActivityBehavior implements CommandHandler {
* @Override
* public void onAttached(Node node, Command command) {
* if (node instanceof Labeled l && command instanceof AsyncCommand c) {
* l.graphicProperty().bind(
* Bindings.createObjectBinding(
* () -> {
* if (c.isExecuting())
* return new ImageView("activityIndicator.gif");
* return null;
* },
* c.executingProperty()));
* }
* }
*
* @Override
* public void onDetached(Node node, Command command) {
* if (node instanceof Labeled l) {
* l.graphicProperty().unbind();
* }
* }
* }
* }
*
* The previously created behavior can then be added to a control by setting the control's
* {@link javafx.scene.control.Control#commandHandlerProperty() commandHandler} property:
* {@code
* // Using the control-based behavior with a button:
* var button = new Button();
* button.setCommandHandler(new MyActivityBehavior());
* }
*
* Similarly, the same effect can also be achieved by subclassing a control and adding the behavior in its
* constructor. This approach is useful when the behavior implementation needs to access non-public methods
* or fields of the control.
*
* Note that in this example, the {@link javafx.scene.Node#addCommandHandler(javafx.scene.command.CommandHandler)}
* API is used instead of setting the control's {@link javafx.scene.control.Control#commandHandlerProperty() commandHandler}
* property. This keeps the behavior an implementation detail of the control, and doesn't leak it to the
* outside world.
*
{@code
* public class MyActivityButton extends Button {
* public MyActivityButton() {
* addCommandHandler(new MyActivityBehavior());
* }
* }
* }
*
* As another option, the behavior can be added to a custom {@link javafx.scene.control.Skin Skin}.
* This approach has the advantage of being able to use CSS to apply behavioral skins to controls.
* As before, note the use of the {@link javafx.scene.Node#addCommandHandler(javafx.scene.command.CommandHandler)}
* and {@link javafx.scene.Node#removeCommandHandler(javafx.scene.command.CommandHandler)} APIs:
* {@code
* public class MyActivitySkin extends ButtonSkin {
* private final CommandHandler behavior = new MyActivityBehavior();
*
* public MyActivitySkin(Button control) {
* super(control);
* control.addCommandHandler(behavior);
* }
*
* @Override
* public void dispose() {
* getSkinnable().removeCommandHandler(behavior);
* super.dispose();
* }
* }
* }
*
*/
package javafx.scene.command;