Не удается создать экземпляр полей класса в проекте JavaFX

Поэтому я пытаюсь перейти от Swing к JavaFX и просто объявляю методы start() и launch(), не зная, как они работают. Но ниже код выводит false и false на консоль. Однако, когда я нажимаю кнопку в графическом интерфейсе, созданном с помощью Scene Builder, который выполняет myMethod(), на этот раз он печатает true. Почему он говорит, что primaryStage не создан?

дополнительная информация: я также сделал этот класс своим контроллером по той же причине — ему нужен доступ к ссылке на сцену. Полная версия Main, которую я не публиковал, реализует Initializable, если это имеет значение.

В качестве дополнительного вопроса мне интересно, нужно ли мне поле primaryStage для ссылки на этап приложения, который будет только один из?, в myMethod().

public class Main extends Application {

    private Stage primaryStage;

    public void start(Stage primaryStage) {
        this.primaryStage = primaryStage;
        try {
            Scene scene = new Scene(FXMLLoader.load(getClass().getResource("Sample.fxml")),600,400);
            primaryStage.setScene(scene);
        } catch(Exception e) {
            e.printStackTrace();
        }
        primaryStage.show();
        //both lines below print false; As they should.
        System.out.println(this.primaryStage == null);
        myMethod();
    }

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

    public void myMethod() {
        System.out.println(primaryStage == null);
    }
}

ИЗМЕНИТЬ

размещение этого документа FXML в той же папке, что и класс выше, позволит вам запустить Main, чтобы увидеть, что кнопка действительно печатает true.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<HBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Main">
   <children>
      <Button mnemonicParsing="false" onAction="#myMethod" text="Button" />
   </children>
</HBox>

person user2651804    schedule 30.09.2015    source источник
comment
Можете ли вы показать, как вы создаете отношения слушателя между Button и MyMethod? А также, пожалуйста, покажите контроллер.   -  person James Wierzba    schedule 30.09.2015
comment
И вы говорите, что Main реализует Initializable? Почему вы удалили его из этого кода?   -  person James Wierzba    schedule 30.09.2015
comment
@JamesWierzba Я удалил части, которые казались ненужными для простоты. связь создается в документе FXML onAction="#myMethod", и метод также выполняется правильно. Просто результат удивляет   -  person user2651804    schedule 30.09.2015
comment
Вполне возможно, что удаленный вами код может иметь отношение к проблеме. Кроме того, пользователь может захотеть воссоздать вашу проблему, чтобы попытаться отладить ее, что невозможно, если вы не показываете код.   -  person James Wierzba    schedule 30.09.2015
comment
@JamesWierzba Теперь у вас есть оба файла, которые позволят вам запустить программу и подтвердить ее поведение. Я надеюсь, что это помогает.   -  person user2651804    schedule 30.09.2015
comment
Не используйте подкласс Application в качестве контроллера: это не сделает ничего проще. Метод launch() создает экземпляр подкласса Application и вызывает метод start(...). Если вы укажете тот же класс в атрибуте fx:controller, FXMLLoader создаст новый экземпляр того же класса и вставит поля с аннотациями @FXML в этот экземпляр. Когда вы нажимаете кнопку, будет вызван метод обработчика экземпляра, созданного FXMLLoader, а не экземпляра, созданного вызовом launch().   -  person James_D    schedule 30.09.2015


Ответы (2)


Это связано с тем, что FXMLLoader создаст новый экземпляр класса application.Main, в котором метод start() не вызывается и, следовательно, private Stage primaryStage имеет значение null.

Для FXML лучше разделить основной класс и контроллер, а первичный этап пройти позже, если это необходимо:

...
try
{
    FXMLLoader loader = new FXMLLoader( getClass().getResource( "Sample.fxml" ) );
    Scene scene = new Scene( loader.load(), 600, 400 );

    ( (MyController) loader.getController() ).setPrimaryStage(primaryStage);
    primaryStage.setScene( scene );
}
catch ( Exception e )
{
    e.printStackTrace();
}
...

где класс MyController может быть таким простым, как:

public class MyController {

    private Stage primaryStage;

    public void setPrimaryStage(Stage primaryStage) {
        this.primaryStage = primaryStage;
    }
}

но также может реализовать интерфейс Initializable. См. Введение в FXML :: Контроллеры.

Также обратите внимание, что вы можете получить сцену и ее сцену из любого узла, который является частью построенного графа сцены (т. е. показанной сцены):

Scene scene = anynode.getScene();
Stage primaryStage = (Stage) anynode.getScene().getWindow();

Конечно, для вторичных стадий, созданных вами, getWindow() вернет эту стадию, а не первичную стадию.

