Why doesn't JavaFX include an undo facility

4.4k views Asked by At

I recently started learning the JavaFX API, after I already experience in Swing.

I noticed, that even a lot of classes were already well implemented in AWT and Swing, they were effectively re-implemented in JavaFX. This includes:

javafx.scene.paint.Color
javafx.event.ActionEvent

vs.

java.awt.Color
java.awt.event.ActionEvent

and much more, even though it could've easily require to use them. I assume that this is to:

  • Decouple JavaFX the most possible, from the other libraries (so new developers shouldn't even know of their existence..., OK).
  • Leverage Java 8 lambda expressions.
  • Make use of Java 5 generics and enum types.
  • Design with FXML in mind.
  • Bindings... JavaFX's version of magic.

If my assumptions are true, why didn't they include a new implementation of:

javax.swing.undo

package?

Although I understand that undo has really nothing to do with the user interface, so, it has nothing to do with Swing too. If for any reason they decided to include it in the javax.swing package, so could they include it in JavaFX.

1

There are 1 answers

2
djKartright On

Why they "forgot" to implement this, is a good question. I would argue, that JavaFX is still in development (that should say everything). However, I needed this long ago and I implemented my own approach using the Command Pattern. As shown below, this is not much effort and very simple.

First you will need to create a Interface called Command, to execute some operations in your application.

public interface Command {
    /**
     * This is called to execute the command from implementing class.
     */
    public abstract void execute();

    /**
     * This is called to undo last command.
     */
    public abstract void undo();
}

Next you will need some class called History to save your executed commands and to undo them.

public final class History {
    // ...
    private static History instance = null;
    private final Stack<Command> undoStack = new Stack<Command>();
    // ...

    public void execute(final Command cmd) {
        undoStack.push(cmd);
        cmd.execute();
    }

    public void undo() {
        if (!undoStack.isEmpty()) {
            Command cmd = undoStack.pop();
            cmd.undo();
        } else {
            System.out.println("Nothing to undo.");
        }
    }

    public static History getInstance() {
        if (History.instance == null) {
            synchronized (History.class) {
                if (History.instance == null) {
                    History.instance = new History();
                }
            }
        }
        return History.instance;
    }

    private History() { }
}

In your FXML you then create a button for your GUI which should invoke the undo function of your application. In your FXML create a button, such as the following one:

<Button fx:id="btnUndo" font="$x2" onAction="#onUndo" prefWidth="75.0" 
        text="Undo" textAlignment="CENTER" underline="false">
    <tooltip>
        <Tooltip text="Undo last command" textAlignment="JUSTIFY" />
    </tooltip>
    <HBox.margin>
        <Insets left="5.0" right="5.0" fx:id="x1" />
    </HBox.margin>
</Button>

In your controller class you reference the button from your FXML.

public class Controller {
    // ...
    @FXML private Button btnUndo;
    // ...

    @FXML
    public void onUndo(ActionEvent event)
    {
        History.getInstance().undo();
    }
}

As you can see, the best thing is that the History class is a Singleton. So you can access the class from everywhere.

Inherit from Command interface to implement a new command. Use some buttons or similar GUI elements for new functionality and execute the custom command using your history.

// You can give arguments to command constructor if you like
Command someCmd = new SomeCommand();
History.getInstance().execute(someCmd); // Saved to history; now you're able to undo using button

With this approach you will be able to undo your operation. It is also possible to implement some redo functionality. For that just add a redo button in FXML and the appropriate method in History class and Command interface.

For more information on the command pattern, have a look here.

Happy Coding!