JavaFX getUserData() leads to NullPointerException - javafx

I'm currently looking into the easiest way to save my SQL Connection within my JavaFX project in order to use it in all of my Controllers.
Since I'm creating the Controller in a SideBar FXML file it is not possible to pass the Object from one Controller to the other.
Therefore I wanted to use the Node.setUserData() method and just save the Connection Object to the root node. Unfortunately I get NullPointers when I want to call it.
Saving it works fine:
myStage.getScene().getRoot().setUserData(con);
And calling it from the same stage variable works fine as well:
... = (Connection) myStage.getScene().getRoot().getUserData();
But I'm accessing the stage within my Sidebar.fxml via
Stage stage = (Stage) myButton.getScene().getWindow();
what then leads to NullPointers while accessing the UserData via
stage.getScene().getRoot().getUserData();
I see that the reason for this is, that it is not the "exact same" stage variable. But it has to be the same stage (when I display a new view there it is displayed on the same stage as before).
How can I find the exact same Node I've saved the UserData before? Or is there a way to access the same Node from another context where I do not have the stage?
EDIT: I've put a MCVE here to show what my Problem is: https://github.com/lud-hu/myJavaFxMcve/
EDIT: Code is working in Github now, I'll post the Code with the initial problem here:
MyMcveStarter.java
package myMcve;
import myMcve.controller.LoginController;
import javafx.application.Application;
import javafx.stage.Stage;
public class MyMcveStarter extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
LoginController controller = new LoginController(primaryStage);
controller.displaySceneOn(primaryStage);
}
}
LoginController.java
package myMcve.controller;
import myMcve.view.LoginSceneView;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class LoginController {
private LoginSceneView view;
private Parent scene;
Stage myStage;
String defaultUrl;
String defaultName;
String defaultPassword;
public LoginController(Stage stage) {
defaultUrl = "jdbc:mysql://localhost:3306/db";
defaultName = "root";
defaultPassword = "localhost";
myStage = stage;
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("../view/LoginScene.fxml"));
try {
scene = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
view = loader.getController();
}
public void displaySceneOn(Stage stage) {
stage.setTitle("login");
Scene myScene = new Scene(scene, 1250, 650);
stage.setScene(myScene);
stage.show();
try {
initializeDbConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
private void initializeDbConnection() throws SQLException {
try {
DriverManager.setLoginTimeout(15);
Connection con = DriverManager.getConnection(defaultUrl, defaultName, defaultPassword);
UserManagementController controller = new UserManagementController(myStage, con);
controller.displaySceneOn(myStage);
} catch (Exception e) {
}
}
}
SideBarController.java
package myMcve.controller;
import myMcve.controller.LevelManagementController;
import myMcve.controller.LoginController;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class SideBarController{
#FXML
private Button levelManBtn;
public Button getLevelManBtn() {
return levelManBtn;
}
#FXML
private void levelMan(ActionEvent event){
//start other Controller from here (SideBar)
//how do I access the DB Connection here?
Stage stage = (Stage) levelManBtn.getScene().getWindow();
//LevelManagementController controller = new LevelManagementController(stage, con);
//controller.displaySceneOn(stage);
}
}
UserManagementController.java
package myMcve.controller;
import com.sun.prism.impl.Disposer;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
import javafx.util.Callback;
import myMcve.view.UserManagementView;
import java.io.IOException;
import java.sql.*;
public class UserManagementController{
private UserManagementView view;
private Parent scene;
Stage myStage;
Connection con;
public UserManagementController(Stage stage, Connection con){
myStage = stage;
this.con = con;
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("../view/UserManagementScene.fxml"));
try {
scene = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
view = loader.getController();
}
public void displaySceneOn(Stage stage){
stage.setTitle("user management");
Scene myScene = new Scene(scene, 1250, 650);
stage.setScene(myScene);
stage.show();
}
}
LoginSceneView.java
package myMcve.view;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
public class LoginSceneView {
#FXML
private Label label;
public Label getLabel() {
return label;
}
}
LoginScene.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.112-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="myMcve.view.LoginSceneView">
<Label fx:id="label" text="login Buttons etc..." />
</AnchorPane>
SideBar.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<VBox fx:id="sidebar" prefHeight="650.0" prefWidth="250.0" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="myMcve.controller.SideBarController">
<children>
<Button fx:id="levelManBtn" layoutX="10.0" layoutY="123.0" prefHeight="50.0" prefWidth="350.0" text="Level Management" onAction="#levelMan"/>
</children>
</VBox>
UserManagementView.java
package myMcve.view;
import javafx.fxml.FXML;
import javafx.scene.control.*;
public class UserManagementView {
#FXML
private Label label;
public Label getLabel() {
return label;
}
}
UserManagementScene.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" fx:controller="myMcve.view.UserManagementView">
<left>
<!-- SideBar import -->
<fx:include fx:id="sidebar" source="SideBar.fxml" />
</left>
<center>
<Label fx:id="label" text="user managemnt tableview and Buttons etc..." />
</center>
</BorderPane>

I tried using a design like the one you are using, in which you implement a traditional MVC architecture by using the FXML/JavaFX Controller pair as the MVC view, and create a separate MVC controller. In the end I dismissed it as too convoluted.
The architecture implicit in the FXML-controller design is really a variant of MVC called MVP ("Model-View-Presenter") and if you read the Martin Fowler article on UI architectures, it's close to the variant he calls "Passive View". In this architecture, the FXML file represents the view, which is essentially completely passive and just defines layout. The JavaFX Controller represents the "Presenter", which observes and updates the model, and responds to changes in it by modifying the view. My main recommendation would be to refactor your design so it conforms to this: so remove one of the "controller" layers entirely, and consider the "JavaFX controller" to be fulfilling the general role of controller/presenter in one of the MVC variants. (I posted a fuller answer to this here.)
In particular, your design struggles when you use <fx:include>. The issue is your design is "controller-centric", in the sense that you favor creating the controllers and letting the controllers create the views. Using <fx:include> basically creates a new view for you (by loading another FXML file), which then creates the controller for you. So this is more "view-centric". The conflict between these two makes it tricky to share a model (or services) among the controllers.
One way to get this to work is to set a controller factory on the FXMLLoader. The controller factory is a function that maps a class (defined by the fx:controller attribute in the FXML file) to the JavaFX controller instance. So you can use this factory to create the controller with the connection already initialized in the controller. (BTW, the connection is playing the role of the MVC model in your application: you might want to refactor that to something more robust at a later stage. You should at least factor all the database-specific code to a Data Access Object, and share that object instead of the raw connection.)
First, define a constructor in SideBarController that takes a Connection:
public class SideBarController{
#FXML
private Button levelManBtn;
private final Connection con ;
public SideBarController(Connection con) {
this.con = con ;
}
// existing code (which obviously can now access the connection)...
}
Now when you load your UserManagementView, specify a controller factory that calls the constructor taking a Connection, if one exists, and calls the no-argument constructor otherwise. The approach shown here uses reflection, and is basically akin to the way dependency injection (which is actually what you are trying to do here) frameworks are implemented. There are other possibilities too.
public UserManagementController(Stage stage, Connection con){
myStage = stage;
this.con = con;
FXMLLoader loader = new FXMLLoader();
// note that this resource name will likely not work if you bundle the app as a jar....
loader.setLocation(getClass().getResource("../view/UserManagementScene.fxml"));
loader.setControllerFactory((Class<?> controllerType) -> {
try {
// check constructors of controllerType to see if one takes a Connection:
for (Constructor<?> c : controllerType.getConstructors()) {
if (c.getParameterCount() == 1 && c.getParameterTypes()[0].equals(Connection.class)) {
// found matching constructor, invoke it with the connection as parameter:
return c.newInstance(con);
}
}
// no matching constructor, just invoke default constructor:
return controllerType.newInstance();
} catch (Exception e) {
// fatal...
throw new RuntimeException(e);
}
});
try {
scene = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
view = loader.getController();
}
Controller factories are propagated to the load process for <fx:include>d FXML files. So when UserManagementScene.fxml is loaded, the specified controller is UseManagementView. That has no constructor taking a Connection, so the default constructor will be invoked. When the <fx:include> for SideBar.fxml is encountered, it specifies a controller of SideBarController, which does (now) have a constructor taking a Connection, so that constructor is invoked.
Note that your design is a little different for the side bar than it is for the rest of your application, in that in the other components, your view is the FXML-controller pair with a separate controller class. For the side bar, you use the JavaFX approach where the view is the FXML file, and the controller is specified as the fx:controller. Again, I would probably refactor the application so that everything follows that second design, rather than the first.

Related

Exception in Application start method [Java FX]

I am trying to create a button in JavaFX using Scene Builder but I am having some issues. Here is my code :
MainDesign.java
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MainDesign extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = (BorderPane) FXMLLoader.load(getClass().getResource("application/Design.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);
}
}
DesignController.java:-
package application;
import java.awt.Button;
import javafx.fxml.FXML;
public class DesignController {
#FXML private Button b1;
}
Design.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.DesignController">
<center>
<Button fx:id="b1" mnemonicParsing="false" text="Button" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
Error:
Exception in Application start method
java.lang.reflect.InvocationTargetException
Caused by: java.lang.RuntimeException: Exception in Application start method
Caused by: java.lang.IllegalAccessError: class com.sun.javafx.fxml.FXMLLoaderHelper (in unnamed module #0x22e6b9c) cannot access class com.sun.javafx.util.Utils (in module javafx.graphics) because module javafx.graphics does not export com.sun.javafx.util to unnamed module #0x22e6b9c
Exception running application application.MainDesign
Thus , it's generating an exception in MainDesign.java file , I guess its regarding FXML loader.
Please help me if anyone has a solution for such issue!!!
There is a confusion with the Button component; in Design.fxml you're using javafx.scene.control.Button and in DesignController.java you're using java.awt.Button. JavaFX and Java AWT are independant libraries, so it raises a problem when FXML loader is reading this line:
#FXML private Button b1;
because it cannot make a connection between b1 in your FXML file and b1 in your controller. You have to import javafx.scene.control.Button in your controller.

javafx KeyEvent triggers twice

I had some problems in a project with KeyEvent. A combo box that switch values from one to another when key Enter is pressed.
After some attempts the problem showed was that the Enter key generates 2 times the event, so the combo box apparently didn't change.
Then, I tried to make a simple test project with this snippet:
window.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if(event.getCode().equals(KeyCode.ENTER))
System.out.println("print");
}
});
The program prints twice every enter. Is it possible that KeyCode.ENTER refers to more than one key or something like this?
If i change KeyCode.ENTER in something else (tried with SPACE or some letters) it works properly.
Is possible to do something to solve this issue?
I made a simple project with 3 files :
Main
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class Main extends Application {
private static Stage window;
#Override
public void start(Stage primaryStage) throws Exception{
window = primaryStage;
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static Stage getWindow(){return window;}
public static void main(String[] args) {
launch(args);
}
}
Controller:
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable {
#FXML
TextField textField;
Stage window;
#Override
public void initialize(URL location, ResourceBundle resources) {
window = Main.getWindow();
window.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode().equals(KeyCode.ENTER))
System.out.println("print");
}
});
}
}
Sample :
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity"
prefHeight="400.0" prefWidth="600.0"
fx:controller="Controller"
xmlns="http://javafx.com/javafx/8.0.121"
xmlns:fx="http://javafx.com/fxml/1">
<children>
<TextField fx:id="textField" layoutX="14.0" layoutY="14.0" />
</children>
</AnchorPane>
It prints twice when Enter is pressed. Before the insertion of the TextField instead worked as expected.
Change this line
window.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
to
window.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
Edit code for #James_D
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable {
#FXML
TextField textField;
Stage window;
#Override
public void initialize(URL location, ResourceBundle resources) {
window = Main.getWindow();
window.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
System.out.println("Key Code:"+event.getCode()+" Target:"+event.getTarget());
});
// textField.setOnAction(event -> {});
}
}
When this runs you get the output of
Key Code:ENTER Target:TextField[id=textField, styleClass=text-input text-field]
Key Code:ENTER Target:AnchorPane#7a78d90b[styleClass=root]
If you uncomment the textField Line the output is
Key Code:ENTER Target:TextField[id=textField, styleClass=text-input text-field]
#James_D Initially I did not but after some looking around and I think it has to do with the preset setOnAction command the description is "The action handler associated with this text field, or null if no action handler is assigned. The action handler is normally called when the user types the ENTER key." So when you change the .setOnAction to consume or nothing it prevents this error which I think is because it is no longer kicking out the enter command to the anchorPane surrounding. Now as to why the KeyEvent.KEY_RELEASED works I think this is because the enter Key can only be released once per press down as opposed to the press which can send the command multiple times. This is all complete speculation because I have no proof of what I'm talking about if you can figure out a more reasonable explanation why I would love to know. This was supposed to be a comment but got to long.
I had the same issue. Every key would trigger the EventFilter once, except ENTER, which would trigger it twice.
A more simple and elegant solution was to simply add e.consume() into your block that respons to enter into the EventFilter.
if(e.getCode().toString().equals("ENTER")){
e.consume();
}

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

Javafx 8, scenebuilder 2, and controlsfx with fontawesome?

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.

Resources