Javafx 8, scenebuilder 2, and controlsfx with fontawesome? - javafx

I'm relatively new to javafx, and have recently started a project using java 8 and javafx. I am using Scenebuilder 2.0 to build my javafx ui. I was wondering if anyone have managed to use fontawesome in scenebuilder? Currently I need to do this to add graphics to a label
levelLabel1.setGraphic(create(FontAwesome.Glyph.CHEVRON_RIGHT));
public static Node create(Glyph glyph) {
FontAwesome fontAwesome = new FontAwesome();
fontAwesome.fontColor(color);
Node result = fontAwesome.create(glyph.getChar());
result.setScaleX(SCALE);
result.setScaleY(SCALE);
return result;
}

You can use FontAwesomeFX 8.1, that have a simply way to do this.
With ControlsFx you need edit fxml file. (more info)
<?import org.controlsfx.glyphfont.*?>
//...
<Label>
<graphic>
<Glyph fontFamily="FontAwesome" icon="PLUS" />
</graphic>
</Label>
//...

You will have to create a custom component for it.
Then you will be able to load it there (but still not able to see the Glyph there) and will be able to set the glyphs easier:
Code available on gist
Main.java
import org.controlsfx.glyphfont.FontAwesome;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FALabel label = new FALabel();
label.setGlyph(FontAwesome.Glyph.ANDROID);
label.setValue("RoBOZUKU");
Pane p = new Pane();
p.getChildren().add(label);
Scene scene = new Scene(p);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
FALabel.java
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.FontAwesome.Glyph;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.paint.Color;
public class FALabel extends Label{
private Color color;
private Glyph glyph;
public void setValue(String text){
this.setText(text);
this.setGraphic(create(getGlyph()));
}
public FALabel() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("FALabel.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
public Node create(Glyph glyph) {
FontAwesome fontAwesome = new FontAwesome();
fontAwesome.fontColor(getColor());
Node result = fontAwesome.create(glyph.getChar());
result.setScaleX(this.getScaleX());
result.setScaleY(this.getScaleY());
return result;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public Glyph getGlyph() {
return glyph;
}
public void setGlyph(Glyph fontAwesome) {
this.glyph = fontAwesome;
}
}
FALabel.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<fx:root type="javafx.scene.control.Label" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"/>
PS: I know this probably wasn't exactly what you are looking for, but scene builder is still very limited.
PS2: You can also create a custom component using the Font Awesome Icons and implementing then in a Pane ( AnchorPane, Pane, HBox) with a ImageView and a Label.

Related

Unable to control elements of another fxml file through another controller

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();
}
}

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.

Trying to set up an "onshowing" ActionEvent in Combobox in javafx

I'm using Scene Builder to build a javafx gui, and I want a ComboBox where something will happen "On Showing." It looks simple enough, but it crashes when I try to implement it. I created a simple version having nothing but a combobox, and it still crashes.
Here is the Main.java module:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
// BorderPane root = new BorderPane();
BorderPane root = (BorderPane) FXMLLoader.load(Main.class.getResource("Comboscreen.fxml"));
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);
}
}
Here is the fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
<center>
<VBox alignment="CENTER" prefHeight="400.0" prefWidth="400.0" BorderPane.alignment="CENTER">
<children>
<ComboBox fx:id="ComboTestBox" onAction="#ComboDo" onShowing="#Showaction" prefWidth="150.0" promptText="Testbox" />
</children>
</VBox>
</center>
</BorderPane>
and here is the Controller:
package application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
public class Controller {
#FXML
public ComboBox<String> ComboTestBox;
#FXML
void ComboDo(ActionEvent event) {
System.out.println(" Option Chosen");
}
ObservableList<String> options =
FXCollections.observableArrayList(
"Option 1",
"Option 2",
"Option 3"
);
#FXML
void Showaction(ActionEvent event) {
System.out.println(" TestAction");
}
public void initialize() {
ComboTestBox.setItems(options);
}
}
It looks simple enough, but it crashes every time. If I remove the "onshowing" action, it works just fine. Any advice is appreciated, as I am pretty new at this.
Assuming by "crashing" you mean it gives you a runtime exception (for future reference, please include the stack trace, which has lots of information for you to diagnose the problem, in your question):
The onShowing handler is an EventHandler<Event>, not an EventHandler<ActionEvent>, so you need:
#FXML
void Showaction(Event event) {
System.out.println(" TestAction");
}
Note that if you're not using the Event parameter, you can omit it, and the FXMLLoader will still be able to map to the correct handler method:
#FXML
void Showaction() {
System.out.println(" TestAction");
}
However, if you include the parameter, it must be of the correct type.
As an aside, you should use proper naming conventions, i.e.
#FXML
void showAction(Event event) {
System.out.println(" TestAction");
}
with the corresponding change to your FXML file.

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.

JavaFX 8: The keyboard events are not being processed if a scene has only shapes

It seems there is an issue in setting focus (and handling keyboard events) on the JavaFX scenes that have only shapes (e.g. Rectangle, Circle, etc). Since formatting FXML is not easy at stackoverflow, I described the issue and a workaround in my blog:
http://yakovfain.com/2015/02/13/javafx-8-the-keyboard-events-are-not-being-processed-if-a-scene-has-only-shapes/
I'd appreciate if someone could find a better solution or explain what do I do wrong.
Thanks
This works for me.
The only change I made from Yakov's original solution is to remove the button work around hack and make the request focus call after the stage had been shown.
I think in Yakov's original implementation, the group containing the shapes was not shown on a stage at the time focus was requested for it - so it never got focus. By ensuring that we wait until the stage is displayed before requesting focus, everything works fine.
sample.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.Group?>
<?import javafx.scene.shape.Rectangle?>
<Group fx:id="theGroup" onKeyPressed="#keyHandler" focusTraversable="true"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="Controller">
<Rectangle fill="BLUE" height="300.0" stroke="BLACK"
strokeType="INSIDE" width="300.0"/>
</Group>
Main.java
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("sample.fxml"));
Parent root = loader.load();
primaryStage.setScene(new Scene(root, 300, 300));
primaryStage.show();
Controller controller = loader.<Controller>getController();
controller.focus();
}
public static void main(String[] args) {
launch(args);
}
}
Controller.java
import javafx.fxml.FXML;
import javafx.scene.Group;
import javafx.scene.input.KeyEvent;
public class Controller {
#FXML
private Group theGroup;
public void keyHandler(KeyEvent event) {
System.out.println("A Key was pressed");
}
public void focus() {
theGroup.requestFocus();
}
}

Resources