Unable to control elements of another fxml file through another controller - javafx

I am working on a project which deals with multiple fxml and corresponding controller files. I need to somehow get access to an fxml element defined in say A.fxml from the controller of B.fxml and use it.
I am not allowed to show the actual code.
However, for this purpose, I have built a simple application with two FXMLs and their corresponding controllers.
This application has Button.fxml with ButtonController.java and ProgressIndicator.fxml with ProgressIndicatorController.java
Upon clicking the button, the Progress indicator would come live.
Below is the code :
Button.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Pane?>
<AnchorPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.ButtonController">
<children>
<Pane prefHeight="206.0" prefWidth="281.0">
<children>
<Button fx:id="button1" layoutX="59.0" layoutY="63.0" mnemonicParsing="false" prefHeight="63.0" prefWidth="164.0" text="Button" onAction="#onButton1Clicked" />
</children>
</Pane>
</children>
</AnchorPane>
ButtonController.java
package application;
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.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ButtonController implements Initializable{
#FXML
private Button button1;
private ProgressIndicator progressIndicator;
private ProgressIndicatorController progressIndicatorController;
#Override
public void initialize(URL location, ResourceBundle resources) {
try {
Object loader = FXMLLoader.load(getClass().getResource("ProgressIndicator.fxml"));
Scene sceneProgressIndicator = new Scene((Parent) loader, 1400, 800);
sceneProgressIndicator.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
sceneProgressIndicator.setFill(Color.TRANSPARENT);
Stage primaryStageProgressIndicator = new Stage();
primaryStageProgressIndicator.setScene(sceneProgressIndicator);
primaryStageProgressIndicator.setResizable(false);
primaryStageProgressIndicator.setHeight(311);
primaryStageProgressIndicator.setWidth(523);
primaryStageProgressIndicator.show();
} catch (IOException e) {
e.printStackTrace();
}
// TODO Auto-generated method stub
}
public void onButton1Clicked() {
FXMLLoader fxmlLoaderProgressIndicator = new FXMLLoader();
fxmlLoaderProgressIndicator.setLocation(getClass().getResource("ProgressIndicator.fxml"));
try {
Parent root = fxmlLoaderProgressIndicator.load();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
progressIndicatorController = (ProgressIndicatorController) fxmlLoaderProgressIndicator.getController();
for(double i = 0.0; i <= 1.0 ; i+=0.2) {
progressIndicator = progressIndicatorController.getProgressIndicator1();
progressIndicator.setProgress(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
ProgressIndicator.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Pane?>
<AnchorPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.ProgressIndicatorController">
<children>
<Pane prefHeight="200.0" prefWidth="200.0">
<children>
<ProgressIndicator fx:id="progressIndicator1" layoutX="47.0" layoutY="31.0" prefHeight="103.0" prefWidth="106.0" progress="0.0" />
</children>
</Pane>
</children>
</AnchorPane>
ProgressindicatorController.java
package application;
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.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ProgressIndicatorController implements Initializable {
#FXML
private ProgressIndicator progressIndicator1;
#Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
}
public ProgressIndicator getProgressIndicator1() {
System.out.println("getteeerrrrrrrr");
return progressIndicator1;
}
public void setProgressIndicator1(ProgressIndicator progressIndicator1) {
this.progressIndicator1 = progressIndicator1;
}
}
The getter method is successfully called.
However when I click the button, the progress indicator doesnot get updated.
I am also not getting any kind of error messages .
I came across this link Javafx - how to acces FXML "objects" which indeed helped me to a great extent,but I'm not able to get this work too. I just tried to modify the button click method as follows :
onButton1Cliked()
public void onButton1Clicked() {
progressIndicator = (ProgressIndicator) sceneProgressIndicator.lookup("#progressIndicator1");
for(double i = 0.0; i <= 1.0 ; i+=0.2) {
try {
System.out.println("Progressingggg " + i);
progressIndicator.setProgress(i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Clicking on the button does indeed print out the statements. But the progress indicator doesn't get updated with each for loop iteration. Its only at last that the progress indicator gets updated and shows done.
Any idea why this is happening ?
Also is there any other way to access the fxml elements other than using a lookup method. Can my previous approach(trying to get the fxml elements through controller) be made to work somehow.
Any advice or suggestions are most welcome. Been into this for several hours. Stackoverflow seems to be the last resort.
EDIT 1 : START
It turned out that the progress Indicator was not updating because of the reason explained in Zephyr's first comment. So i created a separte thread for my application and that seemed to work fine as the progress indicator got updated as expected.
Working version of onButton1Clicked() method that is successfully updating Progress Indicator :
public void onButton1Clicked() {
progressIndicator = (ProgressIndicator) sceneProgressIndicator.lookup("#progressIndicator1");
Runnable runnable = new Runnable() {
#Override
public void run() {
for(double i = 0.0; i <= 1.0 ; i+=0.2) {
try {
System.out.println("Progressingggg " + i);
progressIndicator.setProgress(i);
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
Having resolved this threading issue, I tried once again to go by the original way mentioned in my OP(i.e. to get the ProgressIndicatorController instance and access its progress indicator and then try updating it). However, this is not working for me. I tried debugging and found that I am getting back the ProgressIndicatorController and ProgressIndicator instance back.
Why then my code is unable to update the progress indicator if it has a hold on the progress indicator instance.
The println methods are executed successfully.Its just the progress indicator not getting updated at all.It stands at "0" all the time.
Version of onButton1Clicked() method that is NOT updating progressIndicator :
public void onButton1Clicked() {
FXMLLoader fxmlLoaderProgressIndicator = new FXMLLoader();
fxmlLoaderProgressIndicator.setLocation(getClass().getResource("ProgressIndicator.fxml"));
try {
Parent root = fxmlLoaderProgressIndicator.load();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
progressIndicatorController = (ProgressIndicatorController) fxmlLoaderProgressIndicator.getController();
progressIndicator = progressIndicatorController.getProgressIndicator1();
Runnable runnable = new Runnable() {
#Override
public void run() {
for(double i = 0.0; i <= 1.0 ; i+=0.2) {
try {
System.out.println("Progressingggg " + i);
progressIndicator.setProgress(i);
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
EDIT 1 : END
EDIT 2 : START
Adding other files required to run this sample application
Main.java
package application;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.fxml.FXMLLoader;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Object root =FXMLLoader.load(getClass().getResource("Button.fxml"));
Scene scene = new Scene((Parent) 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);
}
}
My application.css file has nothing in it. Its empty.
I'm using JavaFX8 on Java 8. IDE is Eclipse Photon
I created my project using e(fx)clipse plugin and choosing JavaFX Project from create new project window.
EDIT 2 : END
EDIT 3 : START
Under the section EDIT 1 while trying to test the version of onButton1Clicked() that successfully updates the progress indicator please don't forget to include private Scene sceneProgressIndicator as the member variable of the class ButtonController.java.
EDIT 3 : END

I assume you wanted something like this (note the comments):
import java.io.IOException;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ButtonController {
#FXML
private Button button1;
private ProgressIndicator progressIndicator;
private Scene sceneProgressIndicator;
#FXML
public void initialize() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("ProgressIndicator.fxml"));
Pane progressIndicatorPane = loader.load();
ProgressIndicatorController progressIndicatorController = loader.getController(); //get a reference to the progress indicator controller
progressIndicator = progressIndicatorController.getProgressIndicator1(); //get the progress indicator
sceneProgressIndicator = new Scene(progressIndicatorPane, 1400, 800);
sceneProgressIndicator.setFill(Color.TRANSPARENT);
Stage primaryStageProgressIndicator = new Stage();
primaryStageProgressIndicator.setScene(sceneProgressIndicator);
primaryStageProgressIndicator.setResizable(false);
primaryStageProgressIndicator.setHeight(311);
primaryStageProgressIndicator.setWidth(523);
primaryStageProgressIndicator.show();
} catch (IOException e) {
e.printStackTrace();
}
}
public void onButton1Clicked() {
Runnable runnable = () -> {
for(double i = 0.0; i <= 1.0 ; i+=0.2) {
final double fD = i;
Platform.runLater(() -> progressIndicator.setProgress(fD));//update done on javafx thread
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}

Related

javafx.fxml.LoadException: Controller value already specified [duplicate]

This question already has answers here:
JavaFX Error: Controller value already specified
(1 answer)
javafx exception : Controller value already specified
(1 answer)
Closed 4 years ago.
I have looked at the posts for this and Im still stuck - any help please?
I am trying to display a line chart in scenebuilder (IntelliJ) - but keep getting different errors about constructors.The last error was
'JavaFX Error : Controller already specified'but have removed it previously and still get an error.
and have tried different arguments in Main for FXMLLoader with the loader.setController(...) line, and no luck. I keep reading about sometimes factory controllers are needed if arguments are being passed to the constructor - so think it might be this, but dont know how to write one.Any suggestions please? or good links for me to read up?
My last attempt for FXMLLoaderCode from Main below...
FXMLLoader loader = new FXMLLoader(getClass().getResource("/sample/view/graph.fxml"));
loader.setController(new Controller(sortList));
Parent root = loader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
If anybody has any useful links so I could read up about factory controllers that would be really good. My full code is below.
My programs are
Main(Main.java)
entry point for program and uses FXML loader
Model(SortList.java)
places numbers in x and y(abscissa ordinate) groups
Controller(Controller.java)
add x,y to graph
View(graph.fxml)
graph xml displays x-y line chart
ReadInData(ReadInData.java)
Reads in and parses numbers
Errors
javafx.fxml.LoadException: Controller value already specified.
/C:/Users/user/IdeaProjects/myjavafx/out/production/myjavafx/sample/view/graph.fxml:18
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2597)
at javafx.fxml.FXMLLoader.access$100(FXMLLoader.java:103)
at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:914)
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.load(FXMLLoader.java:2409)
at sample.Main.start(Main.java:32)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$161(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
at java.lang.Thread.run(Thread.java:748)
Model (SortList.java)
public class SortList {
private final double abscissa;
private final double ordinate;
public SortList(double abscissa, double ordinate) {
this.abscissa = abscissa;
this.ordinate = ordinate;
}
public double getabscissa() {
return abscissa;
}
public double getordinate() {
return ordinate;
}
}
View (graph.java)
fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.chart.CategoryAxis?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.chart.ScatterChart?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.Label?>
<AnchorPane id="AnchorPane"
maxHeight="-Infinity"
maxWidth="-Infinity"
minHeight="-Infinity"
minWidth="-Infinity"
prefHeight="400.0"
prefWidth="600.0"
xmlns="http://javafx.com/javafx/8.0.121"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller = "sample.controller.Controller">
<children>
<ScatterChart layoutX="76.0" layoutY="33.0" prefHeight="335.0" prefWidth="449.0" title="Test Graph">
<xAxis>
<CategoryAxis label="X axis" pickOnBounds="false" side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis label="Y axis" side="LEFT" upperBound="1000.0" />
</yAxis>
</ScatterChart>
</children>
</AnchorPane>
Controller (Controller.java)
package sample.controller;
import javafx.fxml.Initializable;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.XYChart;
import sample.model.SortList;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
public class Controller implements Initializable {
public LineChart<Double, Double> lineChart;
private final ArrayList<SortList> sortList;
public LineChart label1;
public Controller(ArrayList<SortList> sortList)
{
this.sortList = sortList;
}
#Override
public void initialize(URL location, ResourceBundle resources) {
XYChart.Series<Double, Double> graphLine = new XYChart.Series<Double, Double>();
for (SortList sortlist : sortList) {
graphLine.getData().add(new XYChart.Data(sortlist.getabscissa(), sortlist.getordinate()));
}
lineChart.getData().add(graphLine);
}
}
Main (Main.java)
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Callback;
import sample.IO.ReadInData;
import sample.controller.Controller;
import sample.model.SortList;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
public class Main extends Application {
#Override
public void start(Stage stage) {
stage.setTitle("Line Chart Sample");
final ArrayList<SortList> sortList = ReadInData.ReadData("C:\\Users\\user\\Desktop\\Java problem\\test.csv");
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/sample/view/graph.fxml"));
loader.setController(new Controller(sortList));
Parent root = loader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
loader.setControllerFactory(new Callback<Class<?>, Object>(){
#Override
public Object call(Class<?> param) {
return null;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
public class SortList {
private final double abscissa;
private final double ordinate;
public SortList(double abscissa, double ordinate) {
this.abscissa = abscissa;
this.ordinate = ordinate;
}
public double getabscissa() {
return abscissa;
}
public double getordinate() {
return ordinate;
}
}
Input/Output Read in File (ReadInData.java)
package sample.IO;
import sample.model.SortList;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
public class ReadInData {
private static final String COMMA_DELIMITER = ",";
static public ArrayList<SortList> ReadData(String filename) {
BufferedReader br = null;
ArrayList<SortList> inputList = new ArrayList<SortList>();
try {
br = new BufferedReader(new FileReader(filename));
String line = "";
br.readLine();
while ((line = br.readLine()) != null) {
String[] inputData = line.split(COMMA_DELIMITER);
if (inputData.length > 0) {
try {
double abscissa = Double.parseDouble(inputData[0]);
double ordinate = Double.parseDouble(inputData[1]);
SortList row = new SortList(abscissa, ordinate);
inputList.add(row);
} catch (NumberFormatException ex) {
ex.printStackTrace();
}
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
return inputList;
}
}
Open your FXML file with Scene Builder and check this

How to launch a window on java(fx)?

I'm having an issue, I've put an exception around part of my code and it returns
"Caught expection :
/Users/delorszimi/Documents/Documents/Java%20CW/JavaFXbasics/bin/Main.fxml:8"
It seems there's a problem with the anchorPane section in my fxml file that's stopping it from launching. Most of my code is below...
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
static Stage window = new Stage();
#Override
public void start(Stage window) throws Exception {
try{
window.setTitle("Sprite Editor");
BorderPane pane = new BorderPane();
Scene sc = new Scene(pane,300,300);
Parent content = FXMLLoader.load(getClass().getClassLoader().getResource("Main.fxml"));
pane.setCenter(content);
window.setScene(sc);
window.show();
window.setOnCloseRequest(e -> closeWindow());
}
catch(Exception e){
System.err.println("Caught expection : " + e.getMessage());
}
}
private void closeWindow() {
Boolean decision = ConfirmBox.display("Are you sure you want to exit?", "Title");
if(decision)
window.close();
}
public static void main(String[] args) {
launch(args);
}
}
FXML file
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="432.0" prefWidth="498.0"
xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="MainController">
<children>
<Canvas fx:id="Canvas1" height="240.0" layoutX="81.0" layoutY="39.0"
width="308.0" />
</children>
</AnchorPane>
package application;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ColorPicker;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
public class MainController {
#FXML
public void initialize(){
Button botao = new Button("Browse Files");
Button exit = new Button("Exit");
exit.setOnAction(e -> System.exit(0));
StackPane layout = new StackPane();
layout.getChildren().addAll(botao,exit);
final Canvas canvas1 = new Canvas(500,300);
GraphicsContext gc = canvas1.getGraphicsContext2D();
gc.setStroke(Color.BLACK);
gc.setLineWidth(1);
ColorPicker cp = new ColorPicker();
layout.getChildren().addAll(canvas1,cp);
Scene sc = new Scene(cp);
sc.setOnMousePressed(e->{
gc.beginPath();
gc.lineTo(e.getSceneX(), e.getSceneY());
gc.stroke();
});
sc.setOnMouseDragged(e->{
gc.lineTo(e.getSceneX(), e.getSceneY());
gc.stroke();
});
}
public void size(){
double width,height;
}
public void fileSelection(ActionEvent evento){
}
}
In FXML file you take a AnchorPane but in code section you create a borderpane instance, don't understand this part. And remove getClassLoader() and try once. I am not sure but you can check for once.

How to switch stages in JavaFX

I have a login stage (300 x 250), I want to open another main stage (fullscreen) if the credentials are correct.
I have figured out how to check the login credentials, but how can I close the login stage and open another stage?
If my application is supposed to work in one window I prefer using a GUI manager singleton class, which manages changing windows. Below I provided the complete code of a simple application which uses this mechanism. Let's assume all the files are in one package, called sample.
Main.java - you initialize the JavaFX components here:
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.io.IOException;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("/sample/root.fxml"));
try{
StackPane rootPane;
rootPane = loader.load();
GuiManager guiModel = GuiManager.getInstance();
guiModel.setRootPane(rootPane);
Scene scene = new Scene(rootPane);
primaryStage.setScene(scene);
primaryStage.show();
guiModel.changeWindow("/sample/firstwindow.fxml");
} catch (IOException exception) {
exception.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
root.fxml - all the windows are supposed to be based on it:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.StackPane?>
<StackPane fx:id="rootPane"
xmlns="http://javafx.com/javafx/8.0.60"
xmlns:fx="http://javafx.com/fxml/1"
prefWidth="1" prefHeight="1"/>
firstwindow.fxml - first actual window which will be displayed:
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="sample.FirstWindowController">
<Label text="First window"/>
<Button text="Change window" onAction="#changeWindow"/>
</VBox>
FirstWindowController.java - a controller class of the first window:
package sample;
import javafx.fxml.FXML;
public class FirstWindowController {
#FXML
private void changeWindow() {
GuiManager.getInstance().changeWindow("/sample/secondwindow.fxml");
}
}
secondwindow.fxml - it will be displayed after clicking the button of the first window:
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="sample.SecondWindowController" >
<Label text="Second window"/>
<Button text="Change window" onAction="#changeWindow"/>
</VBox>
SecondWindowController.java - a controller class of the second window:
package sample;
import javafx.fxml.FXML;
public class SecondWindowController {
#FXML
private void changeWindow() {
GuiManager.getInstance().changeWindow("/sample/firstwindow.fxml");
}
}
GuiManager.java - a class that manages changing windows based on the root:
package sample;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.io.IOException;
public class GuiManager {
private StackPane rootPane;
private static GuiManager instance;
public static GuiManager getInstance() {
if (instance == null) {
instance = new GuiManager();
}
return instance;
}
private GuiManager() {}
public void changeWindow(String path) {
changeWindow(rootPane, path, this);
rootPane.setPrefWidth(-1);
rootPane.setPrefHeight(-1);
}
public static void changeWindow(Pane pane, String newWindowPath, Object callingController) {
Window window = pane.getScene().getWindow();
double x = window.getX() + getHorizontalMidpoint(window);
double y = window.getY() + getVerticalMidpoint(window);
ObservableList<Node> childrenList = pane.getChildren();
removeAllIncludedChildren(childrenList);
FXMLLoader loader = new FXMLLoader(callingController.getClass().getResource(newWindowPath));
try {
pane.getChildren().add(loader.load());
Stage primaryStage = (Stage) window;
primaryStage.setMinHeight(0);
primaryStage.setMinWidth(0);
window.sizeToScene();
window.setX(x - getHorizontalMidpoint(window));
window.setY(y - getVerticalMidpoint(window));
primaryStage.setMinHeight(window.getHeight());
primaryStage.setMinWidth(window.getWidth());
} catch (IOException exception) {
exception.printStackTrace();
}
}
private static double getHorizontalMidpoint(Window window) {
int horizontalBisectionCoefficient = 2;
return window.getWidth() / horizontalBisectionCoefficient;
}
private static double getVerticalMidpoint(Window window) {
int verticalBisectionCoefficient = 2;
return window.getHeight() / verticalBisectionCoefficient;
}
private static void removeAllIncludedChildren(ObservableList<Node> childrenList) {
for (int childIndex = 0; childIndex < childrenList.size(); childIndex++) {
childrenList.remove(childIndex);
}
}
public void setRootPane(StackPane rootPane) {
this.rootPane = rootPane;
}
}
I just run in the same issue and this answer solved my issue perfectly while being short and clean.
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
label.setText("Hello World!");
//Here I want to swap the screen!
Stage stageTheEventSourceNodeBelongs = (Stage) ((Node)event.getSource()).getScene().getWindow();
// OR
Stage stageTheLabelBelongs = (Stage) label.getScene().getWindow();
// these two of them return the same stage
// Swap screen
stage.setScene(new Scene(new Pane()));
}
PS.: Remember to click the original answer and upvote it. The guy deserves...
PPS.: I am not sure just copying an answer is okay(instead of just share the link through a comment) but since this doesnt have a correct answer yet i decided to do it for visibility.

JavaFX representing directories

I am building an application in Java where I need to represent a predefined directory in any kind of view in my window. Imagine something like a backup application where the main window displays all of the files under a specific directory.
Is there a way to do so using JavaFX? I do not care if files are going to be displayed as a tree or icons or anything really.
Thanks
edit: what I tried so far with no luck. This is my Controller class:
import javafx.scene.control.*;
import java.io.IOException;
public class ViewController {
public ListView listView;
public ViewController() throws IOException {
listView = new ListView();
listView.getItems().addAll("Iron Man", "Titanic", "Contact", "Surrogates");
}
}
and my fxml contains:
<ListView fx:id="listView" prefHeight="200.0" prefWidth="200.0" />
I wrote up a brief program that displays a tree of files and directories chosen by the user.
The result:
How it works:
When the user clicks the "Load Folder" button,
getNodesForDirectory is called, and recursively walks through the file tree, making tree items along the way.
Here is the code:
import java.io.File;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
public class DirectoryViewer extends Application {
#Override
public void start(Stage primaryStage) {
TreeView<String> a = new TreeView<String>();
BorderPane b = new BorderPane();
Button c = new Button("Load Folder");
c.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
DirectoryChooser dc = new DirectoryChooser();
dc.setInitialDirectory(new File(System.getProperty("user.home")));
File choice = dc.showDialog(primaryStage);
if(choice == null || ! choice.isDirectory()) {
Alert alert = new Alert(AlertType.ERROR);
alert.setHeaderText("Could not open directory");
alert.setContentText("The file is invalid.");
alert.showAndWait();
} else {
a.setRoot(getNodesForDirectory(choice));
}
}
});
b.setTop(c);
b.setCenter(a);
primaryStage.setScene(new Scene(b, 600, 400));
primaryStage.setTitle("Folder View");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public TreeItem<String> getNodesForDirectory(File directory) { //Returns a TreeItem representation of the specified directory
TreeItem<String> root = new TreeItem<String>(directory.getName());
for(File f : directory.listFiles()) {
System.out.println("Loading " + f.getName());
if(f.isDirectory()) { //Then we call the function recursively
root.getChildren().add(getNodesForDirectory(f));
} else {
root.getChildren().add(new TreeItem<String>(f.getName()));
}
}
return root;
}
}
Good luck with your project!

Load fxml as background process - Javafx

My initial fxml(say home.fxml) has a lot of functionalities, hence it takes a lot of time to load completely. So to avoid the time gap between program start and fxml loading I introduced one more fxml(say loader.fxml) with a gif image which should appear while the main fxml is loading.
The problem is the gif image in my loader.fxml is not moving as in the program is hanging until the home.fxml is loaded completely.
To avoid this I moved the home.fxml loading into a thread as shown in below code.
public class UATReportGeneration extends Application {
private static Stage mainStage;
#Override
public void start(Stage stage) {
Parent loaderRoot = null;
try {
loaderRoot = FXMLLoader.load(getClass().getResource("/uatreportgeneration/fxml/Loader.fxml"));
} catch (IOException ex) {
Logger.getLogger(UATReportGeneration.class.getName()).log(Level.SEVERE, null, ex);
}
Scene loadScene = new Scene(loaderRoot);
stage.setScene(loadScene);
stage.initStyle(StageStyle.UNDECORATED);
stage.getIcons().add(new Image(this.getClass().getResourceAsStream("/uatreportgeneration/Images/logo.png")));
stage.show();
mainStage = new Stage(StageStyle.UNDECORATED);
mainStage.setTitle("Upgrade Analysis");
mainStage.getIcons().add(new Image(this.getClass().getResourceAsStream("/uatreportgeneration/Images/logo.png")));
setStage(mainStage);
new Thread(() -> {
Platform.runLater(() -> {
try {
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(getClass().getResource("/uatreportgeneration/fxml/Home.fxml"));
Scene scene = new Scene(root);
mainStage.setScene(scene);
mainStage.show();
stage.hide();
System.out.println("Stage showing");
// Get current screen of the stage
ObservableList<Screen> screens = Screen.getScreensForRectangle(new Rectangle2D(mainStage.getX(), mainStage.getY(), mainStage.getWidth(), mainStage.getHeight()));
// Change stage properties
Rectangle2D bounds = screens.get(0).getVisualBounds();
mainStage.setX(bounds.getMinX());
mainStage.setY(bounds.getMinY());
mainStage.setWidth(bounds.getWidth());
mainStage.setHeight(bounds.getHeight());
System.out.println("thread complete");
} catch (IOException ex) {
Logger.getLogger(UATReportGeneration.class.getName()).log(Level.SEVERE, null, ex);
}
});
}).start();
}
public static Stage getStage() {
return mainStage;
}
public static void setStage(Stage stage) {
mainStage = stage;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
But after this code also my program is hanging(The gif image is not moving). If I load the fxml outside the Platform.runLater(), I get the exception Not on FX Thread.
I also tired using Task() but from that the gif image is moving but the fxml is not loading in the background, if I try to load the fxml outside Platform.runLater().
Can anyone please help me and tell me how can I correct the code so that my fxml loads in the background without disturbing the foreground process.
Use a Task. You need to arrange to create the scene and update the stage on the FX Application Thread. The cleanest way is to use a Task<Parent>:
Task<Parent> loadTask = new Task<Parent>() {
#Override
public Parent call() throws IOException {
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(getClass().getResource("/uatreportgeneration/fxml/Home.fxml"));
return root ;
}
};
loadTask.setOnSucceeded(e -> {
Scene scene = new Scene(loadTask.getValue());
mainStage.setScene(scene);
mainStage.show();
stage.hide();
System.out.println("Stage showing");
// Get current screen of the stage
ObservableList<Screen> screens = Screen.getScreensForRectangle(new Rectangle2D(mainStage.getX(), mainStage.getY(), mainStage.getWidth(), mainStage.getHeight()));
// Change stage properties
Rectangle2D bounds = screens.get(0).getVisualBounds();
mainStage.setX(bounds.getMinX());
mainStage.setY(bounds.getMinY());
mainStage.setWidth(bounds.getWidth());
mainStage.setHeight(bounds.getHeight());
System.out.println("thread complete");
});
loadTask.setOnFailed(e -> loadTask.getException().printStackTrace());
Thread thread = new Thread(loadTask);
thread.start();
Here is a SSCCE using this technique:
main.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<VBox spacing="10" xmlns:fx="http://javafx.com/fxml/1" fx:controller="MainController">
<padding>
<Insets top="24" left="24" right="24" bottom="24"/>
</padding>
<TextField />
<Button fx:id="button" text="Show Window" onAction="#showWindow"/>
</VBox>
MainController (uses the Task approach shown above):
import java.io.IOException;
import javafx.concurrent.Task;
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;
public class MainController {
#FXML
private Button button ;
#FXML
private void showWindow() {
Task<Parent> loadTask = new Task<Parent>() {
#Override
public Parent call() throws IOException, InterruptedException {
// simulate long-loading process:
Thread.sleep(5000);
FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml"));
Parent root = loader.load();
return root ;
}
};
loadTask.setOnSucceeded(e -> {
Scene scene = new Scene(loadTask.getValue());
Stage stage = new Stage();
stage.initOwner(button.getScene().getWindow());
stage.setScene(scene);
stage.show();
});
loadTask.setOnFailed(e -> loadTask.getException().printStackTrace());
Thread thread = new Thread(loadTask);
thread.start();
}
}
test.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="TestController">
<padding>
<Insets top="24" left="24" right="24" bottom="24"/>
</padding>
<center>
<Label fx:id="label" text="This is a new window"/>
</center>
<bottom>
<Button text="OK" onAction="#closeWindow" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets top="5" bottom="5" left="5" right="5"/>
</BorderPane.margin>
</Button>
</bottom>
</BorderPane>
TestController:
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class TestController {
#FXML
private Label label ;
#FXML
private void closeWindow() {
label.getScene().getWindow().hide();
}
}
Main application:
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
primaryStage.setScene(new Scene(FXMLLoader.load(getClass().getResource("main.fxml"))));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Notice that after pressing the button, you can still type in the text field during the five seconds it takes to "load" the FXML, so the UI is remaining responsive.
your approach using Task was already correct. you were just missing a bit more: you were just missing another Platform#invokeLater() to update the UI:
new Thread(new Task() {
#Override
protected Object call() throws Exception {
// Simulating long loading
Thread.sleep(5000);
FXMLLoader loader = new FXMLLoader(getClass().getResource("home.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root);
// Updating the UI requires another Platform.runLater()
Platform.runLater(new Runnable() {
#Override
public void run() {
mainStage.setScene(scene);
mainStage.show();
stage.hide();
System.out.println("Stage showing");
// Get current screen of the stage
ObservableList<Screen> screens = Screen.getScreensForRectangle(new Rectangle2D(mainStage.getX(), mainStage.getY(), mainStage.getWidth(), mainStage.getHeight()));
// Change stage properties
Rectangle2D bounds = screens.get(0).getVisualBounds();
mainStage.setX(bounds.getMinX());
mainStage.setY(bounds.getMinY());
mainStage.setWidth(bounds.getWidth());
mainStage.setHeight(bounds.getHeight());
System.out.println("thread complete");
}
});
return null;
}
}).start();
In addition to #James_D answer. As mentioned by him, Stages and Scene should not be added in background thread, they should be added only in FX main thread.
I have added tooltips in my home.fxml, which is nothing but a PopupWindow. Therefore to the background thread, it appeared as a new stage. Hence it threw IllegalStateException. After removing the tooltips from the fxml, the fxml was able to load as a background process as there were no stages created in that thread.

Resources