person Uluk Biy    schedule 30.09.2015
comment
Это имеет большой смысл. Но как мне тогда ссылаться на объект Stage в моем отдельно определенном контроллере? - person user2651804; 30.09.2015
comment
Я не знал, что контроллеры были определены как класс MyController? Нужно ли моему классу контроллера расширять MyController для поддержки функции getPrimaryStage()? - person user2651804; 30.09.2015
comment
@user2651804 user2651804 извините за неясность! MyController — это ваш класс :), у которого есть метод initialize() (и, конечно, в нашем случае дополнительный сеттер для primaryStage). Что-то вроде этого. - person Uluk Biy; 30.09.2015
comment
Почему вы вообще пытаетесь создать ссылку на него в контроллере? Просто вызовите getScene().getWindow() на любом узле, когда вам нужен доступ к нему. - person James_D; 30.09.2015
comment
@ user2651804 смотрите обновление. Ссылка, которую я предоставил в комментарии выше, предназначена для JavaFX 2.2. В ответе есть для JavaFX 8. - person Uluk Biy; 30.09.2015
comment
@James_D, поэтому я упомянул, если это необходимо в ответе. Но добавлю ваше примечание к ответу. - person Uluk Biy; 30.09.2015
comment
@James_D Хорошо, понятно. Когда вы говорите, что в этом никогда нет необходимости, вы не имеете в виду, что это никогда не бывает полезным, верно? В моем случае, пока я действительно искал ваше решение, оказалось, что это не так просто, поскольку у меня нет идентификатора для моего узла, кнопки. Только это тело функции. - person user2651804; 30.09.2015
comment
Я хочу сказать, что это никогда не бывает полезным. Вы почти всегда вводите что-то в контроллер. В редких случаях, когда вы этого не делаете, и если вы действительно не хотите вводить что-то специально для этой цели, вы всегда можете заставить метод обработчика принимать параметр Event. Затем вы можете вызвать getSource для события и преобразовать его в Node, а затем вызвать getScene().getWindow(). Я предпочитаю в этих случаях просто вводить корневой элемент FXML в контроллер и использовать его, хотя это позволяет избежать понижения. - person James_D; 30.09.2015
comment
@James_D, в конце концов, мне все равно придется переходить из окна в сцену, я ничего здесь не упускаю, верно? - person user2651804; 30.09.2015
comment
@user2651804 user2651804 это зависит от того, хотите ли вы дальше работать со Stage/Window. Для минимизации с помощью setIconified() требуется понижение приведения и т. д. - person Uluk Biy; 30.09.2015
comment
Может быть, поэтому я должен сказать, что он избегает некоторых понижений. (Может быть, все, в зависимости от того, для чего вам нужно окно.) - person James_D; 30.09.2015

Нет необходимости прыгать через любой из этих обручей, чтобы получить доступ к окну. Вы можете просто вызвать getScene().getWindow() на любом узле, чтобы получить окно, в котором он отображается (и, конечно, вы можете просто внедрить любой узел в свой контроллер обычным способом).

Не используйте подкласс Application в качестве класса контроллера: у вас будет (как минимум) два разных экземпляра (один создан методом launch(), потому что это подкласс Application, а другой создан методом FXMLLoader, потому что это класс контроллера). Различные поля будут инициализированы в разных экземплярах.

Создайте класс контроллера и внедрите в него хотя бы один узел:

public class Controller {

    @FXML
    private Parent root ;

    @FXML
    private void myMethod() {
        Window window = root.getScene().getStage();
        // assuming you are running as a standalone application, the window 
        // will actually be a Stage instance.

        window.hide();  // for example...
    }
}

Используйте это как класс вашего контроллера и внедрите в него узел:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<HBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller" fx:id="root">
   <children>
      <Button mnemonicParsing="false" onAction="#myMethod" text="Button" />
   </children>
</HBox>
person James_D    schedule 30.09.2015
comment
Не могли бы вы сказать, что лучше иметь ссылку на корневой узел, а не на саму сцену? С моей точки зрения кажется, что нет никакой разницы. - person user2651804; 30.09.2015
comment
Да, но только потому, что это позволяет избежать уродливой проводки к классу контроллера. Обратите внимание, что мне не нужно получать контроллер из загрузчика и вызывать для него методы и т. д. Всякий раз, когда мне нужно получить контроллер из загрузчика, я расцениваю это как признак того, что в моем проекте что-то не так (хотя есть конечно случаи, когда это проще, чем альтернатива). Код приложения должен просто загружать компонент пользовательского интерфейса и что-то с ним делать: ему не нужно беспокоиться о том, как компонент пользовательского интерфейса управляется. И опять же, количество раз, когда вы еще не вводили что-то, ничтожно мало. - person James_D; 30.09.2015
comment
@ user2651804, могут быть сценарии, в которых есть первичный этап, но fxml этого контроллера не является его частью. А именно 2 разных этапа, где нам нужен этап, который не возвращается с помощью getWindow(). Но это также будет запах кода. - person Uluk Biy; 30.09.2015
comment
@ user2651804, в конечном итоге у вас будет несколько контроллеров, когда приложение станет сложным. И будет потребность передавать данные внутри этих контроллеров. Существуют разные подходы, которые уже опубликованы для подобных вопросов на SO здесь. В этих случаях вы можете получить контроллер из FXMLloader или даже использовать опцию fxmlloadler.setController() для некоторых мест. - person Uluk Biy; 30.09.2015
comment
@UlukBiy Я пришел к выводу, что как только вы достигнете этой точки, D.I. Фреймворк стоит иметь, чтобы дизайн оставался элегантным. Используйте afterburner.fx, который представляет собой D.I., ориентированный на FX. framework или используйте стандартный: вот мой пример Spring. Но вроде не по теме для этого вопроса :). - person James_D; 30.09.2015
comment
Да, возможно, сейчас это немного вышло за рамки. Большое спасибо, ребята, вам обоим! :) Мне всегда было очень трудно понять концепцию MVC-паттерна. не могли бы вы просто очень быстро ответить, что вы имели в виду с кодом приложения? Это мой контроллер? И имеет ли смысл говорить о model без базы данных? да/нет будет достаточно, спасибо, ребята!! - person user2651804; 30.09.2015
comment
Под кодом приложения я имел в виду (это может быть или не быть стандартным использованием в данном контексте) код, который собирает приложение, т. е. загружает fxml и помещает его в сцену, возможно, инициализирует модель и т. д. Итак, в этом примере код в подклассе Application. Модель имеет смысл без базы данных (она представляет данные в приложении, независимо от того, откуда эти данные были получены). Мой пример Spring/FX, упомянутый в предыдущем комментарии, является своего рода моим идеалом для организации приложения (реальные приложения могут не соответствовать этому идеалу...). - person James_D; 30.09.2015