I know this question is asked many times, but I can't find a solution, that works for me (actually I can't even see what I am doing wrong).
The basic idea is to load GUI-components when needed. So I structured the GUI in various FXML-Files and implemented controller-classes. Both - FXML-files and classes - are stored in the same package but ther is a package for every component. Every FXML-file is loading and added to the GUI as long as I do not define the controller-class within the FXML-file (fx:controller). If it is defined I will get a LoadException.
For a better understanding here is my code (simplified):
Main.java:
package application;
import application.a.ControllerA;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application
{
// Button aus MainLayout.fxml
#FXML
private Button button;
#Override
public void start(Stage primaryStage)
{
try
{
BorderPane root = new BorderPane();
Parent contentMain = FXMLLoader.load(getClass().getResource("MainLayout.fxml"));
ControllerA contentA = new ControllerA(root);
root.setTop(contentA.getContent());
root.setCenter(contentMain);
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
// Event-Handler für den Button -> funktioniert!
#FXML
public void buttonClicked(ActionEvent e)
{
if (!button.getText().equals("NEW"))
{
button.setText("NEW");
}
else
{
button.setText("OLD");
}
}
}
This class is also a controller for the following layout (and it works fins so far):
MainLayout.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.GridPane?>
<Pane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Main">
<children>
<Button fx:id="button" mnemonicParsing="false" onAction="#buttonClicked" text="Button" />
</children>
</Pane>
In a sub-package (called a) of application you will find this:
ControllerA.java:
package application.a;
import java.net.URL;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
public class ControllerA
{
private Parent content;
#FXML
private Button buttonA;
public ControllerA(BorderPane root)
{
String sceneFile = "A.fxml";
URL url = null;
try
{
url = getClass().getResource(sceneFile);
content = FXMLLoader.load(url);
}
catch (Exception ex)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Parent getContent()
{
return content;
}
#FXML
public void clickedA(ActionEvent e)
{
buttonA.setText("Clicked already");
}
}
A.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.GridPane?>
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.a.ControllerA">
<children>
<Button fx:id="buttonA" layoutX="274.0" layoutY="188.0" mnemonicParsing="false" onAction="#clickedA" text="A" />
</children>
</Pane>
And this is, where it all went wrong:
javafx.fxml.LoadException:
/Z:/BachelorArbeit/Projektdateien/Entwicklung/EclipseWorkspace/Sandbox/bin/application/a/A.fxml:8
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
at javafx.fxml.FXMLLoader.access$700(FXMLLoader.java:103)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:932)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:971)
at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
at application.a.ControllerA.<init>(ControllerA.java:26)
at application.Main.start(Main.java:35)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(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$148(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.InstantiationException: application.a.ControllerA
at java.lang.Class.newInstance(Class.java:427)
at sun.reflect.misc.ReflectUtil.newInstance(ReflectUtil.java:51)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927)
... 23 more
Caused by: java.lang.NoSuchMethodException: application.a.ControllerA.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 25 more
I've tried to fiddle around with the path-String like
"./a/A.fxml"
"/application/a/A.fxml"
"A.fxml"
"a/A.fxml"
...
but nothing worked. I would be quite relieved if someone can halp me with this problem.
You're mixing two different ways of using FXML and controllers. If your FXML file has fx:controller="SomeClass", then the FXMLLoader will instantiate that class and use the instance as a controller: in other words, it makes the FXML create the controller.
On the other hand, your controller's constructor loads the FXML, so you also have the controller creating the UI defined in the FXML.
The reason for the exception is that when the FXMLLoader encounters the fx:controller attribute, it calls the no-argument constructor of the specified class: i.e. in this case it tries to call new ControllerA(). Since there is no such constructor you get the exception:
java.lang.NoSuchMethodException: application.a.ControllerA.<init>()
It's not really clear what the purpose of the BorderPane parameter to the controller's constructor is, as you never use it. However, even if you had an appropriate constructor here, it would cause another problem: loading the FXML would invoke the controller's constructor, which loads the FXML, which would invoke the controller's constructor, etc: you would get a StackOverflowException. If you want to load the FXML from the controller's constructor:
remove the fx:controller attribute from the FXML file
Explicitly set the controller on the FXMLLoader to the current controller instance:
public ControllerA(BorderPane root)
{
String sceneFile = "A.fxml";
URL url = null;
try
{
url = getClass().getResource(sceneFile);
FXMLLoader loader = new FXMLLoader(url);
loader.setController(this);
content = loader.load();
}
catch (Exception ex)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
I'll show you an minimal working example project and I suggest that you adapt your structure to it.
The folder structure is as follows
Here is the main class
package dynamic.content.javafx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public final class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("main.fxml"));
Scene scene = new Scene(root, 300, 200);
stage.setTitle("FXML Welcome");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The main controller
package dynamic.content.javafx;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
public final class MainController implements Initializable{
#FXML
private Button btnSwitch;
#FXML
private AnchorPane contentPane;
#Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
}
private boolean swtch = false;
#FXML
public void handleContentSwitch() throws IOException{
Parent contentNode = null;
if(swtch){
System.out.println("loading content A");
contentNode = FXMLLoader.load(getClass().getResource("./content/content_a.fxml"));
}else{
System.out.println("loading content B");
contentNode = FXMLLoader.load(getClass().getResource("./content/content_b.fxml"));
}
contentPane.getChildren().clear();
contentPane.getChildren().add(contentNode);
swtch = !swtch;
}
}
The main FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dynamic.content.javafx.MainController">
<children>
<Button fx:id="btnSwitch" onAction="#handleContentSwitch" alignment="CENTER" contentDisplay="CENTER" mnemonicParsing="false" text="Switch Content" />
<AnchorPane fx:id="contentPane" prefHeight="150.0" prefWidth="300.0" />
</children>
</VBox>
Let's say we have content A and B we need to create a controller and a FXML every one of them
package dynamic.content.javafx.content;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.Initializable;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class ContentAController implements Initializable{
#FXML
private Label labl;
#Override
public void initialize(URL location, ResourceBundle resources) {
labl.setText("Content A");
}
}
And now the corresponding FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="150.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dynamic.content.javafx.content.ContentAController">
<children>
<Label fx:id="labl" alignment="CENTER" contentDisplay="CENTER" text="Label" textAlignment="CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
Here follows the second controller
package dynamic.content.javafx.content;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
public class ContentBController implements Initializable{
#FXML
private Label labl;
#Override
public void initialize(URL location, ResourceBundle resources) {
labl.setText("Content B");
}
}
and the second FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="150.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dynamic.content.javafx.content.ContentBController">
<children>
<Label fx:id="labl" alignment="CENTER" contentDisplay="CENTER" text="Label" textAlignment="CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
If you know press the button the content will be loaded dynamically
If you have any questions, just let me know :-)
Related
my program is really simple, it's just two scenes, the second scene just says "this is the second scene" and in the first scene you have to select a folder which will then appear in an un-editable TextField. you can go back and forth between these two scenes using a back and next button. The problem is, when I go to the second scene then go back to the first scene, the TextField is empty, when it should really be displaying the path of the folder I selected before, but when I reselect the folder it shows again, but every time I change the scene, it disappears. The FX:id of the TextField is textfield. I tried making it so that it again sets the text to the path of the folder but this time in the "back" method for the back button. But this never works if it's not in the OpenFolder method for some reason, it doesn't work anywhere else. I want to know why it only works in the OpenFolder method and not anywhere else. This is my controller class:
package sample;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
import javafx.event.ActionEvent;
public class Controller {
private Stage stage;
private Scene scene;
private Parent root;
public Controller() {
}
#FXML
public void Next(ActionEvent event) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("scene2.fxml"));
stage = ((Stage)((Node)event.getSource()).getScene().getWindow());
scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
#FXML
public void Back(ActionEvent event) throws IOException{
Parent root = FXMLLoader.load(getClass().getResource("scene1.fxml"));
stage = ((Stage)((Node)event.getSource()).getScene().getWindow());
scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
#FXML
private TextField textfield = new TextField();
#FXML
private AnchorPane anchorid1;
#FXML
public static File thefolder;
#FXML
DirectoryChooser SongsOpener;
#FXML
public File OpenFolder(){
final DirectoryChooser SongsOpener = new DirectoryChooser();
stage = (Stage) anchorid1.getScene().getWindow();
thefolder = SongsOpener.showDialog(stage);
if (thefolder != null){
textfield.setText(thefolder.getAbsolutePath());
}
return thefolder;
}
}
This is my FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<AnchorPane fx:id="anchorid1" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="411.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<children>
<Text layoutX="326.0" layoutY="76.0" strokeType="OUTSIDE" strokeWidth="0.0" text="This is scene 1">
<font>
<Font size="33.0" />
</font>
</Text>
<Button layoutX="939.0" layoutY="373.0" mnemonicParsing="false" onAction="#Next" text="Next" />
<Label layoutX="63.0" layoutY="117.0" prefHeight="18.0" prefWidth="122.0" text="Songs folder :">
<font>
<Font size="20.0" />
</font></Label>
<Button layoutX="888.0" layoutY="121.0" mnemonicParsing="false" onAction="#OpenFolder">
<graphic>
<ImageView fitHeight="18.0" fitWidth="17.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="#../toppng.com-folder-icon-png-transparent-black-and-white-folder-ico-1589x1366.png" />
</image>
</ImageView>
</graphic>
</Button>
<TextField fx:id="textfield" layoutX="190.0" layoutY="119.0" prefHeight="23.0" prefWidth="685.0" />
</children>
</AnchorPane>
Here it shows the path in the TextField after I selected the folder image
But when I go to the next scene and come back, it disappears : image
Every time you load a FXML file, the FXMLLoader creates a new set of controls corresponding to the elements in the FXML file. So if you reload the first view, you get a new TextField, which obviously does not contain the same text as the previous TextField unless you explicitly set the text.
So you can either avoid reloading the FXML files every time (first solution below), or create a mechanism by which you update the text field with the correct value every time you load the FXML (second solution below, using a model and binding to update the text field).
Note also that there's absolutely no reason to create new Scenes every time you change the view. Just use a single scene and replace its root.
Solution loading each view once
So one way to fix this is to just load each FXML once, and then arrange to switch between the two views in the controllers:
App.java:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class App extends Application {
#Override
public void start(Stage stage) throws IOException {
FXMLLoader loader1 = new FXMLLoader(getClass().getResource("View1.fxml"));
Parent view1 = loader1.load();
FXMLLoader loader2 = new FXMLLoader(getClass().getResource("View2.fxml"));
Parent view2 = loader2.load();
Scene scene = new Scene(view1);
View1Controller controller1 = loader1.getController();
View2Controller controller2 = loader2.getController();
controller1.setOnNext(() -> scene.setRoot(view2));
controller2.setOnBack(() -> scene.setRoot(view1));
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
View1Controller.java:
import java.io.File;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
public class View1Controller {
private Runnable onNext = () -> {};
#FXML
private TextField textfield ;
public void setOnNext(Runnable onNext) {
this.onNext = onNext ;
}
#FXML
private void next() {
onNext.run();
}
#FXML
public void openFolder(){
final DirectoryChooser songsOpener = new DirectoryChooser();
Stage stage = (Stage) textfield.getScene().getWindow();
File thefolder = songsOpener.showDialog(stage);
if (thefolder != null){
textfield.setText(thefolder.getAbsolutePath());
}
}
}
View2Controller.java
import javafx.fxml.FXML;
public class View2Controller {
private Runnable onBack = () -> {} ;
public void setOnBack(Runnable onBack) {
this.onBack = onBack ;
}
#FXML
private void back() {
onBack.run();
}
}
View1.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="411.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.jamesd.examples.switchviews.View1Controller">
<children>
<Text layoutX="326.0" layoutY="76.0" strokeType="OUTSIDE" strokeWidth="0.0" text="This is scene 1">
<font>
<Font size="33.0" />
</font>
</Text>
<Button layoutX="939.0" layoutY="373.0" mnemonicParsing="false" onAction="#next" text="Next" />
<Label layoutX="63.0" layoutY="117.0" prefHeight="18.0" prefWidth="122.0" text="Songs folder :">
<font>
<Font size="20.0" />
</font></Label>
<Button layoutX="888.0" layoutY="121.0" mnemonicParsing="false" onAction="#openFolder" text="Open" />
<TextField fx:id="textfield" layoutX="190.0" layoutY="119.0" prefHeight="23.0" prefWidth="685.0" />
</children>
</AnchorPane>
View2.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.HBox?>
<HBox alignment="CENTER" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.jamesd.examples.switchviews.View2Controller">
<children>
<Button mnemonicParsing="false" onAction="#back" text="Back" />
</children>
</HBox>
Solution using a model to store the state
Another way is to create a model containing the data you need, and bind, for example, the text in the text field to an appropriate property in the model. Then just pass the same model instance to each controller.
For example:
ViewState.java:
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class ViewState {
private final StringProperty selectedFolder = new SimpleStringProperty();
public final StringProperty selectedFolderProperty() {
return this.selectedFolder;
}
public final String getSelectedFolder() {
return this.selectedFolderProperty().get();
}
public final void setSelectedFolder(final String selectedFolder) {
this.selectedFolderProperty().set(selectedFolder);
}
}
Then in App.java:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class App extends Application {
#Override
public void start(Stage stage) throws IOException {
FXMLLoader loader1 = new FXMLLoader(getClass().getResource("View1.fxml"));
Parent view1 = loader1.load();
ViewState viewState = new ViewState();
View1Controller controller = loader1.getController();
controller.setViewState(viewState);
Scene scene = new Scene(view1);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
View1Controller.java:
import java.io.File;
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.control.TextField;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
public class View1Controller {
#FXML
private TextField textfield ;
private ViewState viewState ;
public void setViewState(ViewState viewState) {
this.viewState = viewState ;
textfield.textProperty().bindBidirectional(viewState.selectedFolderProperty());
}
#FXML
private void next() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("View2.fxml"));
Parent root = loader.load();
textfield.getScene().setRoot(root);
View2Controller controller = loader.getController();
controller.setViewState(viewState);
}
#FXML
public void openFolder(){
final DirectoryChooser songsOpener = new DirectoryChooser();
Stage stage = (Stage) textfield.getScene().getWindow();
File thefolder = songsOpener.showDialog(stage);
if (thefolder != null){
textfield.setText(thefolder.getAbsolutePath());
}
}
}
View2Controller:
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
public class View2Controller {
private ViewState viewState ;
#FXML
private Parent root ;
public void setViewState(ViewState viewState) {
this.viewState = viewState;
}
#FXML
private void back() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("View1.fxml"));
Parent newRoot = loader.load();
this.root.getScene().setRoot(newRoot);
View1Controller controller = loader.getController();
controller.setViewState(viewState);
}
}
The only change to the FXML files is adding a fx:id to the root element in the second view:
View2.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.HBox?>
<HBox fx:id="root" alignment="CENTER" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.jamesd.examples.switchviews.View2Controller">
<children>
<Button mnemonicParsing="false" onAction="#back" text="Back" />
</children>
</HBox>
i'm new at JavaFx and actually i'm trying to change/set a label text. In my eyes i did everything that is to do but it does not work. Hope that someone could help me. I'm searching now for hours but guess i'll never find my misstake. Furthermore i guess the misstake is to find at the controller but in some examples i watched, it looks similar to my code..
The FXML:
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<SplitPane id="Split" dividerPositions="0.3" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="620.0" prefWidth="871.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="presentation.DataController">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<Label id ="name" fx:id="name" layoutX="26.0" layoutY="21.0" prefHeight="576.0" prefWidth="205.0" textAlignment="CENTER"/>
</children></AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<Label fx:id="daten" layoutX="19.0" layoutY="14.0" prefHeight="576.0" prefWidth="511.0"/>
<!-- <Slider layoutX="573.0" layoutY="11.0" orientation="VERTICAL" prefHeight="576.0" prefWidth="14.0" /> !-->
</children></AnchorPane>
</items>
</SplitPane>
My Controller:
package presentation;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
public class DataController implements Initializable {
#FXML
private Label name;
private Label daten;
private void init() {
name.setText("Hello World!");
daten.setText("AnotherTest");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
And the Application Class
package presentation;
import application.*;
import data.*;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
public class Data extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("DataView.fxml"));
Scene scene = new Scene(root);
stage.setTitle("Übung 0");
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
public class DataController implements Initializable {
#FXML
private Label name;
#FXML
private Label daten;
private void init() {
name.setText("Hello World!");
daten.setText("AnotherTest");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
init();
}
}
You need to load your fxml file in the controller class. Something like this:
Parent warningRoot = FXMLLoader.load(getClass().getResource("yourfxmlfile.fxml"));
Scene scene = new Scene(warningRoot);
Stage stage = new Stage();
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 6 years ago.
This is my Main Class
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("application/anwendung.fxml"));
primaryStage.setTitle("Benutzerverwaltung");
root.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(new Scene(root));
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
This is my Controller class
package application;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.input.InputEvent;
import javafx.stage.Stage;
class AnwendungsController {
#FXML
public Button closeButton;
#FXML
public void handleCloseButtonAction(ActionEvent event) {
Stage stage = (Stage) closeButton.getScene().getWindow();
stage.close();
}
}
And this is my fxml file
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="140.0" prefWidth="350.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.AnwendungsController">
<children>
<Button fx:id="closeButton" layoutX="18.0" layoutY="100.0" mnemonicParsing="false" onAction="#handleCloseButtonAction" onMouseClicked="#onMouseClickedCancelBtn" prefHeight="26.0" prefWidth="77.0" text="Abbrechen" />
<Label layoutX="10.0" layoutY="27.0" prefHeight="27.0" prefWidth="342.0" text="Sie können das System nun verwenden" textAlignment="CENTER" textOverrun="CLIP">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Label>
</children>
</AnchorPane>
How can I fix it? I try everything from here JavaFX "Location is required." even though it is in the same package
UPDATE:
java.lang.NullPointerException: Location is required.
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3207)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
at application.Main.start(Main.java:20)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(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$148(WinApplication.java:191)
at java.lang.Thread.run(Unknown Source)
Two problems exist:
1)The File path
2)The Controller was not set properly
Recommendation:
If you are using SceneBuilder, when you want to see what your controller might look like, you can go to View -> Show Sample Controller Skeleton.
Solution
Main class:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
System.out.println(getClass().getResource("anwendung.fxml").getPath());
//other solution
//FXMLLoader loader = new FXMLLoader(getClass().getResource("anwendung.fxml"));
//Parent root = loader.load();
//Keep in mind that you are calling a static method
Parent root = FXMLLoader.load(getClass().getResource("anwendung.fxml"));
primaryStage.setTitle("Benutzerverwaltung");
root.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(new Scene(root));
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Controller:
package application;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
public class AnwendungsController {
#FXML
private Button closeButton;
#FXML
void handleCloseButtonAction(ActionEvent event) {
}
#FXML
void onMouseClickedCancelBtn(MouseEvent event) {
}
}
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="140.0" prefWidth="350.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.AnwendungsController">
<children>
<Button fx:id="closeButton" layoutX="18.0" layoutY="100.0" mnemonicParsing="false" onAction="#handleCloseButtonAction" onMouseClicked="#onMouseClickedCancelBtn" prefHeight="26.0" prefWidth="77.0" text="Abbrechen" />
<Label layoutX="10.0" layoutY="27.0" prefHeight="27.0" prefWidth="342.0" text="Sie können das Systema nun verwenden" textAlignment="CENTER" textOverrun="CLIP">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Label>
</children>
</AnchorPane>
I'm currently trying to implements a very basic application with JavaFX just to do some tests. The final goal here is to implement an interface separated in severals parts, and each part will have its own .fxm and controller.
For the beginning, I've tried to develop a basic application with this architecture :
Project explorer
I have a main VueGlobale.fxml file which include another .fxml file clavier.fxml :
VueGlobale.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="529.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
<children>
<fx:include source="clavier.fxml" fx:id="clavier" />
</children>
</VBox>
clavier.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controllers.ClavierController">
<children>
<Button layoutX="274.0" layoutY="188.0" mnemonicParsing="false" onAction="#ajouterDo" text="Button" />
</children>
</AnchorPane>
An here's the ClavierController.fxml :
package controllers;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import model.Model;
public class ClavierController {
private Model model;
public ClavierController(Model m) {
this.model = m;
}
#FXML
public void ajouterDo(ActionEvent e){
System.out.println("Click !");
this.model.doSomething();
}
}
Model
package model;
public class Model {
public void doSomething() {
System.out.println("Model !");
}
}
Main
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("views/VueGlobale.fxml"));
Parent root = loader.load();
primaryStage.setTitle("Test");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The problem is : I don't know what to do in my main so that I can give to my clavier.fxml a ClavierController(Model m). (It's working with a controller without parameters, but what if I need to precise parameters ?)
Here's the StackTrace if it could help you :
Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
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(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.launcher.LauncherHelper$FXHelper.main(Unknown Source)
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$155(LauncherImpl.java:182)
at java.lang.Thread.run(Unknown Source)
Caused by: javafx.fxml.LoadException:
/J:/Programming/Telecom%20Nancy/S3/Test/bin/views/clavier.fxml:6
/J:/Programming/Telecom%20Nancy/S3/Test/bin/views/VueGlobale.fxml:9
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
at javafx.fxml.FXMLLoader.access$700(FXMLLoader.java:103)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:932)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:971)
at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.access$2700(FXMLLoader.java:103)
at javafx.fxml.FXMLLoader$IncludeElement.constructValue(FXMLLoader.java:1143)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:746)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409)
at Main.start(Main.java:14)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(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$148(WinApplication.java:191)
... 1 more
Caused by: java.lang.InstantiationException: controllers.ClavierController
at java.lang.Class.newInstance(Unknown Source)
at sun.reflect.misc.ReflectUtil.newInstance(Unknown Source)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927)
... 23 more
Caused by: java.lang.NoSuchMethodException: controllers.ClavierController.<init>()
at java.lang.Class.getConstructor0(Unknown Source)
... 26 more
Exception running application Main
Thank you in advance for your help and for your time, have a good day.
EDIT :
By including several fxml, i mean this :
VueGlobale.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="529.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
<children>
<fx:include source="viewtop.fxml" fx:id="top" />
</children>
<children>
<fx:include source="clavier.fxml" fx:id="clavier" />
</children>
<children>
<fx:include source="viewbottom.fxml" fx:id="bottom" />
</children>
</VBox>
So I have three .fxml file included in my VueGlobale.fxml. Let us supposed that all this .fxml have their own controller (ClavierController.java, TopController.java, BottomController.java). All these controllers needs the model. What should I do in my main factory ? Something like this doesn't work :
import controllers.ClavierController;
import controllers.TopController;
import controllers.BottomController;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import model.Model;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Model m = new Model();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("views/VueGlobale.fxml"));
loader.setControllerFactory(ic -> new ClavierController(m));
loader.setControllerFactory(ic -> new TopController(m));
loader.setControllerFactory(ic -> new BottomController(m));
Parent root = loader.load();
primaryStage.setTitle("Test");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
ButtonPane.fxml
----------------
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="buttonPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="30.0" prefWidth="700.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.everest.amcu.ButtonPaneController">
<children>
<HBox fx:id="hboxButton" alignment="CENTER_LEFT" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" spacing="5.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<JFXButton fx:id="btnAdd" maxHeight="1.7976931348623157E308" maxWidth="100.0" prefWidth="50.0" text="Add" HBox.hgrow="ALWAYS" />
<JFXButton fx:id="btnEdit" layoutX="10.0" layoutY="10.0" maxHeight="1.7976931348623157E308" maxWidth="100.0" prefWidth="50.0" text="Edit" HBox.hgrow="ALWAYS" />
<JFXButton fx:id="btnDelete" layoutX="62.0" layoutY="10.0" maxHeight="1.7976931348623157E308" maxWidth="100.0" prefWidth="50.0" text="Delete" HBox.hgrow="ALWAYS" />
<JFXButton fx:id="btnClose" layoutX="114.0" layoutY="10.0" maxHeight="1.7976931348623157E308" maxWidth="100.0" prefWidth="50.0" text="Close" HBox.hgrow="ALWAYS" />
</children>
</HBox>
</children>
</AnchorPane>
PaneOne.fxml
---------------
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.everest.amcu.PaneOneController">
<fx:include source="ButtonPane.fxml" fx:id="buttonPane" /> </AnchorPane>
ButtonPaneController
--------------------
import java.net.URL;
import java.util.ResourceBundle;
import com.jfoenix.controls.JFXButton;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
public class ButtonPaneController implements Initializable {
#FXML
public JFXButton btnClose;
#FXML
public JFXButton btnDelete;
#FXML
public JFXButton btnAdd;
#FXML
public JFXButton btnEdit;
#FXML
private AnchorPane buttonPane;
#FXML
private HBox hboxButton;
#Override
public void initialize(URL location, ResourceBundle resources) {
btnClose.setOnAction(event -> {
System.out.println("In Button Pane");
});
btnAdd.setOnAction(event -> {
System.out.println("In Button Pane");
});
}
public void hideButton(JFXButton... jfxButton) {
for (int i = 0; i < jfxButton.length; i++) {
hboxButton.getChildren().remove(jfxButton[i]);
}
}
}
PaneOneController
------------------
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
public class PaneOneController implements Initializable {
#FXML
ButtonPaneController buttonPaneController;
#Override
public void initialize(URL location, ResourceBundle resources) {
buttonPaneController.btnAdd.setOnAction(event -> {
System.out.println("In Pane One");
});
}
}
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
public static Stage primaryStage;
public static Label lblTitle;
#Override
public void start(Stage primaryStage) {
try {
Main.primaryStage = primaryStage;
Parent root = FXMLLoader.load(Main.class.getResource("view/PaneOne.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add(Main.class.getResource("view/css/application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Steps of doing this:
Create ButtonPane.fxml and declare id of anchor pane and all the
button.
Create PaneOne.fxml and include Button.fxml using
,important thing is when you include fxml must be
declare id same as anchor pane id of ButtonPane.fxml.
Create ButtonPaneController for initialize variable.
Create PaneOneController and just declare ButtonPaneController using
#FXML annotation.
Run the program, it's working well with action event of button.
The controllerFactory is a property like any other property. If you set its value, it has that value, so it makes no sense to set its value to three different things in three consecutive lines of code.
You can create a controller factory that simply checks the parameter and returns the appropriate controller:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Model m = new Model();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("views/VueGlobale.fxml"));
loader.setControllerFactory(ic -> {
if (ic == ClavierController.class) {
return new ClavierController(m);
} else if (ic == TopController.class) {
return new TopController(m);
} else if (ic == BottomController.class) {
return new BottomController(m) ;
}
throw new IllegalArgumentException("Unexpected controller type: "+ic.getName());
});
Parent root = loader.load();
primaryStage.setTitle("Test");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You can also use reflection to implement logic like "If the supplied controller class has a constructor taking a model, call that constructor and pass the model in, otherwise call the default constructor":
loader.setControllerFactory(ic -> {
try {
for (Constructor<?> c : ic.getConstructors()) {
if (c.getParameterCount() == 1 && c.getParameterTypes()[0]==Model.class) {
return c.newInstance(m);
}
}
return ic.newInstance();
} catch (Exception e) {
// fatal...
throw new RuntimeException(e);
}
});
This is all a bit heavy-handed for what you are trying to achieve, though. You are merely trying to pass a model to the nested controllers (the controllers for the FXML files loaded via <fx:include>). The documentation explicitly provides an injection-based approach for this.
Specifically, if your "main" FXML adds fx:ids to the <fx:include>s, then the controllers can be injected into the main controller. So you can do something like:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="529.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
<children>
<fx:include source="viewtop.fxml" fx:id="top" />
</children>
<children>
<fx:include source="clavier.fxml" fx:id="clavier" />
</children>
<children>
<fx:include source="viewbottom.fxml" fx:id="bottom" />
</children>
</VBox>
Now define a "main" controller for this FXML file:
public class MainController {
#FXML
private Parent top ;
#FXML
private Parent clavier ;
#FXML
private Parent bottom ;
#FXML
private TopController topController ;
#FXML
private ClavierController clavierController ;
#FXML
private BottomController bottomController ;
private final Model model ;
public MainController(Model model) {
this.model = model ;
}
#FXML
public void initialize() {
topController.setModel(model);
clavierController.setModel(model);
bottomController.setModel(model);
}
// ...
}
Just define the "nested" controllers with the appropriate methods for setting the model:
public class TopController {
private Model model ;
public void setModel(Model model) {
this.model = model ;
}
#FXML
private void someHandlerMethod(ActionEvent event) {
model.doSomething();
}
}
And finally load your main fxml with:
Model model = new Model();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/fxml"));
loader.setController(new MainController(model));
Parent root = loader.load();
and it should all be good to go.
It's about JavaFX. When i want to inject fx:id in Scene Builder, i get this warning: No injectable field found in FXML Controller class for the id 'something'. I wanted to ignore it, and created a function, but it didn't work either. I created mainController class and added it into my FXML file. Here are my codes...
mainController.java
package main;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.Slider;
public class mainController implements Initializable {
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
// TODO Auto-generated method stub
}
#FXML
private ProgressBar pb;
#FXML
private Slider sl;
#FXML
private Label label;
public void changed(ActionEvent event){
}
}
Main.java
package main;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("/fxml/main.fxml"));
Scene scene = new Scene(root, 600, 400);
scene.getStylesheets().add("/fxml/styles/main.css");
ProgressBar pb1 = new ProgressBar();
ProgressBar pb2 = new ProgressBar();
//pb1.
//primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.setTitle("Something");
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="main.mainController">
<children>
<BorderPane layoutX="14.0" layoutY="14.0" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<top>
<ProgressBar fx:id="pb" prefWidth="200.0" progress="0.0" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets bottom="30.0" left="30.0" right="30.0" top="30.0" />
</BorderPane.margin>
</ProgressBar>
</top>
<bottom>
<Slider fx:id="sl" onDragDetected="#changed" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets bottom="30.0" left="30.0" right="30.0" top="30.0" />
</BorderPane.margin>
</Slider>
</bottom>
</BorderPane>
</children>
</AnchorPane>
I did same things in my old projects, and they work like a charm. But this one seems not to obey me. Thanks in advance...
In Scene Builder, if the FXML file is associated to a controller class, you know that what makes the connection between the variable in the controller class (pb) and the object in the FXML file (<ProgressBar ... />) is the value of the object's fx:id.
So when you set an fx:id on an object, Scene Builder tries to parse the controller class trying to find a variable of that name.
If it doesn't find any, it displays this warning. It's just a reminder that you may want to add such a variable to the controller class, or that you have to choose other valid name from a list.
Since you have label defined on your controller, if you try to add a Label on Scene Builder, you can get its fx:id from the list:
But if you assing another name, you will get the warning:
On a side note, you don't need to instantiate the progress bar on the main class, since it will be instantiated in the controller. And ìf you try to link change(ActionEvent event) to a method in Scene Builder (#change), you have to annotate it with #FXML. Anyway, don't use onDragDetected with the slider.
don't forget that Netbeans will Auto-Generate the code in the controller class.
Goto your FXMLDocument.fxml in your Netbeans project, and right click and select: "Make Controller".
Also, remember to save your changes first to your FXMLDocument.fxml, in Scene builder.