【问题标题】:Implementing Undo/Redo in JavaFX在 JavaFX 中实现撤消/重做
【发布时间】:2016-11-17 16:10:25
【问题描述】:

我正在尝试在 JavaFX 中实现撤消/重做 - 我使用 graphicsContext() 绘制所有形状。我环顾四周,发现图形上下文中有一个save 方法,但它只是保存属性而不是画布的实际形状/状态。最好的解决方法是什么?

这是我创建圆圈时的代码之一,例如:

 public CircleDraw(Canvas canvas, Scene scene, BorderPane borderPane) {
        this.borderPane = borderPane;
        this.scene = scene;
        this.graphicsContext = canvas.getGraphicsContext2D();

        ellipse = new Ellipse();
        ellipse.setStrokeWidth(1.0);
        ellipse.setFill(Color.TRANSPARENT);
        ellipse.setStroke(Color.BLACK);

        pressedDownMouse = event -> {
            startingPosX = event.getX();
            startingPosY = event.getY();
            ellipse.setCenterX(startingPosX);
            ellipse.setCenterY(startingPosY);
            ellipse.setRadiusX(0);
            ellipse.setRadiusY(0);
            borderPane.getChildren().add(ellipse);

        };

        releasedMouse = event -> {
            borderPane.getChildren().remove(ellipse);
            double width = Math.abs(event.getX() - startingPosX);
            double height = Math.abs(event.getY() - startingPosY);
            graphicsContext.setStroke(Color.BLACK);
            graphicsContext.strokeOval(Math.min(startingPosX, event.getX()),    Math.min(startingPosY, event.getY()), width, height);
        removeListeners();
        };

        draggedMouse = event -> {
            ellipse.setCenterX((event.getX() + startingPosX) / 2);
            ellipse.setCenterY((event.getY() + startingPosY) / 2);
            ellipse.setRadiusX(Math.abs((event.getX() - startingPosX) / 2));
            ellipse.setRadiusY(Math.abs((event.getY() - startingPosY) / 2));

        };

    }

【问题讨论】:

  • UndoFX library 可能会帮助您解决问题。该库仅提供了一个抽象的撤消/重做状态管理器,因此它不能解决开箱即用的问题,您需要在应用程序中使用相当多的自定义代码才能适当地使用它(例如,诸如fabian 的解决方案中的EllipseDrawOperation 仍然需要 UndoFX 库,它只是提供了一个存储和操作此类操作历史记录的地方。

标签: java javafx javafx-2 javafx-8


【解决方案1】:

这里的问题是这样的信息没有保存在Canvas 中。此外,没有反向操作可以让您为每个抽奖信息返回到先前的状态。当然,您可以绘制相同的椭圆,但使用背景颜色,但是以前绘图信息中的信息可能已被覆盖,例如如果您要绘制多个相交的椭圆。

但是,您可以使用命令模式存储绘图操作。这使您可以重绘所有内容。

public interface DrawOperation {
    void draw(GraphicsContext gc);
}

public class DrawBoard {
    private final List<DrawOperation> operations = new ArrayList<>();
    private final GraphicsContext gc;
    private int historyIndex = -1;

    public DrawBoard(GraphicsContext gc) {
        this.gc = gc;
    }

    public void redraw() {
        Canvas c = gc.getCanvas();
        gc.clearRect(0, 0, c.getWidth(), c.getHeight());
        for (int i = 0; i <= historyIndex; i++) {
            operations.get(i).draw(gc);
        }
    }

    public void addDrawOperation(DrawOperation op) {
        // clear history after current postion
        operations.subList(historyIndex+1, operations.size()).clear();

        // add new operation
        operations.add(op);
        historyIndex++;
        op.draw(gc);
    }

    public void undo() {
        if (historyIndex >= 0) {
            historyIndex--;
            redraw();
        }
    }

    public void redo() {
        if (historyIndex < operations.size()-1) {
            historyIndex++;
            operations.get(historyIndex).draw(gc);
        }
    }
}

class EllipseDrawOperation implements DrawOperation {

    private final double minX;
    private final double minY;
    private final double width;
    private final double height;
    private final Paint stroke;

    public EllipseDrawOperation(double minX, double minY, double width, double height, Paint stroke) {
        this.minX = minX;
        this.minY = minY;
        this.width = width;
        this.height = height;
        this.stroke = stroke;
    }

    @Override
    public void draw(GraphicsContext gc) {
        gc.setStroke(stroke);
        gc.strokeOval(minX, minY, width, height);
    }

}

DrawBoard 实例而不是Canvas 传递给您的类并替换

graphicsContext.setStroke(Color.BLACK);
graphicsContext.strokeOval(Math.min(startingPosX, event.getX()),    Math.min(startingPosY, event.getY()), width, height);

drawBoard.addDrawOperation(new EllipseDrawOperation(
                             Math.min(startingPosX, event.getX()),
                             Math.min(startingPosY, event.getY()),
                             width,
                             height,
                             Color.BLACK));

undoredo 浏览历史。

【讨论】:

  • 这对fabian 非常有帮助,谢谢——不过我有一个问题。如果我画另一个圆圈(在我画出第一个圆圈之后),我会不断收到错误 - 在这条线上抛出:operations.subList(historyIndex + 1, operations.size()).clear(); 我得到一个:"JavaFX Application Thread" java.lang.IllegalArgumentException: fromIndex(1) &gt; toIndex(0)。我不太清楚为什么会这样?
  • @xn139:抱歉,但目前我无法找到一系列操作来重现此问题。此外:修改historyIndexundo/redo/addDrawOperation)/列表(addDrawOperation)大小的方法应该确保historyIndex总是一个有效的索引或-1,所以historyIndex &lt;= size应该得到保证。
  • @fabian 在其他东西上用透明颜色绘图不会改变任何东西。这就是透明色的目的。
  • 现在已经在作者的回答中更正了。 (只是为了避免混淆。)
  • @fabian,非常感谢 - 我已经解决了我遇到的问题。感谢您的帮助!
猜你喜欢
  • 2016-02-18
  • 2015-08-10
  • 2011-11-14
  • 2011-03-10
  • 2012-04-08
  • 2020-08-11
  • 2021-06-04
  • 2012-07-02
  • 2014-04-20
相关资源
最近更新 更多