如何在 JavaFX 场景图中拖动位于另一个 `shape` 下方的 `Shape`?

How to drag a `Shape` that is below anther `shape` in JavaFX scene graph?

我正在使用 JavaFX 编写一个教育应用程序,用户可以在其中绘制和操作贝塞尔曲线 LineQuadCurveCubicCurve。这些曲线应该能够用鼠标拖动。我有两种选择:

1- 使用classes LineQuadCurveCubicCurve,然后用透明色填充,并用另一种颜色描边,比如黑色.该选项出现的问题是用户想要拖动一条曲线,但看到拖动的是另一条曲线。这样做的原因是用户要拖动的曲线位于场景图中另一条曲线的下方。例如,在下图中,较小的曲线无法拖动,因为它在场景图中位于较大曲线的下方。

2- 使用 class javafx.scene.shape.Path,在这种情况下,问题是操纵这样的路径有点复杂,因为它由一些 PathElement 组成,并且简单地操作元素不会改变 Path,除非我们从其元素 属性 中删除一个元素并添加一个新元素。因此我更喜欢方法 1.

如何克服第一种方法中出现的问题?

提前感谢您的帮助。我的程序代码简化版如下

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.CubicCurve;
import javafx.scene.shape.QuadCurve;
import javafx.stage.Stage;

public class SampleForWhosebug extends Application
{
    double lastMouseX;
    double lastMouseY;
    double lastTranslateX;
    double lastTranslateY;

    @Override
    public void start(Stage window)
    {
        final double STROKE_WIDTH = 5;

        QuadCurve quad = new QuadCurve(100, 200, 150, 50, 200, 200);
        quad.setFill(Color.TRANSPARENT);
        quad.setStroke(Color.BLACK);
        quad.setStrokeWidth(STROKE_WIDTH);
        quad.setOnMousePressed(e -> {
            lastMouseX = e.getSceneX();
            lastMouseY = e.getSceneY();
            lastTranslateX = quad.getTranslateX();
            lastTranslateY = quad.getTranslateY();
        });
        quad.setOnMouseDragged(e -> followMouse(quad, e));

        CubicCurve cubic = new CubicCurve(0, 300, 100, 0, 300, 0, 300, 300);
        cubic.setFill(Color.TRANSPARENT);
        cubic.setStroke(Color.BLACK);
        cubic.setStrokeWidth(STROKE_WIDTH);
        cubic.setOnMousePressed(e -> {
            lastMouseX = e.getSceneX();
            lastMouseY = e.getSceneY();
            lastTranslateX = cubic.getTranslateX();
            lastTranslateY = cubic.getTranslateY();
        });
        cubic.setOnMouseDragged(e -> followMouse(cubic, e));

        Group root = new Group(quad, cubic);
        Scene scene = new Scene(root, 500, 500);
        window.setScene(scene);
        window.show();
    }


    private void followMouse(Node node, MouseEvent e)
    {
        double deltaX = e.getSceneX() - lastMouseX;
        double deltaY = e.getSceneY() - lastMouseY;
        node.setTranslateX(deltaX + lastTranslateX);
        node.setTranslateY(deltaY + lastTranslateY);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

使用路径创建 QuadCurve 和 CubicCurve 似乎对我来说效果很好。这是一个完整的例子:

import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.QuadCurveTo;
import javafx.stage.Stage;

public class SampleForWhosebug extends Application {

    private final double STROKE_WIDTH = 5;

    private double lastMouseX;
    private double lastMouseY;
    private double lastTranslateX;
    private double lastTranslateY;

    @Override
    public void start(Stage window) {

        Path quad = initQuadCurve(100, 200, 200, 200, 150, 50);
        Path cubic = initCubicCurve(0, 300, 300, 300, 100, 0, 300, 0);

        Pane root = new Pane(cubic, quad);

        Scene scene = new Scene(root, 500, 500);
        window.setScene(scene);
        window.show();
    }

    private Path initQuadCurve(int xStart, int yStart, int xEnd, int yEnd, int controlX, int controlY) {

        Path curvePath = new Path();
        curvePath.setStrokeWidth(STROKE_WIDTH);

        MoveTo moveTo = new MoveTo(xStart, yStart);

        QuadCurveTo quadTo = new QuadCurveTo();
        quadTo.setControlX(controlX);
        quadTo.setControlY(controlY);
        quadTo.setX(xEnd);
        quadTo.setY(yEnd);

        curvePath.getElements().addAll(moveTo, quadTo);

        curvePath.setOnMousePressed(e -> {
            lastMouseX = e.getSceneX();
            lastMouseY = e.getSceneY();
            lastTranslateX = curvePath.getTranslateX();
            lastTranslateY = curvePath.getTranslateY();
        });

        curvePath.setOnMouseDragged(e -> followMouse(curvePath, e));

        return curvePath;
    }

    private Path initCubicCurve(int xStart, int yStart, int xEnd, int yEnd, int x1Control, int y1Control, int x2Control,
            int y2Control) {

        Path curvePath = new Path();
        curvePath.setStrokeWidth(STROKE_WIDTH);
        MoveTo moveTo = new MoveTo(xStart, yStart);

        CubicCurveTo cubicTo = new CubicCurveTo();
        cubicTo.setControlX1(x1Control);
        cubicTo.setControlY1(y1Control);
        cubicTo.setControlX2(x2Control);
        cubicTo.setControlY2(y2Control);
        cubicTo.setX(xEnd);
        cubicTo.setY(yEnd);

        curvePath.getElements().addAll(moveTo, cubicTo);

        curvePath.setOnMousePressed(e -> {
            lastMouseX = e.getSceneX();
            lastMouseY = e.getSceneY();
            lastTranslateX = curvePath.getTranslateX();
            lastTranslateY = curvePath.getTranslateY();
        });

        curvePath.setOnMouseDragged(e -> followMouse(curvePath, e));

        return curvePath;
    }

    private void followMouse(Node node, MouseEvent e) {
        double deltaX = e.getSceneX() - lastMouseX;
        double deltaY = e.getSceneY() - lastMouseY;
        node.setTranslateX(deltaX + lastTranslateX);
        node.setTranslateY(deltaY + lastTranslateY);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

老实说,我的第一次尝试是按照下面两个 post 的建议调用 quad.setPickOnBounds(false);(以及立方体):

鼠标事件在底层被忽略

JavaFX 通过透明节点将 MouseEvents 传递给子节点

但它不起作用或者我遗漏了一些东西,但是如果你自己创建路径可行,我会发现没有必要让事情变得更复杂。无论如何,如果您想遵循第一种方法,我建议您查看链接。如果您要遵循第二种方法,我认为操纵路径不会很困难。

您应该明确地将曲线的填充颜色设置为 null 并将 pickOnBounds 设置为 false,而不是在您的第一个场景中使用透明颜色。透明颜色仍会捕获鼠标事件,但 null 不会,当 pickOnBounds 为 false 时,仅当您正好位于形状的彩色部分上方时才会捕获鼠标事件。