Bug in JavaFX by binding with middle man onto slider value property? - javafx

I stumbled upon a problem using a slider in JavaFX.
I create a fxml-file with a Slider and add a controller to it.
Inside the controller I have a DoubleProperty, which binds to the Slider's valueProperty. Then, when I want to bind to this property from somewhere else, I bind to the property, which is inside the controller (some kind of a middle-man approach see Figure).
But when I do so, it does not work properly.
When I use the slider, the values get updated accordingly for a while, but when I wiggle it around, at some point it seems to stop updating the binding and refuses to do so again even after releasing and pressing it again.
When I delete the middle-man property in the controller and just pipe through the valueProperty from the slider directly, it is working.
Program:
Main.java
public class Main extends Application{
private MainController controller;
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("main.fxml"));
Parent root = loader.load();
controller = loader.getController();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
showSlider();
}
private void showSlider() {
SliderShower sliderShower = new SliderShower();
sliderShower.show();
sliderShower.getSliderValueProp().addListener(((observable, oldValue, newValue) -> {
controller.setText(Double.toString((double)newValue));
}));
}
public static void main(String[] args) {
launch(args);
}
}
main.fxml
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane prefHeight="100.0" prefWidth="100.0" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sliderBug.main.MainController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label fx:id="label" text="Label" />
</children>
</GridPane>
MainController.java
public class MainController {
#FXML
private Label label;
public void setText(String text) {
label.setText(text);
}
}
SliderShower.java
public class SliderShower {
private Parent root;
private SliderShowerController controller;
private Stage stage;
private DoubleProperty sliderValueProp;
public SliderShower() {
sliderValueProp = new SimpleDoubleProperty(0);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("sliderShower.fxml"));
try {
root = loader.load();
controller = loader.getController();
stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setScene(new Scene(root));
sliderValueProp.bind(controller.getSliderValueProp());
} catch (IOException e) {
e.printStackTrace();
}
}
public DoubleProperty getSliderValueProp() {
return sliderValueProp; // This does not work
// return controller.getSliderValueProp(); // This would work
}
public void show() {
stage.show();
}
}
sliderShowerController.java
public class SliderShowerController {
#FXML
private Slider sliderUI;
DoubleProperty getSliderValueProp() {
return sliderUI.valueProperty();
}
}
sliderShower.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane prefHeight="100.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sliderBug.sliderShower.SliderShowerController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Slider fx:id="sliderUI" max="200.0" min="1.0" />
</children>
</GridPane>
Here is a link to a repository depicting the problem:
https://github.com/Chrisss50/sliderBug
Am I doing something wrong or is this just a bug?
Greetings

Bindings in JavaFX use weak listeners under the hood. This is intended to prevent memory leaks, but has the side effect that if the properties to which something is bound go out of scope, the binding will not prevent them being garbage collected and will cease to work if they are. See this question and this blog post.
You can verify this is the issue by adding
root.setOnMouseClicked(e -> {
if (e.getClickCount() == 2) {
System.out.println("GC");
System.gc();
}
});
to the SliderShower constructor. After doing that, double-clicking near the slider will force garbage collection, and the binding will immediately cease to work.
In your case, Main instantiates SliderShower as a local variable in the showSlider method, and consequently it goes out of scope as soon as the method completes. One (somewhat unnatural) fix is to forcibly retain a reference to the SliderShower:
package sliderBug.main;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import sliderBug.sliderShower.SliderShower;
/**
* Created by Christopher Juerges on 16/02/17.
*/
public class Main extends Application{
private MainController controller;
private SliderShower sliderShower ;
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("main.fxml"));
Parent root = loader.load();
controller = loader.getController();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
showSlider();
}
private void showSlider() {
sliderShower = new SliderShower();
sliderShower.show();
sliderShower.getSliderValueProp().addListener(((observable, oldValue, newValue) -> {
controller.setText(Double.toString((double)newValue));
}));
}
public static void main(String[] args) {
launch(args);
}
}
Another fix is to use a listener instead of the binding:
public SliderShower() {
sliderValueProp = new SimpleDoubleProperty(0);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("sliderShower.fxml"));
try {
root = loader.load();
root.setOnMouseClicked(e -> {
if (e.getClickCount() == 2) {
System.out.println("GC");
System.gc();
}
});
controller = loader.getController();
stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setScene(new Scene(root));
// sliderValueProp.bind(controller.getSliderValueProp());
controller.getSliderValueProp().addListener((obs, oldValue, newValue) ->
sliderValueProp.set(newValue.doubleValue()));
} catch (IOException e) {
e.printStackTrace();
}
}

