首页 > 解决方案 > 如何使窗格停留在javafx中连接draggablenode的线上

问题描述

我正在设计带有可拖动节点的图形结构的 UI。在图中,我有一个名为关系的组件(它是一个窗格),它显示了两个节点之间的链接。我希望关系保持并沿着线中部的线移动。

目前的UI设计如下图

预期的就像:

标签: javafxlinepanedrag

解决方案


修改线的结束坐标时,需要刷新节点的位置。为了避免每次布局传递多次触发计算,我建议从layoutChildren父级的方法中执行此操作,但您也可以从侦听器到startX, endY, ... 属性执行此操作。但这会导致一些不必要的计算。

至于计算节点的位置: 节点的中心需要与直线的中点对齐,因此需要求解以下方程markTopLeft

markTopLeft + (markWidth, markHeight) / 2 = (lineStart + lineEnd) / 2

markTopLeft = (lineStart + lineEnd - (markWidth, markHeight)) / 2

例子

允许自定义布局计算的窗格

public class PostProcessPane extends Pane {

    private final Set<Node> modifiedChildren = new HashSet<>();
    private final Set<Node> modifiedChildrenUnmodifiable = Collections.unmodifiableSet(modifiedChildren);
    private final List<Consumer<Set<Node>>> postProcessors = new ArrayList<>();

    public List<Consumer<Set<Node>>> getPostProcessors() {
        return postProcessors;
    }

    private final ChangeListener listener = (o, oldValue, newValue) -> modifiedChildren.add((Node) ((ReadOnlyProperty) o).getBean());

    private void initListener() {
        getChildren().addListener((ListChangeListener.Change<? extends Node> c) -> {
            while (c.next()) {
                if (c.wasRemoved()) {
                    for (Node n : c.getRemoved()) {
                        n.boundsInParentProperty().removeListener(listener);
                    }
                }
                if (c.wasAdded()) {
                    for (Node n : c.getAddedSubList()) {
                        n.boundsInParentProperty().addListener(listener);
                    }
                }
            }
        });
    }

    public PostProcessPane() {
        initListener();
    }

    public PostProcessPane(Node... children) {
        super(children);
        initListener();

        for (Node n : children) {
            n.boundsInParentProperty().addListener(listener);
        }
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();

        if (!modifiedChildren.isEmpty()) {
            for (Consumer<Set<Node>> processor : postProcessors) {
                processor.accept(modifiedChildrenUnmodifiable);
            }
            modifiedChildren.clear();
        }

    }

}

用法

@Override
public void start(Stage primaryStage) throws Exception {
    Rectangle r1 = new Rectangle(200, 50, Color.BLUE);
    Rectangle r2 = new Rectangle(200, 50, Color.RED);
    Rectangle mark = new Rectangle(200, 50, Color.YELLOW);
    Line line = new Line();

    r1.setX(20);
    r2.setX(380);
    r2.setY(450);

    PostProcessPane root = new PostProcessPane(line, r1, r2, mark);
    root.getPostProcessors().add(changedNodes -> {
        if (changedNodes.contains(r1) || changedNodes.contains(r2) || changedNodes.contains(mark)) {
            Bounds bounds1 = r1.getBoundsInParent();
            Bounds bounds2 = r2.getBoundsInParent();

            // refresh line ends
            line.setStartX(bounds1.getMinX() + bounds1.getWidth() / 2);
            line.setStartY(bounds1.getMaxY());
            line.setEndX(bounds2.getMinX() + bounds2.getWidth() / 2);
            line.setEndY(bounds2.getMinY());

            // recalculate mark position
            mark.setX((line.getStartX() + line.getEndX() - mark.getWidth()) / 2);
            mark.setY((line.getStartY() + line.getEndY() - mark.getHeight()) / 2);
        }
    });

    // add some movement for the nodes
    Timeline timeline = new Timeline(
            new KeyFrame(Duration.ZERO,
                    new KeyValue(r1.xProperty(), r1.getX()),
                    new KeyValue(r1.yProperty(), r1.getY()),
                    new KeyValue(r2.xProperty(), r2.getX())),
            new KeyFrame(Duration.seconds(1),
                    new KeyValue(r2.xProperty(), r1.getX())),
            new KeyFrame(Duration.seconds(2),
                    new KeyValue(r1.xProperty(), r2.getX()),
                    new KeyValue(r1.yProperty(), r2.getY() / 2))
    );
    timeline.setAutoReverse(true);
    timeline.setCycleCount(Animation.INDEFINITE);
    timeline.play();

    Scene scene = new Scene(root);

    primaryStage.setScene(scene);
    primaryStage.show();
}

推荐阅读