JavaFX: отложенное вычисление координат - PullRequest
0 голосов
/ 10 мая 2018

Я пытаюсь нарисовать кривую между двумя узлами, которые находятся в другом родительском контейнере.Контейнер может быть произвольно глубоко вложенным.Я могу преобразовать локальные координаты в координатное пространство общего предка, однако границы узлов недоступны во время рисования линии.

В моем приложении я создаю свои узлы в фоновом потокеи добавить их позже на сцену.Поэтому вычисление координат линии должно быть запущено, когда два узла полностью расположены (они должны иметь высоту и ширину).

Я попытался решить проблему с помощью прослушивателя изменения свойства, но не могу найти свойство, которое изменяется достаточно часто.

Есть ли событие, которое я могу прослушать, которое вызывается при необходимостисвойства установлены?

Заранее спасибо!

Я добавил упрощенный пример, который должен показать проблему.Линия отображается правильно, как только вы нажмете кнопку «перерисовать».

Класс приложения:

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Showcase");
        Scene scene = new Scene(root, 300, 300);
        primaryStage.setScene(scene);
        primaryStage.setResizable(false);
        primaryStage.show();
    }

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

sample.fxml

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.layout.*?>

<AnchorPane fx:controller="sample.Controller" xmlns:fx="http://javafx.com/fxml">

    <ToolBar AnchorPane.rightAnchor="0" AnchorPane.leftAnchor="0" prefHeight="50">
        <Button onAction="#redrawEdge" text="Redraw Line"/>
    </ToolBar>

    <AnchorPane fx:id="contentPane" AnchorPane.topAnchor="50"
                AnchorPane.leftAnchor="0"
                AnchorPane.rightAnchor="0"/>

</AnchorPane>

Контроллер:

public class Controller {

    private final Rectangle node1 = new Rectangle();
    private final Ellipse node2 = new Ellipse();
    private final HBox parent = new HBox();
    private final HBox parent2 = new HBox();
    private final VBox parentParent = new VBox();
    private final Line edge = new Line();

    @FXML
    private AnchorPane contentPane;

    @FXML
    public void initialize() {
        // not updated as expected
        node1.boundsInParentProperty().addListener((o) -> drawLine());
        node2.boundsInParentProperty().addListener((o) -> drawLine());

        node1.setHeight(20);
        node1.setWidth(20);
        node1.setFill(Color.BURLYWOOD);

        node2.setRadiusX(20);
        node2.setRadiusY(20);
        node2.setFill(Color.DEEPSKYBLUE);

        parent.setStyle("-fx-border-color: #DC143C");
        parent.setPadding(new Insets(10));
        parent.getChildren().add(node1);

        parent2.setStyle("-fx-border-color: #5CD3A9");
        parent2.setPadding(new Insets(10));
        parent2.getChildren().add(node2);

        parentParent.setStyle("-fx-border-color: #0336FF");
        parentParent.setLayoutX(200);
        parentParent.setLayoutY(50);
        parentParent.setPadding(new Insets(10));
        parentParent.getChildren().add(parent);

        contentPane.setStyle("-fx-border-color: #4B0082;");
        contentPane.setPadding(new Insets(10));
        contentPane.getChildren().addAll(parentParent, parent2, edge);
    }

    @FXML
    public void redrawEdge(ActionEvent actionEvent) {
        drawLine();
    }

    private void drawLine() {
        Bounds n1InCommonAncestor = getRelativeBounds(node1, contentPane);
        Bounds n2InCommonAncestor = getRelativeBounds(node2, contentPane);
        Point2D n1Center = getCenter(n1InCommonAncestor);
        Point2D n2Center = getCenter(n2InCommonAncestor);

        Point2D startIntersection = findIntersectionPoint(n1InCommonAncestor,
                n1Center, n2Center);
        Point2D endIntersection = findIntersectionPoint(n2InCommonAncestor,
                n2Center, n1Center);

        edge.setStartX(startIntersection.getX());
        edge.setStartY(startIntersection.getY());
        edge.setEndX(endIntersection.getX());
        edge.setEndY(endIntersection.getY());
    }

    private Bounds getRelativeBounds(Node node, Node relativeTo) {
        Bounds nodeBoundsInScene = node.localToScene(node.getBoundsInLocal());
        return relativeTo.sceneToLocal(nodeBoundsInScene);
    }

    private Point2D getCenter(Bounds bounds) {
        return new Point2D(bounds.getMinX() + bounds.getWidth() / 2,
                bounds.getMinY() + bounds.getHeight() / 2);
    }

    private Point2D findIntersectionPoint(Bounds nodeBounds,
                                          Point2D inside, Point2D outside) {

        Point2D middle = outside.midpoint(inside);

        double deltaX = outside.getX() - inside.getX();
        double deltaY = outside.getY() - inside.getY();

        if (Math.hypot(deltaX, deltaY) < 1.) {
            return middle;
        } else {
            if (nodeBounds.contains(middle)) {
                return findIntersectionPoint(nodeBounds, middle, outside);
            } else {
                return findIntersectionPoint(nodeBounds, inside, middle);
            }
        }
    }
}

1 Ответ

0 голосов
/ 10 мая 2018

Создание следующего должно работать, хотя вам может потребоваться учитывать последствия для производительности в некоторых обстоятельствах:

Node node = ... ;

ObjectBinding<Bounds> boundsInScene = new ObjectBinding<Bounds>() {

    {
        bind(node.boundsInLocalProperty(), node.localToSceneTransformProperty());
    }

    @Override
    protected Bounds computeValue() {
        return node.localToScene(node.getBoundsInLocal());
    }
};

Вы также можете сделать что-то подобное для относительных границ:

Node node1 = ... ;
Node node2 = ... ;

ObjectBinding<Bounds> relativeBounds = new ObjectBinding<Bounds>() {

    {
        bind(
            node1.boundsInLocalProperty(),
            node1.localToSceneTransformProperty(),
            node2.boundsInLocalProperty(),
            node2.localToSceneTransformProperty()
        );
    }

    @Override
    protected Bounds computeValue() {
        return node2.sceneToLocal(node1.localToScene(node1.getBoundsInLocal()));
    }
};

Добавление слушателей к ним и перерисовка при их изменении должны дать вам контроль над тем, когда нужно перерисовывать.

...