Related

a scene get back previous scene after Specified second javafx

i have scene 1 and and wrote below code to this scene 1 goes to another scene 2:
main class:
public class ProjectSeventh extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/FXMLfiles/FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
sceneOne Controlled code:
public class FXMLDocumentController implements Initializable {
#FXML Label itemCouner;
public void frize(Event event) throws IOException {
FXMLLoader watingLoader = new FXMLLoader();
watingLoader.setLocation(getClass().getResource("/FXMLfiles/wating.fxml"));
Parent watingParent = watingLoader.load();
Scene watingScene = new Scene(watingParent);
Stage watingStage = (Stage) ((Node) event.getSource()).getScene().getWindow();
watingStage.setScene(watingScene);
watingStage.show();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
Random myRand = new Random();
int holdingFrizes = myRand.nextInt(2);
}
}
i want to when scene 2 comes up, automaticly after 6 seconds scene 2 goes back to scene 1 without any event! but this switch scene protocol i learn needs an event at "(Node) event.getSource()" what should i do?
scene2 controller code:
public class WatingController implements Initializable {
public void getBackPreviousScene() throws IOException{
PauseTransition myPouse = new PauseTransition();
myPouse.setDuration(javafx.util.Duration.seconds(6));
myPouse.setOnFinished((event) -> {
try {
FXMLLoader shopingLoader = new FXMLLoader();
shopingLoader.setLocation(getClass().getResource("/FXMLfiles/FXMLDocument.fxml"));
Parent shopingParent = shopingLoader.load();
Scene shopingScene = new Scene(shopingParent);
Stage shopingStage = (Stage) ((Node) event.getSource()).getScene().getWindow();
shopingStage.setScene(shopingScene);
shopingStage.show();
} catch (IOException ex) {
}
});
myPouse.play();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
try {
getBackPreviousScene();
} catch (IOException ex) {
}
}
}
Your code generates a ClassCastException in the onFinished handler defined in getBackPreviousScene(), because the source of the event is the PauseTransition, which is not a Node. It doesn't have a Scene or access to one.
First, using the source of the event in general is not a very good way to access the scene. Instead, you can simply call getScene() on any of the injected nodes in the controller. E.g.:
public class FXMLDocumentController implements Initializable {
#FXML Label itemCouner;
public void frize(Event event) throws IOException {
FXMLLoader watingLoader = new FXMLLoader();
watingLoader.setLocation(getClass().getResource("/FXMLfiles/wating.fxml"));
Parent watingParent = watingLoader.load();
Scene watingScene = new Scene(watingParent);
Stage watingStage = (Stage)itemCouner.getScene().getWindow();
watingStage.setScene(watingScene);
// watingStage.show();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
Random myRand = new Random();
int holdingFrizes = myRand.nextInt(2);
}
}
Note also that if you're getting the window containing a scene containing a node on which an event occurred, that window must necessarily already be showing, so there's no point at all in calling
watingStage.show();
in the code above. You could do the same in your WatingController by injecting some node into the controller.
Second, the WatingController seems to be the wrong place to control how long the UI declared in the FXML is displayed. This should be the responsibility of the code that displays it, which in this case is the frize() method in the FXMLDocumentController class. In particular, this makes it impossible to load the FXML ahead of time and display it later, because after six seconds calling getScene().getWindow() would throw a null pointer exception.
So, e.g., you could do:
public class FXMLDocumentController implements Initializable {
#FXML Label itemCouner;
public void frize(Event event) throws IOException {
FXMLLoader watingLoader = new FXMLLoader();
watingLoader.setLocation(getClass().getResource("/FXMLfiles/wating.fxml"));
Parent watingParent = watingLoader.load();
Scene watingScene = new Scene(watingParent);
Scene currentScene = itemCouner.getScene();
Stage stage = (Stage) currentScene.getWindow();
stage.setScene(watingScene);
PauseTransition pause = new PauseTransition(Duration.seconds(6));
pause.setOnFinished(e -> stage.setScene(currentScene));
pause.play();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
And then the code in your WatingController can all be removed.
Finally, note that in your example there seems to be no need at all to create new scenes. Just replace the root of the existing scene:
public class FXMLDocumentController implements Initializable {
#FXML Label itemCouner;
public void frize(Event event) throws IOException {
FXMLLoader watingLoader = new FXMLLoader();
watingLoader.setLocation(getClass().getResource("/FXMLfiles/wating.fxml"));
Parent watingParent = watingLoader.load();
Scene currentScene = itemCouner.getScene();
Parent currentRoot = currentScene.getRoot();
currentScene.setRoot(watingParent);
PauseTransition pause = new PauseTransition(Duration.seconds(6));
pause.setOnFinished(e -> currentScene.setRoot(currentRoot));
pause.play();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
Complete example:
primary.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.jamesd.examples.tempscene.PrimaryController">
<children>
<Button fx:id="showTempButton" text="Show Temporary View" onAction="#showTemp"/>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</VBox>
PrimaryController.java:
package org.jamesd.examples.tempscene;
import java.io.IOException;
import javafx.animation.PauseTransition;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.util.Duration;
public class PrimaryController {
#FXML
private Button showTempButton ;
#FXML
private void showTemp() throws IOException {
Scene currentScene = showTempButton.getScene();
Parent currentRoot = currentScene.getRoot();
FXMLLoader loader = new FXMLLoader(getClass().getResource("temp.fxml"));
Parent tempRoot = loader.load();
currentScene.setRoot(tempRoot);
PauseTransition pause = new PauseTransition(Duration.seconds(2));
pause.setOnFinished(e -> currentScene.setRoot(currentRoot));
pause.play();
}
}
temp.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" >
<children>
<Label text="Temporary View" />
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</VBox>
App.java:
package org.jamesd.examples.tempscene;
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 {
private static Scene scene;
#Override
public void start(Stage stage) throws IOException {
scene = new Scene(FXMLLoader.load(getClass().getResource("primary.fxml")), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}

after restore from minimize WebView become disabled in javafx

My problem is when I am restoring minimized javafx application from windows taskbar WebView become disabled.
After resize stage it becomes enabled.
Main java Code:
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
AnchorPane root = new AnchorPane();
FXMLLoader loader = new FXMLLoader(getClass().getResource("Browser.fxml"));
root = loader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Shivam Jewels ERP");
primaryStage.setMaximized(true);
primaryStage.show();
primaryStage.iconifiedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
if (t1) {
System.out.println("minimized:");
}
}
});
primaryStage.maximizedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
System.out.println("maximized:");
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
My JavaFX Code Is:
public class BrowserController implements Initializable {
#FXML
private WebView browser;
#FXML
private GridPane grdPn;
#Override
public void initialize(URL location, ResourceBundle resources) {
final WebEngine webEngine = browser.getEngine();
browser.getEngine().setUserStyleSheetLocation(getClass().getResource("application.css").toExternalForm());
browser.setContextMenuEnabled(false);
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
webEngine.load("http://192.168.2.6:4200");
}
}
This is my CSS Code:
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
-webkit-border-radius: 10px;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
-webkit-border-radius: 10px;
border-radius: 10px;
background: rgba(0,0,0,0.5);
}
::-webkit-scrollbar-thumb:window-inactive {
background: rgba(0,0,0,0.1);
}
This is my FXML Code:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.web.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="382.0" prefWidth="353.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.BrowserController">
<children>
<GridPane fx:id="grdPn" layoutX="57.0" layoutY="135.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" />
</columnConstraints>
<rowConstraints>
<RowConstraints fillHeight="false" valignment="CENTER" vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
</rowConstraints>
<children>
<WebView fx:id="browser" minHeight="-1.0" minWidth="-1.0" prefHeight="-1.0" prefWidth="-1.0" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.valignment="CENTER" GridPane.vgrow="ALWAYS" />
</children>
</GridPane>
</children>`enter code here`
</AnchorPane>
This is actually a JDK bug reported multiple times here, here and here. The behavior you described happens when satge is maximized.
This bug was scheduled to be fixed in JDK9 but according to this is also going to be fixed in JDK8u122:
Approved to backport to 8u-dev for 8u122.
JDK 8u122 is planned to be released in January 2017 so it won't be long before we can use it but for now you can download and use early access release from JDK 8u122 early access page.
I myself downloaded it and tested the code and fortunately problem no longer exists.

How to get a reference to the nodes described via fx:id in javafx?

I have a reduced example of my problem where the code references from fx:id are not null when the initialize is called but then go to null right after the function call. What is the correct way to get such references? This is sample.fxml
<GridPane alignment="center" hgap="10" vgap="10" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="sample.Main">
<columnConstraints>
<ColumnConstraints />
</columnConstraints>
<rowConstraints>
<RowConstraints />
</rowConstraints>
<children>
<Text fx:id="textRef" strokeType="OUTSIDE" strokeWidth="0.0" text="Hello world" />
</children>
</GridPane>
And this is the Main.java which is declared as its controller.
public class Main extends Application implements Initializable{
#FXML
public Text textRef;
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
this.someNewFunction();
}
private void someNewFunction() {
this.textRef.setText("Changed in somNewFunction");
}
public static void main(String[] args) {
launch(args);
}
#Override
public void initialize(URL location, ResourceBundle resources) {
this.textRef.setText("Changed at initialize");
}
}
The text ref is valid inside the initialize call but throws a nullpointerexception when inside the someNewFunction.
The Main instance that is launched is a different object than the Main instance created by the FXMLLoader to be used as controller.
IMHO it would be better to get the controller from the FXMLLoader after loading the fxml and also use a class different to the Application as controller:
public class MainController implements Initializable {
...
}
<GridPane alignment="center" hgap="10" vgap="10" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="sample.MainController">
...
</GridPane>
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root = loader.load();
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
MainController controller = loader.getController();
controller.someNewFunction();
}
However you could also specify the controller that should be used with the fxml:
<GridPane alignment="center" hgap="10" vgap="10" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8">
...
</GridPane>
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
loader.setController(this);
Parent root = loader.load();
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
this.someNewFunction();
}
You are executing the someNewFunction outside the javafx loop.
I mean, after the show method ends, the main thread takes control and the javafx components no longer exists (they are destroyed as is supposed that they are no longer needed).
You have to bind the someNewFunction to some event attached to some FXML element reference (i.e. a Button via the setOnAction on SceneBuilder, or via code), place the method call inside the initialize method, or simply move the call before the primaryStage.show() line.
Hopes this helps

