Мне кажется, не оправданно много полезных статей не только не посвящают и двух слов самому животрепещущему вопросу при начале работы с JavaFX, но и посвятив, все равно не раскрывают его полностью. А вопрос возникает следующий: как наладить связь ваших node по их fx:id и вашего кода. Как использовать их в разных частях кода, чтобы ссылаться на ваш программный интерфейс? Вот на это, я и постараюсь ответить под катом
По какой-то причине, для авторов статей и комментаторов на StackOverFlow не очевидно, что попросту не понятно для читателя, как происходит связь fx:id ссылок с вашим кодом. Потому что это не очевидно. А некоторые моменты, вообще больше похожи на магию, этот момент я отдельно упомяну ниже.
Вообще, многие статьи полезны, информативны (особенно на английском), так же, есть ответы почти на все на StackOverFlow. Но, практически нигде развернуто не сказано, как именно работать с fx:id, которые мы определяем в FXML файле. Лишь краткие упоминания, которые приводят к еще большей путанице в голове. Обычно пишут "задайте вашей node нужный вам fx:id и будем вам счастье".
К сожалению, счастья не будет. Будет NullPointerException. По причине некорректного использования, которое идет из непонимания области видимости, скажем так, этих id. И я хочу рассказать, по какой причине получается exception, а главное, как корректно связать ваш FXML с классом-контроллером и java-кодом. Надеюсь, это поможет людям избежать тех мучений, которые прошел я в поисках ответа на данный вопрос.
Я бы наверно и пришел к ответу сам, рано или поздно, но спустя многие часы дни поиска, я наткнулся на статью. В ней рассказано про MVC модель, которую и следует использовать при разработке на JavaFX. Не стану повторять написанное в статье, там достаточное описание этой модели. Если вы о ней не слышали, то к прочтению обязательно, поскольку можно обойтись работая с javaFX вообще без FXML, на чистом java коде, но не стоит. Так же, там есть некоторое пояснение, как заставить ожить fx:id, но я все же хочу дополнить эту статью своими наблюдениями и знаниями в оригинальной статье.
Модель эта, кстати, не так очевидна, даже если смотреть статьи на Oracle. По крайней мере, я не нашел у Oracle, как мне работать с fx:id. Зато очень много экспериментировал, что и привело к пониманию, как с ними работать.
Итак, с предисловием пора заканчивать, к делу.
Что конкретно не очевидно из примеров в статьях? Примеры в интернетах, предлагают в FXML оформить дизайн приложения, после создать к нему класс-контроллер, который наследуется от Application, в нем определить метод start() (или сделать это в классе Main, разные есть примеры) и там пользоваться вашими fx:id. И это работает. Казалось бы, чудненько, все так просто, что даже хочется сплясать.
Вот даже банальный пример, естественно "hello world":
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.control.Button?>
<Pane xmlns="http://javafx.com/javafx/1.8.0.261" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Main">
<Button fx:id="fxButton" text="clickMe" onAction="#click"/>
</Pane>
public class Main extends Application {
@FXML
public Button fxButton;
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("YourFXML.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public void click(ActionEvent actionEvent) {
System.out.println("Hello World");
fxButton.setText("Hey!");
}
}
Это полностью рабочий пример. В данном примере, в FXML определяется Pane, в ней одна кнопка Button, у которой fx:id="fxButton", при нажатии на нее, вызывается метод в классе-контроллере onAction="#click". В методе click есть вывод на консоль и назначения текста самой кнопке.
Если его запустить и ткнуть в кнопку, то в консоли получим результат, у кнопки изменится ее название. Лихо? А вот и нет. Это не вносит ясности, как с этим работать. И такими примерами пестрит интернет. А если не делать MVC модель или каким-либо иным способом не разделять и властвовать, получится каша, на которой подскользнешься, ну и как говорят во всех американских фильмах, "упадешь и сломаешь бедро". Причем, бедренная кость — одна из самых крепких…
Следующим, логичным казалось бы шагом, было бы взять в этом же коде выше, убрать из него метод click и поработать напрямую, из кода. Например из метода start(), чтобы далеко не ходить, добавить в него обработку клика мышки на кнопку fxButton (панель/иную часть интерфейса, не имеет значения, это простой пример fx:id):
public class Main extends Application {
@FXML
public Button fxButton;
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("YourFXML.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
// добавляем слушателя, по клику мышки выполним действие (вывод в консоль):
fxButton.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseEvent -> System.out.println("Hello World"));
}
}
Вас обматерит ваша IDE еще при запуске
Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$154(LauncherImpl.java:182)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NullPointerException
at sample.Main.start(Main.java:25)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$161(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
… 1 more
Exception running application sample.Main
Process finished with exit code 1
В первый миг, захочется материться в ответ, ведь не ясно, почему не инициализирована переменная. Ведь это контроллер, он знает про FXML файл, в него с успехом обращаются элементы интерфейса и обрабатываются, ведь в коде с методом click мы так и делали, обращались по fx:id.
Но это работает только в методах, в которые они жестко закодированы в FXML. А при прямом обращении из кода java в файл FXML, а не наоборот, получается, что объявленная в классе переменная Button fxButton, к которой мы в методе успешно обратились — не инициализирована. Вот казалось бы, незадача. И волки вроде сыты, да и овцы целы, но вот пастух слегка лукавит...
Ваша IDE, например, IDEA, вполне успешно генерирует пример выше, модель в нем верная(генерируются FXML и классы Controller, Main и немного кода в нем). Этого достаточно для начала работы. Я немного дописал кода, для наглядности, но сделал это в Main классе, вместо контроллера, для экономии места.
Итак, выходит что для взаимодействия с вашими node в в FXML файле, вам требуется создать метод, в котором действие будет обработано. Но обратите внимание, именно действие по элементу интерфейса. Внутри метода, можно так же использовать fx:id.
Но, стоит задуматься о использовании кнопки из примера выше в других частях кода (точнее выразиться, использовании fx:id этих элементов), даже в этом же классе (а это — контроллер), вас ждет NullPointerException и отсутствие понимания, собственно, WHY? А вдруг я хочу в коде ссылаться на какую-либо панель, сделав ей отдельный fx:id, менять текст или внешний вид форм, при этом не взаимодействуя физически с кнопками и прочими элементами, делая это из кода, согласно какой-то логике? Какое верное решение?
Это настолько не очевидно, учитывая отсутствие информации по данному вопросу, что я ушел в магазин за пивом...
Вернувшись и проковыряв дыру в интернете и в голове, перепробовав разные варианты, я докопался до истины. Местами в интернете, видел предложения провести инициализацию. Собственно, это и оказалось решением, но нигде опять же, нет примеров. По такому случаю, сейчас примеры с разъяснениями будут у меня. Как должна выглядеть программа на javaFX, прилагаю код.
FXML. Внешний вид. Необходимо определить в нем контроллер
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<Pane xmlns="http://javafx.com/javafx/1.8.261" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller">
<VBox layoutX="14.0" prefHeight="50.0" prefWidth="50.0">
<Button fx:id="fxButton" onAction="#click" text="clickMe" />
<Label fx:id="labelFx" minHeight="17.0" minWidth="185.0" text="label" />
</VBox>
</Pane>
Main. Единственная задача, запустить приложение, загрузить сцену из FXML
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("YourFXML.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Controller. Адаптер FXML и java кода.
public class Controller extends View implements Initializable {
@FXML
private Button fxButton;
@FXML
private Label labelFx;
@FXML
Label localLabel;
@FXML
public void click(ActionEvent actionEvent) {
System.out.println("Hello World");
fxButton.setText("Hey!");
labelLocalInitialize();
}
private void labelLocalInitialize(){
localLabel = labelFx;
localLabel.setText("local variable control");
}
@Override
public void initialize(URL location, ResourceBundle resources) {
// Инициализация и передача контроля в View класс labelFx
setViewLabelFxText("transfer of control in View \"labelFx\" variable");
// Добавляем слушателя, по клику мышки на кнопку, выводит текст на консоль
fxButton.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseEvent -> System.out.println("Listener triggered"));
}
}
View. Отвечает за то, что отображает приложение.
public class View {
@FXML
private Label labelFx;
public void setViewLabelFxText(String text){
labelFx.setText(text);
}
}
В контроллере и View я показал, как можно взаимодействовать разными способами с fx:id.
Прямо закодировать в FXML вызов метода в контроллере.
Инициализировать используемые id и иже с ними node и передавать в необходимое место в коде, вплоть до присваивания локальным переменным этих node по их fx:id.
Разберу работу кода, предложенного выше. Запускается вот такого вида окно:
В FXML файле определен текст labelFx, но в initialize() он при запуске, получает значение, которое определено в методе.
При нажатии на кнопку:
Происходит вывод на консоль "Hello World", у кнопки меняется текст на "Hey!", управление переходит в метод labelLocalInitialize(), в нем локально определенной переменной назначается объект labelFx. Следом, labelFx назначается новый текст.
Где-то выше я говорил, что есть магия в javaFX. Она начинается в классе View. Вроде, очевидно бы было, если бы от контроллера можно было наследовать классы, для работы с id, но происходит обратное… Почему так сделано, я в подробности не вдавался, хотя и любопытно, почему так. Если в комментариях знающие люди приоткроют завесу тайны, буду весьма благодарен.
К сожалению, не доступен сервер mySQL