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.
Related
#Override
public void start(Stage stage) throws Exception {
BorderPane root = FXMLLoader.load(getClass().getClassLoader().getResource("mainView.fxml"));
When i run this it doesnt show the injected views
Im building a new application with JavaFX for the main page im using 3 views build by 3 fxml files
each view has its controller. for the main page i want to inject the three fxml files in a mainView.fxml via fx:include the mainView.fxml has also a controller how can i do that?
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.Font?>
<fx:root alignment="CENTER_LEFT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="93.0" prefWidth="600.0" style="-fx-min-width: 800; -fx-min-height: 100; -fx-spacing: 30;" type="javafx.scene.layout.HBox" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label fx:id="title" alignment="CENTER" prefHeight="91.0" prefWidth="179.0" style="-fx-label-padding: 20; -fx-line-spacing: 20;" text="News" textFill="#00a4f2">
<font>
<Font name="Arial Black" size="36.0" />
</font>
<opaqueInsets>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</opaqueInsets></Label>
<Button fx:id="refresh" alignment="CENTER" contentDisplay="CENTER" mnemonicParsing="false" style="-fx-alignment: CENTER; -fx-background-color: #66a6ff;" text="Button" textAlignment="CENTER">
<opaqueInsets>
<Insets bottom="30.0" left="30.0" right="30.0" top="30.0" />
</opaqueInsets></Button>
<Button fx:id="stat" alignment="CENTER" mnemonicParsing="false" style="-fx-background-color: #feada6;" text="Button">
<opaqueInsets>
<Insets bottom="30.0" left="30.0" right="30.0" top="30.0" />
</opaqueInsets></Button>
</children>
</fx:root>
This is per example the topView.fxml
package ch.bfh.spacenews;
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
public class TopBarController extends HBox {
#FXML
public Label title;
#FXML
public Button refresh;
#FXML
public Button stat;
TopBarController(){
FXMLLoader load = new FXMLLoader(getClass().getClassLoader().getResource("topView.fxml"));
load.setRoot(this);
load.setController(this);
try {
System.out.println("TopBarController");
load.load();
}catch(IOException e) {
e.printStackTrace();
}
}
}
This is the corresponding controller of the topView.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<BorderPane fx:controller="ch.bfh.spacenews.mainController" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<top>
<fx:include fx:id="topBar" source="topView.fxml"/>
</top>
<center>
<fx:include fx:id="article" source="sample.fxml"/>
</center>
<right>
<fx:include fx:id="seacrh" source="searchView.fxml"/>
</right>
</BorderPane>
This is where i want to inject the topView.fxml
package ch.bfh.spacenews;
import javafx.fxml.FXML;
public class mainController {
#FXML
TopBarController topBarController;
#FXML
ArticleController articleController;
#FXML
SearchController searchController;
#FXML
public void initialize() {
}
}
And this is the Controller of the mainView.fxml where i want to inject the topView.fxml
In the FXML custom component pattern the controller classes also serve as the wrapper for the view, by subclassing an appropriate Node subclass. This means you can just instantiate them directly in the FXML via the FXMLLoader. For example, the <TopBarController> element instructs the FXMLLoader to instantiate the TopBarController by calling its no-argument constructor. That constructor, as per your code, loads topView.fxml, etc. There is no need to use <fx:include>, which is an instruction for the FXMLLoader to load another FXML file, since you already have code in your TopBarController to do that.
So your main view FXML file should look like:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import ch.bfh.spacenews.TopBarController?>
<!-- other imports... -->
<BorderPane fx:controller="ch.bfh.spacenews.MainController" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<top>
<TopBarController fx:id="topBar" />
</top>
<center>
<ArticleController fx:id="article" />
</center>
<right>
<SearchController fx:id="search" />
</right>
</BorderPane>
And the corresponding controller is
package ch.bfh.spacenews;
import javafx.fxml.FXML;
public class MainController {
#FXML
TopBarController topBar;
#FXML
ArticleController article;
#FXML
SearchController search;
#FXML
public void initialize() {
}
}
Note that it's really critical to follow standard naming conventions here. The FXMLLoader explicitly relies on the case of an element to determine if it's referring to a class name or a property name.
Main class : Launch.java
public class Launch extends Application {
#Override
public void start(Stage stage) throws Exception {
Controller.show(stage);
}
public static void main(String[] args) {
launch(args);
}
}
You can add view from controller :
public class Controller {
public BorderPane mainPane; // fx:id of your pane
public static Stage current;
public static void show(Stage stage) {
this.current = stage;
Scene scene = null;
BorderPane pane = null;
try {
scene = new Scene(FXMLLoader.load(getClass().getResource("mainView.fxml")));
pane = (BorderPane) scene.lookup("#mainPane");
// Add .fxml view to mainView
pane.setCenter(FXMLLoader.load(getClass().getResource("sample.fxml")));
pane.setTop(FXMLLoader.load(getClass().getResource("topView.fxml")));
pane.setRight(FXMLLoader.load(getClass().getResource("searchView.fxml")));
} catch (IOException e) {
e.printStackTrace();
}
stage.setTitle("MainView");
stage.setScene(scene);
stage.show();
}
}
I have a problem with getting to the Controller.
I have a main template with two parts: Menu and Main Content
In two separate FXML files I have the Menu (MenuTemplate.fxml) and Main Content (Content1Template.fxml).
The loading of the Menu and Main Content proceeds correctly
The problem appears when Main Content wants to change the text after pressing the button.
Content1Utils.java has to set text in Content1Controller.java.
I guess I have to get to the controller. I just have a problem how to do it.
Link >> My Project in rar
Luncher.java
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import sample.main.MainController;
import java.io.File;
import java.net.URL;
public class Launcher extends Application {
private static MainController mainController;
#Override
public void start(Stage primaryStage) throws Exception{
URL mainTemplate = new File("src/sample/main/MainTemplate.fxml").toURI().toURL();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(mainTemplate);
Parent root = loader.load();
mainController = loader.getController();
Scene scene = new Scene(root);
primaryStage.setTitle("Hello World");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public MainController getMainController(){
return mainController;
}
}
MainController.java
package sample.main;
import com.jfoenix.controls.JFXDrawer;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.VBox;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class MainController {
#FXML private VBox rootBox;
#FXML private JFXDrawer menuBox;
#FXML private VBox contentBox;
#FXML
void initialize() throws IOException {
URL menuTemplate = new File("src/sample/menu/MenuTemplate.fxml").toURI().toURL();
VBox contentMenu = FXMLLoader.load(menuTemplate);
menuBox.setContent(contentMenu);
}
public void changeContentBox(VBox content){
contentBox.getChildren().setAll(content);
}
}
MenuController.java
package sample.menu;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import sample.Launcher;
import sample.main.MainController;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class MenuController {
#FXML private VBox menuRoot;
#FXML private Button menuButton1;
#FXML private Button menuButton2;
#FXML private Button menuButton3;
private MainController mainController;
#FXML
void initialize(){
}
public void clickButton1() throws IOException {
URL content1Template = new File("src/sample/content1/Content1Template.fxml").toURI().toURL();
VBox contentContent1 = FXMLLoader.load(content1Template);
mainController = new Launcher().getMainController();
mainController.changeContentBox(contentContent1);
}
}
Content1Controller.java
package sample.content1;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
public class Content1Controller {
#FXML private VBox content1root;
#FXML private Button content1Button;
#FXML private TextArea content1TextArea;
#FXML
void initialize(){
}
public void content1Button(){
Content1Utils contentUtils = new Content1Utils();
contentUtils.prepareText("XXX");
}
public void setTextInArea(String txt){
content1TextArea.setText(txt);
}
}
Content1Utils.java
package sample.content1;
public class Content1Utils {
public void prepareText(String txt){
String newTxt = txt + " - Test123";
new Content1Controller().setTextInArea(newTxt);
}
}
MainTemplate.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXDrawer?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox fx:id="rootBox" prefHeight="442.0" prefWidth="665.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.main.MainController">
<children>
<HBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" VBox.vgrow="ALWAYS">
<children>
<JFXDrawer fx:id="menuBox" style="-fx-background-color: #ebd534;" />
<VBox fx:id="contentBox" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-background-color: #34b7eb;" HBox.hgrow="ALWAYS" />
</children>
</HBox>
</children>
</VBox>
MenuTemplate.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<VBox fx:id="menuRoot" prefHeight="442.0" prefWidth="100.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.menu.MenuController">
<children>
<Button fx:id="menuButton1" maxHeight="100.0" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#clickButton1" text="Button 1" VBox.vgrow="ALWAYS" />
<Button fx:id="menuButton2" maxHeight="100.0" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Button 2" VBox.vgrow="ALWAYS" />
<Button fx:id="menuButton3" maxHeight="100.0" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Button3" VBox.vgrow="ALWAYS" />
</children>
</VBox>
Content1Template.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.VBox?>
<VBox fx:id="content1root" alignment="CENTER" prefHeight="442.0" prefWidth="565.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.content1.Content1Controller">
<children>
<Button fx:id="content1Button" contentDisplay="CENTER" mnemonicParsing="false" onAction="#content1Button" prefHeight="48.0" prefWidth="400.0" text="Button" textAlignment="CENTER">
<VBox.margin>
<Insets />
</VBox.margin>
</Button>
<TextArea fx:id="content1TextArea" maxWidth="400.0" prefHeight="200.0" prefWidth="400.0">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
</TextArea>
</children>
</VBox>
ERROR
Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1774)
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1657)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:394)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$353(GlassViewEventHandler.java:432)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:431)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:937)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
at java.lang.Thread.run(Thread.java:748)
Caused by: 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 sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1771)
... 48 more
Caused by: java.lang.NullPointerException
at sample.content1.Content1Controller.setTextInArea(Content1Controller.java:25)
at sample.content1.Content1Utils.prepareText(Content1Utils.java:8)
at sample.content1.Content1Controller.content1Button(Content1Controller.java:21)
... 58 more
The problem is here:
public class Content1Utils {
public void prepareText(String txt){
String newTxt = txt + " - Test123";
new Content1Controller().setTextInArea(newTxt);
}
}
You are making a new controller instance that is not initialized from the FXML so it doesn't have a valid TextArea field. You don't want to have your utility class work this way.
Try:
public void content1Button(){
Content1Utils contentUtils = new Content1Utils(this);
contentUtils.prepareText("XXX");
}
Content1Utils.java:
package sample.content1;
public class Content1Utils {
private final Content1Controller controller;
public Content1utils(Content1Controller ctrl) {
this.controller = ctrl;
}
public void prepareText(String txt){
String newTxt = txt + " - Test123";
controller.setTextInArea(newTxt);
}
}
if i run the Main Class my JavaFX is not opening. A Java symbol appears in my menubar but no window appears. I don't get any error in the console. I am on a Mac machine and I am using eclipse, together with e(fx)clipes and Gluon Scene Builder. The code is actually 1 by 1 written down from a tutorial. In the tutorial the run configuration JRE is set to JavaSE-1.8. - however, I tested every available JRE, without any success. What do I need to adjust?
Main Class:
package application;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
public class Main extends Application {
private Stage primaryStage;
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
}
public void mainWindow() {
try {
FXMLLoader loader = new FXMLLoader(Main.class.getResource("MainWindow.fxml"));
AnchorPane pane = loader.load();
primaryStage.setMinHeight(400.00);
primaryStage.setMinWidth(500.00);
MainWindowController mainWindowController = loader.getController();
mainWindowController.setMain(this);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Controller class:
package application;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
public class MainWindowController {
public Main main;
#FXML private Label label;
#FXML private TextField field;
#FXML private Button clear;
#FXML private Button changeText;
public void setMain(Main main) {
// TODO Auto-generated method stub
this.main = main;
}
#FXML
public void handleChangeText() {
String text = field.getText();
label.setText(text);
}
#FXML
public void handleClear() {
field.clear();
}
}
FXML file:
<?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.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="400.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainWindowController">
<children>
<VBox alignment="CENTER" layoutY="83.0" spacing="30.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label fx:id="label" text="Label">
<font>
<Font size="24.0" />
</font></Label>
<HBox alignment="CENTER">
<children>
<TextField fx:id="field" prefWidth="220.0" />
</children>
</HBox>
<HBox alignment="CENTER" spacing="20.0">
<children>
<Button fx:id="changeText" mnemonicParsing="false" onAction="#handleChangeText" text="Change Text" />
<Button fx:id="clear" mnemonicParsing="false" onAction="#handleClear" text="Clear" />
</children>
</HBox>
</children>
</VBox>
</children>
</AnchorPane>
You didn't do anything in you start method. All you did was set your primaryStage variable, you should call your mainWindow method after that.
In my application I have declared a custom component like this:
#DefaultProperty("todoItems")
public class TodoItemsVBox extends VBox {
private ObservableList<TodoItem> todoItems;
// Setter/Getter omitted
}
and now somewhere in the fxml I want to use the TodoItemsVBox component like this:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<BorderPane prefHeight="600" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.todolist.controller.TodoListController"
stylesheets="#../css/app.css">
<top>
<HBox spacing="10.0">
<TextField fx:id="input" layoutX="35.0" layoutY="64.0" prefWidth="431.0" promptText="Enter todo task" HBox.hgrow="ALWAYS" onAction="#addTask"/>
<Button layoutX="216.0" layoutY="107.0" mnemonicParsing="false" onAction="#addTask" prefHeight="27.0" prefWidth="70.0" text="Add" HBox.hgrow="ALWAYS" />
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</HBox>
</top>
<center>
<ScrollPane fitToHeight="true" fitToWidth="true" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${todoTasks}"/>
</ScrollPane>
</center>
... so as we can see the fxml has it's controller TodoListController
public class TodoListController implements {
private final ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList(/*Fill in the collection somehow - for now doesn't matter*/);
#FXML
private TodoItemsVBox todoItemsVBox;
// Setter/Getter omitted
}
So, here what I want to do: pass todoTasks into TodoItemsVBox defined in the FXML via such construction: todoItems="${todoTasks}" ---- unfortunately this doesn't work as I expected, because fxml files load before controllers initialised so todoTasks is always null. I also tried #NamedArg with one arg constructor in TodoItemsVBox - it even fails with exception: "Cannot bind to untyped object.”.
Could some one suggest a solution how to pass a collection of objects, defined in a controller, into a custom component via it's parameters?
There are two issues with the code as you have it:
For FXML expression binding, you need to expose properties from your class, not just the values themselves. This applies to ObservableLists as well as regular values. So your TodoItemsVBox class needs to expose a ListProperty todoItemsProperty()
FXML expression bindings (i.e. ${todoTasks}) reference the FXMLLoader's namespace, not the controller. The controller is automatically injected into the namespace (with key "controller"), so, given that the task list is stored in your controller (which is not necessarily a good idea) you can use ${controller.todoTasks} here.
Here's a minimal, complete version of your app which works.
A basic TodoItem.java:
public class TodoItem {
private final String name ;
public TodoItem(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
}
A TodoItemsVBox that exposes the list as a property:
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
public class TodoItemsVBox extends VBox {
private ListProperty<TodoItem> todoItems = new SimpleListProperty<>();
public TodoItemsVBox() {
// not efficient but works for demo:
todoItems.addListener((Change<? extends TodoItem> c) -> rebuildView());
}
private void rebuildView() {
getChildren().clear();
todoItems.stream()
.map(TodoItem::getName)
.map(Label::new)
.forEach(getChildren()::add);
}
public ListProperty<TodoItem> todoItemsProperty() {
return todoItems ;
}
public ObservableList<TodoItem> getTodoItems() {
return todoItemsProperty().get() ;
}
}
A simple controller:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class TodoListController {
private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();
// not actually needed...
#FXML
private TodoItemsVBox todoItemsVBox;
#FXML
private TextField input ;
public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}
#FXML
private void addTask() {
todoTasks.add(new TodoItem(input.getText()));
}
}
The FXML file (TodoList.fxml):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import org.jamesd.examples.TodoItemsVBox ?>
<BorderPane prefHeight="600" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.todolist.controller.TodoListController"
>
<top>
<HBox spacing="10.0">
<TextField fx:id="input" promptText="Enter todo task" HBox.hgrow="ALWAYS" onAction="#addTask"/>
<Button onAction="#addTask" text="Add" HBox.hgrow="ALWAYS" />
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</HBox>
</top>
<center>
<ScrollPane fitToWidth="true" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${controller.todoTasks}"/>
</ScrollPane>
</center>
</BorderPane>
And finally the application class:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class TodoApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("TodoList.fxml"));
Scene scene = new Scene(loader.load());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
Really, a controller is no place to be storing data; you should have a separate model class to do that, which is shared between the controller and view. This is reasonably straightforward to do here; you just need to do a little more work with the FXMLLoader (namely putting the model in the namespace, and manually creating and setting the controller).
For example:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class TodoModel {
private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();
public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}
}
Then your controller becomes:
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class TodoListController {
// not actually needed...
#FXML
private TodoItemsVBox todoItemsVBox;
#FXML
private TextField input ;
private TodoModel model ;
public TodoModel getModel() {
return model;
}
public void setModel(TodoModel model) {
this.model = model;
}
#FXML
private void addTask() {
model.getTodoTasks().add(new TodoItem(input.getText()));
}
}
Modify the FXML to use
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${model.todoTasks}"/>
And finally assemble the application with
public void start(Stage primaryStage) throws Exception {
TodoModel model = new TodoModel();
FXMLLoader loader = new FXMLLoader(getClass().getResource("TodoList.fxml"));
loader.getNamespace().put("model", model);
Scene scene = new Scene(loader.load());
TodoListController controller = loader.getController();
controller.setModel(model);
primaryStage.setScene(scene);
primaryStage.show();
}
The advantage to this approach is that your data are now separated from the UI (both view and controller), which becomes essential if you want to access the same data in another part of the UI (which would use another FXML and another controller).
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 :-)