Scene change vs Pane change

I'm relatively new to Java and espacially JavaFX. I'm trying to make a menu, which switches the displayed content on buttonclick. I've done this now by clearing the Pane and asigning a new fxml-file to it.
This is one method from my Controller:
protected void CustomStart(ActionEvent event) {
content.getChildren().clear();
try {
content.getChildren().add(
(Node) FXMLLoader.load(getClass().getResource(
"/view/CustomStartStructure.fxml")));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
It works just fine so far but I wuld like to to it by changing the scenes as well.
I want to initiate the scenes whit a fxml-file in the Constructor. It works within another method. But if I try to initiate it in the constructor I get an InvocationTargetException caused by a RuntimeException caused by a StackOverflow error. If I do it in the other method, I get a NullPointerException when I try to change the Scene.
This is the constructor
public Game() throws IOException {
this.MainMenu = new Scene((GridPane) FXMLLoader.load(getClass()
.getResource("/view/MainMenuStructure.fxml")), 400, 400);
this.stage = new Stage();
this.stage.setScene(MainMenu);
}
This is the method in whicht the invocation works:
public void run() throws Exception {
/**
* Set the Scenes for the different menus by using the panels from the
* fxml-files
*/
this.MainMenu = new Scene((GridPane) FXMLLoader.load(getClass()
.getResource("/view/MainMenuStructure.fxml")), 400, 400);
MainMenu.getStylesheets().add(
getClass().getResource("/view/MainMenuDesign.css")
.toExternalForm());
this.SingleRaceMenu = new Scene((GridPane) FXMLLoader.load(getClass()
.getResource("/view/CustomStartStructure.fxml")), 400, 400);
/** Giving the Stage a Scene */
this.setStage(new Stage());
this.stage.setScene(MainMenu);
this.stage.show();
}
This is the Buttoncontroller:
protected void CustomStart(ActionEvent event) {
this.getStage().setScene(getSingleRaceMenu());
}
I hope you can give me an advice!
Here is a simple example which has two fxml files, both loaded into separate scenes and the scenes are set to the same Stage.
Controller is defined for only scene1.fxml, since this is a basic example of how you can change scene using a button event on a controller.
The important part in the example is to see how I fetch the current stage reference using the button reference, which is already a part of the scene graph :
((Stage)button.getScene().getWindow())
If you want to learn about how to switch scenes, and go back to previous scene you can go implement the following example, by loading the fxml's in their respective scene :
Loading new fxml in the same scene
Example
scene1.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" spacing="10.0" style="-fx-background-color: goldenrod;" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller">
<children>
<Label text="Scene 1" />
<Button fx:id="button" mnemonicParsing="false" onAction="#changeScene" text="Change Scene" />
</children>
</VBox>
scene2.fxml
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" spacing="10.0" style="-fx-background-color: cyan;" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label text="You have switched to Scene 2" />
</children>
</VBox>
Scene1 Controller
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.stage.Stage;
import java.io.IOException;
public class Controller {
#FXML
private Button button;
#FXML
public void initialize() {
}
#FXML
private void changeScene(ActionEvent event) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/scene2.fxml"));
Parent parent = loader.load();
((Stage)button.getScene().getWindow()).setScene(new Scene(parent, 200, 200));
} catch (IOException eox) {
eox.printStackTrace();
}
}
}
Main
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
FXMLLoader fxmlloader = new FXMLLoader(Main.class.getResource("/scene1.fxml"));
VBox root = fxmlloader.load();
Scene scene = new Scene(root, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Output
On the matter of Scene change vs Pane change:
Since the Scene change closes and opens a new window, if you are on full screen, I ruled it out for my purpose.
Instead I allways load a new Parent into my scene which is smooth and quick.
Since I use FXML the only difference between the scenes is in fact the parent given by an FXML file. So it is sufficient for me to stick with different Parents.
Here is a snippet of my Controller Class:
public class GameController {
private Parent mainMenu;
private Stage stage;
private Scene scene;
/** Constructor which receives a Stage */
public GameController(Stage stage) {
this.stage = stage;
}
public void start() {
/** Initialize the MainMenu */
initializeMenu(mainMenu, "/view/MainMenuStructure.fxml");
this.setScene(new Scene(mainMenu));
stage.setScene(scene);
stage.setFullScreen(true);
stage.setFullScreenExitHint("");
stage.show();
}
#FXML
private void MainMenu(ActionEvent event) {
setRoot(mainMenu);
}
/** Initialize the menus and the in game screen */
private void initializeMenu(Parent parent, String path) {
FXMLLoader loader = new FXMLLoader(getClass().getResource(path));
loader.setController(this);
if (parent == mainMenu) {
try {
this.setMainMenu(loader.load());
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void setRoot(Parent parent) {
this.getStage().getScene().setRoot(parent);
}
}
I'm very confortable with my solution. But since I'm relatively new the Java and Javafx I hope this is helps a little and is not quick and dirty.
Thanks for the comments whicht actually helped a lot!

Java FX Getting a custom control working using multiple FXML files

I am trying to work with custom controls in JavaFX and multiple FXML files. I create a custom control:
public class PetListGUIManager extends BorderPane
{
#FXML ListView<String> petList;
private PetManager pm;
public PetListGUIManager()
{
try
{
FXMLLoader loader = new FXMLLoader( getClass().getResource("PetList.fxml"));
loader.setRoot( this );
loader.setController( this );
loader.load();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
pm = new PetManager();
ObservableList<String> items = FXCollections.observableArrayList( pm.getDisplayablePets() );
petList.setItems( items );
}
#FXML
public void handleButtonAction( ActionEvent event )
{
Button b = (Button) event.getSource();
b.setText("Clicked!");
}
}
Using this FXML file:
<fx:root type="javafx.scene.layout.BorderPane" xmlns:fx="http://javafx.com/fxml">
<bottom>
<Button onAction="#handleButtonAction" text="Click Me!" />
</bottom>
<center>
<ListView fx:id="petList" prefHeight="200.0" prefWidth="200.0" />
</center>
</fx:root>
Then I use this other main program:
public class TestMain extends Application
{
PetListGUIManager pm;
#FXML FlowPane mainPane;
#Override
public void start(Stage stage) throws Exception
{
Parent root = FXMLLoader
.load(getClass().getResource("Main.fxml"));
Scene scene = new Scene(root);
stage.setTitle("Test Main");
stage.setScene(scene);
stage.show();
pm = new PetListGUIManager();
}
/**
* Purpose:
* #param args
*/
public static void main(String[] args)
{
Application.launch( args );
}
#FXML
public void doSomething( ActionEvent ae )
{
System.out.println( "In do something: " + mainPane );
ObservableList<Node> children = mainPane.getChildren( );
System.out.println( "Children are " + children );
children.add( pm );
}
}
Which uses this FXML file:
<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml" fx:controller="TestMain">
<children>
<FlowPane fx:id="mainPane" prefHeight="400.0" prefWidth="600.0" >
<Button text="Click here to start" onAction="#doSomething"/>
</FlowPane>
</children>
</AnchorPane>
When I click the button in the main window - which should load the custom control I get a java.lang.reflect.InvocationTargetException exception caused by: java.lang.NullPointerException: Children: child node is null: parent = FlowPane[id=mainPane]
What am I missing here?
Second question: You may notice I arbitrarily added a button to my main window so when it was clicked I could then load my BorderPane custom control class. Ideally I would like to do this directly in the start method of TestMain but mainPane is null in the start method - when does it become not null?
You should never make your main Application class a controller - it's just too confusing and fraught with errors such as you have encountered.
What is happening is that when you load Main.fxml, the FXMLLoader will create a new instance of the Application class. When you click on the button defined in the main fxml UI, the doSomething method will be invoked on that new TestMain object, not the original TestMain object that was created when your application was launched.
The solution is to create a MainController.java which functions as the controller for the Main.fxml GUI (similar to what you have already done with PetListGUIManager) and remove any of the #FXML related notation completely from your main application class.

Resources