JavaFX: как получить сцену из контроллера во время инициализации?

Я хочу обрабатывать сценические события (т. Е. Скрытие) из моего класса контроллера. Итак, все, что мне нужно сделать, это добавить слушателя через

((Stage)myPane.getScene().getWindow()).setOn*whatIwant*(...);

но проблема в том, что инициализация начинается сразу после

Parent root = FXMLLoader.load(getClass().getResource("MyGui.fxml"));

и раньше

Scene scene = new Scene(root);
stage.setScene(scene);

таким образом .getScene () возвращает null.

Единственный обходной путь, который я нашел сам, - это добавить слушателя к myPane.sceneProperty (), и когда он станет не нулевым, я получаю сцену, добавляю к ней .windowProperty () my! Черт возьми! обработка слушателя, которую я, наконец, извлекаю stage. И все заканчивается настройкой желаемых слушателей для сценических событий. Я считаю, что слушателей слишком много. Это единственный способ решить мою проблему?


person Chechulin    schedule 06.11.2012    source источник


Ответы (7)


Вы можете получить экземпляр контроллера из FXMLLoader после инициализации через getController(), но тогда вам нужно создать экземпляр FXMLLoader вместо использования статических методов.

Я бы прошел этап после вызова load() непосредственно в контроллер:

FXMLLoader loader = new FXMLLoader(getClass().getResource("MyGui.fxml"));
Parent root = (Parent)loader.load();
MyController controller = (MyController)loader.getController();
controller.setStageAndSetupListeners(stage); // or what you want to do
person zhujik    schedule 06.11.2012
comment
Думаете, вам не хватает гипса? Родительский корень = (Родительский) loader.load (); - person Paul Eden; 15.02.2014
comment
Это действительно лучший способ сделать это? В структуре JavaFX нет ничего, что могло бы заархивировать это? - person Hannes; 18.10.2014
comment
По какой-то причине это не работает, если вы используете конструктор без параметров и передаете URL-адрес методу load(). (Также javadoc на _2 _ похоже, что это должно быть на setController.) - person Bombe; 07.02.2015
comment
@Bombe это потому, что метод load () с параметром URL является статическим методом, который ничего не устанавливает в экземпляре, который вы его вызываете. - person zhujik; 26.02.2015
comment
controller.setStageAndSetupListeners(stage); не имеет такого метода. Что я должен делать? - person Martin Erlic; 07.08.2017
comment
@santafebound, это метод, который вы должны реализовать для себя, или вы можете вызвать любой метод вашего контроллера, который вам нравится. Это просто заполнитель в моем ответе. - person zhujik; 08.08.2017

Все, что вам нужно, это дать AnchorPane идентификатор, и тогда вы сможете получить Stage от него.

@FXML private AnchorPane ap;
Stage stage = (Stage) ap.getScene().getWindow();

Отсюда вы можете добавить Listener, которое вам нужно.

Изменить: как указано ниже EarthMind, это не обязательно должен быть элемент AnchorPane; это может быть любой элемент, который вы определили.

person Robert Martin    schedule 28.07.2015
comment
Обратите внимание, что element может быть любым элементом с fx:id в этом окне: Stage stage = (Stage) element.getScene().getWindow();. Например, если в ваших окнах есть только Button с fx:id, используйте это, чтобы получить сцену. - person EarthMind; 11.01.2016
comment
getScene() по-прежнему возвращает null. - person m0skit0; 24.05.2016
comment
До завершения инициализации (например, в методе инициализации контроллера) это не сработает, потому что getScene () возвращает null. Исходный плакат подразумевает, что это его ситуация. В этом случае лучше вариант 1, представленный Утку Оздемиром ниже. Если вам не нужна сцена, тогда достаточно одного слушателя, я использую это в initialize (), чтобы растянуть ImageView: bgimage.sceneProperty().addListener((observableScene, oldScene, newScene) -> { if (oldScene == null && newScene != null) { bgimage.fitWidthProperty().bind(newScene.widthProperty()); ... } }); - person Georgie; 22.02.2017
comment
Это решает мою проблему, и я считаю, что это лучший способ. Я думаю, стоит заметить, что «ap» - это идентификатор AnchorPane, указанный в поле fx: id в вашем файле .fxml (если вы работаете с дизайном пользовательского интерфейса с помощью FXML) или именем, которое вы дали объекту AnchorPane. Обычно он отлично работает для всех объектов XXXPane. - person Aaron Chen; 17.01.2018

Я знаю, что это не тот ответ, который вам нужен, но предлагаемые решения IMO не очень хороши (и ваш собственный путь). Почему? Потому что они зависят от состояния приложения. В JavaFX элемент управления, сцена и этап не зависят друг от друга. Это означает, что элемент управления может существовать без добавления к сцене, а сцена может существовать без привязки к сцене. Затем в момент времени t1 элемент управления может быть прикреплен к сцене, а в момент t2 эта сцена может быть добавлена ​​к сцене (и это объясняет, почему они являются наблюдаемыми свойствами друг друга).

