撤销和重做功能在很多软件都是非常普遍的,这里记录一下JavaFX实现撤销和重做功能的一种方案:

1. 接口定义

对于所有能执行撤销和重做的动作定义统一的接口:

public interface Undo {
    void execute();
    void undo();
    void redo();
}

2. UndoManager

要实现撤销(undo)和重做(redo),需要一个UndoManager来管理所有Undo和Redo,这个UndoManager是整个撤销和重做的核心,下面是具体的实现代码:

public class UndoManager {

    private static final UndoManager instance = new UndoManager(30);
    public static UndoManager getInstance() {
        return instance;
    }

    private final int maxSize;
    private final List<Undo> undoStack = new ArrayList<>();
    private final List<Undo> redoStack = new ArrayList<>();

    public UndoManager(int maxSize) {
        this.maxSize = maxSize;
    }

    public void push(Undo undo) {
        undo.execute();
        undoStack.add(0, undo);
        if (undoStack.size() > maxSize) {
            undoStack.remove(undoStack.size() - 1);
        }
        redoStack.clear();
    }

    public void clear() {
        undoStack.clear();
        redoStack.clear();
    }


    public boolean canUndo() {
        return !undoStack.isEmpty();
    }

    public void undo() {
        final Undo undo = undoStack.get(0);
        undo.undo();
        undoStack.remove(0);
        redoStack.add(0, undo);
    }

    public boolean canRedo() {
        return !redoStack.isEmpty();
    }

    public void redo() {
        final Undo undo = redoStack.get(0);
        undo.redo();
        redoStack.remove(0);
        undoStack.add(0, undo);
    }
}

3. 使用UndoManager

UndoManager的使用非常简单,直接通过UndoManager.getInstance()获取实例即可,默认情况下支持撤销的次数为30。

下面是一个使用例子:

public class Sample02 extends Application {

    @Override
    public void start(Stage stage) {
        UndoManager undoManager = UndoManager.getInstance();

        StackPane root = new StackPane();

        VBox container = new VBox();
        container.setSpacing(10);
        container.setAlignment(Pos.CENTER);
        root.getChildren().add(container);

        Button btn = new Button("新增文本");
        AtomicInteger inc = new AtomicInteger(0);
        btn.setOnMouseClicked(event -> undoManager.push(new TextUndo(container, new Label("米虫2022" + inc.incrementAndGet()))));

        Button undo = new Button("撤销(undo)");
        undo.setOnMouseClicked(event -> {
            if (undoManager.canUndo()) {
                undoManager.undo();
            }
        });

        Button redo = new Button("重做(redo)");
        redo.setOnMouseClicked(event -> {
            if (undoManager.canRedo()) {
                undoManager.redo();
            }
        });

        container.getChildren().addAll(btn, undo, redo);

        stage.setTitle("撤销 + 重做");
        stage.setWidth(320);
        stage.setHeight(480);
        stage.setScene(new Scene(root));
        stage.getIcons().clear();
        stage.getIcons().add(new Image("logo.jpg"));
        stage.show();
    }
}

效果如下:

4. 监听

可以进一步完善UndoManager对外暴露变更属性,外部通过监听变更,跟无法undo或者redo时禁用相应的功能。

调整代码实现如下:

Button undo = new Button("撤销(undo)");
undo.setDisable(true);
undo.setOnMouseClicked(event -> {
    if (undoManager.canUndo()) {
        undoManager.undo();
    }
});

Button redo = new Button("重做(redo)");
redo.setDisable(true);
redo.setOnMouseClicked(event -> {
    if (undoManager.canRedo()) {
        undoManager.redo();
    }
});

undoManager.revisionProperty().addListener((ob, ov, nv) -> {
    undo.setDisable(!undoManager.canUndo());
    redo.setDisable(!undoManager.canRedo());
});

效果如下:

posted on 2023-01-08 15:02  $$X$$  阅读(369)  评论(0)    收藏  举报