JavaFX Node.getBoundsInParent () не работает, если вызывается слишком рано после макета

Если я вызываю Node.getBoundsInParent() слишком рано после добавления узла в GridPane, все члены возвращенного объекта Bounds будут 0.0. Вот код Java, демонстрирующий мою проблему:

import java.lang.Runnable;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;

public class MyApplication extends Application {

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

    @Override
    public void start(Stage primaryStage) {
        List<Label> labels = new ArrayList<Label>();
        for (int index = 0; index < 12; ++index) {
            labels.add(new Label(String.format("%d", index)));
        }

        GridPane gridPane = new GridPane();

        Button layoutButton = new Button("Layout");
        layoutButton.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent event) {
                    gridPane.getChildren().setAll(labels);
                    for (int index = 0; index < labels.size(); ++index) {
                        gridPane.setConstraints(labels.get(index), 0, index);
                    }
                }
            });

        Button getBoundsButton = new Button("Get Bounds");
        getBoundsButton.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent event) {
                    System.out.println(labels.get(0).getBoundsInParent());
                }
            });

        Button layoutAndGetBoundsButton = new Button("Layout and Get Bounds");
        layoutAndGetBoundsButton.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent event) {
                    gridPane.getChildren().setAll(labels);
                    for (int index = 0; index < labels.size(); ++index) {
                        gridPane.setConstraints(labels.get(index), 0, index);
                    }

                    System.out.println(labels.get(0).getBoundsInParent());
                }
            });

        Button layoutAndGetBoundsLaterButton = new Button("Layout and Get Bounds Later");
        layoutAndGetBoundsLaterButton.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent event) {
                    gridPane.getChildren().setAll(labels);
                    for (int index = 0; index < labels.size(); ++index) {
                        gridPane.setConstraints(labels.get(index), 0, index);
                    }

                    class MyRunnable implements Runnable {
                        Label label;
                        public MyRunnable(Label label) {
                            this.label = label;
                        }
                        @Override
                        public void run() {
                            System.out.println(this.label.getBoundsInParent());
                        }
                    }

                    Platform.runLater(new MyRunnable(labels.get(0)));
                }
            });

        VBox vbox = new VBox();
        vbox.getChildren().setAll(
            layoutButton,
            getBoundsButton,
            layoutAndGetBoundsButton,
            layoutAndGetBoundsLaterButton);

        HBox hbox = new HBox();
        hbox.getChildren().setAll(vbox, gridPane);

        primaryStage.setTitle("Race Condition?");
        primaryStage.setScene(new Scene(hbox, 320, 240));
        primaryStage.show();
    }

}

И вот Jython, который впервые появился в моем вопросе:

import java.lang.Runnable as Runnable
import javafx.application.Application as Application
import javafx.application.Platform as Platform
import javafx.event.EventHandler as EventHandler
import sys

class MyApplication(Application):

    @classmethod
    def main(cls, args):
        MyApplication.launch(cls, args)

    def start(self, primaryStage):
        import javafx.scene.Scene as Scene
        import javafx.scene.control.Button as Button
        import javafx.scene.control.Label as Label
        import javafx.scene.layout.GridPane as GridPane
        import javafx.scene.layout.HBox as HBox
        import javafx.scene.layout.VBox as VBox

        labels = [Label('%d' % index) for index in range(12)]
        gridPane = GridPane()

        layoutButton = Button('Layout')
        def _handler(event):
            gridPane.getChildren().setAll(labels)
            for index, label in enumerate(labels):
                gridPane.setConstraints(label, 0, index)
        layoutButton.setOnAction(_handler)

        getBoundsButton = Button('Get Bounds')
        def _handler(event):
            print labels[0].getBoundsInParent()
        getBoundsButton.setOnAction(_handler)

        layoutAndGetBoundsButton = Button('Layout and Get Bounds')
        def _handler(event):
            gridPane.getChildren().setAll(labels)
            for index, label in enumerate(labels):
                gridPane.setConstraints(label, 0, index)
            print labels[0].getBoundsInParent()
        layoutAndGetBoundsButton.setOnAction(_handler)

        layoutAndGetBoundsLaterButton = Button('Layout and Get Bounds Later')
        def _handler(event):
            gridPane.getChildren().setAll(labels)
            for index, label in enumerate(labels):
                gridPane.setConstraints(label, 0, index)
            class MyRunnable(Runnable):
                def __init__(self, label):
                    self.label = label
                def run(self):
                    print self.label.getBoundsInParent()
            Platform.runLater(MyRunnable(labels[0]))
        layoutAndGetBoundsLaterButton.setOnAction(_handler)

        vbox = VBox()
        vbox.getChildren().setAll([
                layoutButton,
                getBoundsButton,
                layoutAndGetBoundsButton,
                layoutAndGetBoundsLaterButton])

        hbox = HBox()
        hbox.getChildren().setAll([vbox, gridPane])

        primaryStage.setTitle('Race Condition?')
        primaryStage.setScene(Scene(hbox, 320, 240))
        primaryStage.show()

MyApplication.main(sys.argv)
  • Если я последовательно нажимаю кнопки «Макет» и «Получить границы», моя программа, как и ожидалось, напечатает ограничивающую рамку с некоторыми ненулевыми элементами.

  • Если я нажму кнопку «Разметка и получение границ», моя программа напечатает ограничивающую рамку со всеми элементами, установленными в ноль. (Если щелкнуть эту кнопку в течение секунды, будут напечатаны ожидаемые результаты.)

  • Если я нажму кнопку «Разметить и получить границы позже», результаты будут не лучше.

Кстати, я вижу эту проблему с GridPane прямо сейчас, но я видел аналогичные проблемы с другими элементами управления JavaFX: я прошу его сделать что-то, а затем прошу его сделать что-то еще, но он не совсем готов к сделай еще второе.

Что я делаю неправильно?

Обновление: похоже, я столкнулся с этой проблемой который был закрыт как «не проблема».


person davidrmcharles    schedule 20.11.2015    source источник


Ответы (1)


У меня есть обходной путь, но он уродлив и не совсем надежен.

Мой обходной путь - поместить вызов Node.getBoundsInParent() внутри обработчика событий, а затем использовать анимацию, чтобы задержать его на миллисекунду:

def printBounds(event):
    print labels[0].getBoundsInParent()

import javafx.animation.Timeline as Timeline
import javafx.animation.KeyFrame as KeyFrame
import javafx.util.Duration as Duration

timeline = Timeline([
        KeyFrame(
            Duration.millis(1), printBounds, [])])
timeline.play()

Этого достаточно, чтобы GridPane стабилизировался, поэтому Node вернет правильные границы.

Скажите, пожалуйста, есть способ получше.

person davidrmcharles    schedule 20.11.2015