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

91

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

((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. И все это заканчивается настройкой желаемых слушателей для сценических событий. Я считаю, что слушателей слишком много. Это единственный способ решить мою проблему?

Чечулин
источник

Ответы:

119

Вы можете получить экземпляр контроллера 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
жужик
источник
1
Думаете, вам не хватает гипса? Родительский корень = (Родительский) loader.load ();
Пол Иден,
12
Это действительно лучший способ сделать это? В платформе JavaFX нет ничего для этого?
Ханнес
1
По какой-то причине это не работает, если вы используете конструктор без параметров и передаете URL-адрес load()методу. (Кроме того, javadoc on getControllerзвучит так, как будто он должен быть setController
включен
4
@Bombe это потому, что метод load () с параметром URL является статическим методом, который ничего не устанавливает в экземпляре, который вы его вызываете.
zhujik
@ Себастьян, ну да ладно. Виноват.
Bombe
112

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

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

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

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

Роберт Мартин
источник
2
Коротко и мило. Спасибо
Седрик
7
Обратите внимание , что elementможет быть любой элемент с fx:idв этом окне: Stage stage = (Stage) element.getScene().getWindow();. Например, если в ваших окнах есть только значок Buttonс fx:idсимволом, используйте его, чтобы получить сцену.
EarthMind
29
getScene()все еще возвращается null.
m0skit0
3
Это ответ, аккуратно!
destan
12
До завершения инициализации (например, в методе инициализации контроллера) это не сработает, потому что getScene () возвращает null. Исходный плакат подразумевает, что это его ситуация. В этом случае лучше вариант 1, данный Утку Оздемиром ниже. Если вам не нужна сцена, тогда достаточно одного слушателя, я использую его в initialize (), чтобы растянуть ImageView:bgimage.sceneProperty().addListener((observableScene, oldScene, newScene) -> { if (oldScene == null && newScene != null) { bgimage.fitWidthProperty().bind(newScene.widthProperty()); ... } });
Джорджи,
32

Я знаю, что это не тот ответ, который вам нужен, но предлагаемые решения 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);
...
Утку Оздемир
источник
1
Это самый короткий справочник JavaFX на планете !! Спасибо, Утку!
Арам Пароникян
Интересный взгляд на это. Спасибо :)
Utku Özdemir
Я пытаюсь использовать этот подход для заполнения TableView и демонстрации ProgressIndicator, ориентированного на TableView, но оказывается, что внутри значения событий для getX () и getY () не такие, как если бы вы использовали после всей инициализации конец процесса (внутри события щелчка на кнопке) либо для сцены, либо для сцены, даже этап getX () и getY () возвращают NaN, любая идея?
leobelizquierdo
@leobelizquierdo, у вас сейчас проблема с getY (), вы нашли решение?
JonasAnon
если я добавлю paneк сцене, которая уже прикреплена к сцене, это не сработает, верно? Разве не следует проверять scenePropertyслушателя и добавлять stagePropertyслушателя только в том случае, если этап все еще нулевой? А иначе сразу делать то, что мне нужно?
завтра
14

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

  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. Теперь вы можете получить доступ к сцене в контроллере

Сандип Кумар
источник
Для меня это было победителем. Ответ Роберта Мартина сначала работал, но затем начал выдавать ошибку, которую я решил, сделав stageэто.
Dammeul
1

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

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

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

Анкит РаджДео
источник
1

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

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

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

Platform.runLater(()->{
    ((Stage)myPane.getScene().getWindow()).setOn*whatIwant*(...);
});
AdamOutler
источник
-3

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

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

node.getScene();

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

Platform.runLater(() -> {
    node.getScene().addEventFilter(KeyEvent.KEY_PRESSED, event -> {
               //your event
     });
});
fjqa86
источник