Таким образом, подход, который предлагает получить ссылку на контроллер и вызвать метод, передав ему этап, добавляет состояние в ваше приложение. Это означает, что вам нужно вызвать этот метод в нужный момент, сразу после создания этапа. Другими словами, вам необходимо выполнить следующий порядок: 1- Создать этап 2- Передать этот созданный этап контроллеру с помощью метода.

Вы не можете (или не должны) изменять этот порядок при таком подходе. Итак, вы потеряли безгражданство. А в ПО вообще состояние зло. В идеале методы не должны требовать какого-либо порядка вызова.

Так какое же правильное решение? Есть две альтернативы:

1- Ваш подход в свойствах прослушивания контроллера, чтобы получить сцену. Я считаю, что это правильный подход. Нравится:

pane.sceneProperty().addListener((observableScene, oldScene, newScene) -> {
    if (oldScene == null && newScene != null) {
        // scene is set for the first time. Now its the time to listen stage changes.
        newScene.windowProperty().addListener((observableWindow, oldWindow, newWindow) -> {
            if (oldWindow == null && newWindow != null) {
                // stage is set. now is the right time to do whatever we need to the stage in the controller.
                ((Stage) newWindow).maximizedProperty().addListener((a, b, c) -> {
                    if (c) {
                        System.out.println("I am maximized!");
                    }
                });
            }
        });
    }
});

2- Вы делаете то, что вам нужно, там, где вы создаете Stage (а это не то, что вы хотите):

Stage stage = new Stage();
stage.maximizedProperty().addListener((a, b, c) -> {
            if (c) {
                System.out.println("I am maximized!");
            }
        });
stage.setScene(someScene);
...
person Utku Özdemir    schedule 18.06.2015
comment
Я пытаюсь использовать этот подход для заполнения TableView и демонстрации ProgressIndicator, ориентированного на TableView, но оказывается, что внутри значения событий для getX () и getY () не такие, как если бы вы использовали после всей инициализации конец процесса (внутри события щелчка на кнопке) либо для сцены, либо для сцены, даже этап getX () и getY () возвращают NaN, любая идея? - person leobelizquierdo; 26.04.2016
comment
@leobelizquierdo, у вас сейчас проблема с getY (), вы нашли решение? - person JonasAnon; 20.03.2017
comment
если я добавлю pane к сцене, которая уже прикреплена к сцене, это не сработает, верно? Разве не следует проверять sceneProperty прослушиватель и добавлять прослушиватель stageProperty только в том случае, если этап все еще нулевой? В противном случае сразу делать то, что мне нужно? - person tomorrow; 23.08.2020

Самый простой способ получить объект сцены в контроллере:

  1. Добавьте дополнительный метод в собственный созданный класс контроллера, например (это будет метод установки для установки сцены в классе контроллера),

    private Stage myStage;
    public void setStage(Stage stage) {
         myStage = stage;
    }
    
  2. Получить контроллер в методе запуска и настроить этап

    FXMLLoader loader = new FXMLLoader(getClass().getResource("MyFXML.fxml"));
    OwnController controller = loader.getController();
    controller.setStage(this.stage);
    
  3. Теперь вы можете получить доступ к сцене в контроллере

person Sandeep Kumar    schedule 15.10.2014
comment
Для меня это было победителем. Ответ Роберта Мартина сначала сработал, но затем начал выдавать ошибку, которую я решил, получив stage вот так. - person Dammeul; 21.12.2018

Назначьте fx: id или объявите переменную для любого узла: anchorpane, button и т. Д. Затем добавьте к нему обработчик событий и в этот обработчик событий вставьте указанный ниже код:

Stage stage = (Stage)((Node)((EventObject) eventVariable).getSource()).getScene().getWindow();

Надеюсь, что это работает для вас!!

person Ankit RajDeo    schedule 11.08.2018

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

Platform.runLater(() -> {
    ((Stage) listView.getScene().getWindow()).widthProperty().addListener((obs, oldVal, newVal) -> {
        listView.refresh();
    });
});

в твоем случае

Platform.runLater(()->{
    ((Stage)myPane.getScene().getWindow()).setOn*whatIwant*(...);
});
person AdamOutler    schedule 23.01.2020

Вы можете получить с node.getScene, если вы не вызываете из Platform.runLater, результатом будет нулевое значение.

пример нулевого значения:

node.getScene();

пример без нулевого значения:

Platform.runLater(() -> {
    node.getScene().addEventFilter(KeyEvent.KEY_PRESSED, event -> {
               //your event
     });
});
person fjqa86    schedule 12.08.2017