JavaFX initialize() is not called on inner custom component - java

I am using javaFX and I need to put custom components to my scene. Therefore I have "main_pane.fxml" with grid pane containing my components (for example DocumentModule).
main_pane.fxml
<GridPane xmlns:fx="http://javafx.com/fxml" fx:controller="GUI.MainPane" gridLinesVisible="true" >
<padding>
<Insets bottom="0" top="0" left="0" right="0" />
</padding>
.
.
.
<DocumentModule fx:id="documentModule"
minWidth="200" minHeight="400"
GridPane.columnIndex="0" GridPane.rowIndex="1">
.
.
.
</DocumentModule>
.
.
.
Each of them is defined in separate fxml file.
document_module.fxml
<GridPane xmlns:fx="http://javafx.com/fxml" fx:controller="GUI.DocumentModule" >
<padding>
<Insets bottom="0" top="0" left="0" right="0" />
</padding>
<ToolBar fx:id="toolBar" GridPane.rowIndex = "0" GridPane.columnIndex = "0" orientation="HORIZONTAL" >
.
.
.
</ToolBar>
<ScrollPane fx:id="scrollPane" hbarPolicy="AS_NEEDED" vbarPolicy="AS_NEEDED" GridPane.rowIndex = "1" GridPane.columnIndex = "0">
<DocumentView fx:id="documentView"/>
</ScrollPane>
</GridPane>
Problem is that DocumentModule is not initialized after construction. It's constructor is called but not it's initialize(URL location, ResourceBundle resources) method. Therefore objects from fxml are not injected.
Controller code for document module
public class DocumentModule extends GridPane implements Initializable {
protected Document document;
#FXML
private DocumentView documentView;
#FXML
private ScrollPane scrollPane;
.
.
.
public DocumentModule() {
System.out.println("Document Module constructed.");
//this is called correctly
}
#Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println("Document Module initialized.");
//This is not called at all.
}
}
For MainPane everything works fine but not for any of inner components.
The component tree seems to be constructed correctly, just the inner components are not initialized. Also the inner components are not dispalyed in application scene (if I load their fxml directly they work, if I use fx:include they are just displayed).
MainPane controller
public final class MainPane extends GridPane implements Initializable {
#FXML
private DocumentModule documentModule;
#FXML
private EditModule editModule;
public MainPane() {
System.out.println("Main Pane constructed.");
}
#Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println("Main Pane initialized.");
}
}
Application entry class' start method
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("main_pane.fxml"));
GridPane root = fxmlLoader.load();
Scene scene = new Scene(root, 800, 600);
primaryStage.setMaximized(true);
primaryStage.setMinWidth(800);
primaryStage.setMinHeight(600);
primaryStage.setTitle("App");
primaryStage.setScene(scene);
primaryStage.show();
}
I haven't find any topic/page/blog with same problem. Few of them had similar symptoms, but no of solutins helped me. Does anyone have idea why initialize is not called on inner components?
Thanks!
Tom

Nowhere in your code do you actually load document_module.fxml, so the elements defined there will never be created. The initialize() method is called on the controller for an fxml file when the FXMLLoader loads that file, but since you never load the fxml file, the initialize() method is never called.
The element <DocumentModule> in your main FXML merely causes the DocumentModule class to be instantiated (via a call to its no-arg constructor), but there is no link from there to the fxml file.
It looks like you are trying to follow the FXML custom component pattern. To do so, you need to load the FXML file in the custom component constructor. Specify a dynamic root and do not specify the controller class in the fxml, and set both on the FXMLLoader before you call load:
document_module.fxml:
<fx:root type="GridPane" xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="0" top="0" left="0" right="0" />
</padding>
<ToolBar fx:id="toolBar" GridPane.rowIndex = "0" GridPane.columnIndex = "0" orientation="HORIZONTAL" >
.
.
.
</ToolBar>
<ScrollPane fx:id="scrollPane" hbarPolicy="AS_NEEDED" vbarPolicy="AS_NEEDED" GridPane.rowIndex = "1" GridPane.columnIndex = "0">
<DocumentView fx:id="documentView"/>
</ScrollPane>
</fx:root>
DocumentModule.java:
public class DocumentModule extends GridPane implements Initializable {
protected Document document;
#FXML
private DocumentView documentView;
#FXML
private ScrollPane scrollPane;
.
.
.
public DocumentModule() {
System.out.println("Document Module constructed.");
FXMLLoader loader = new FXMLLoader(getClass().getResource("document_module.fxml"));
loader.setRoot(this);
loader.setController(this);
try {
loader.load();
} catch (IOException exc) {
exc.printStackTrace();
// this is pretty much fatal, so:
System.exit(1);
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println("Document Module initialized.");
// This will now be called after the #FXML-annotated fields are initialized.
}
}

Related

Add a custom component in JavaFX

I would like to add a custom element into a VBox.
For example: being able to write VBox.getChildren().add(element) and element is a custom node created by me in FXML.
I already followed this tutorial: https://docs.oracle.com/javafx/2/fxml_get_started/custom_control.htm
but the example only shows how to do this inside the same controller (a single controller, i already have my "big" controller, which is WinnerController, I would like to split the two classes, one that manages the single element, and one that manages the whole scene Winner.fxml).
I already have a class WinnerController which is the controller of my FXML Winner.fxml.
Here the code of my Winner.fxml:
<AnchorPane id="paneWinner" fx:id="paneWinner" prefHeight="800.0" prefWidth="1280.0" stylesheets="#winner.css" xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.gui.WinnerController">
<children>
<VBox layoutX="434.0" layoutY="125.0" prefHeight="250.0" prefWidth="413.0" spacing="10.0">
<children>
<AnchorPane id="leaderBoardElement" prefHeight="80.0" prefWidth="413.0" stylesheets="#leaderBoard.css">
<children>
<Text layoutX="49.0" layoutY="45.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Text" wrappingWidth="107.13671875" />
</children></AnchorPane>
</children>
</VBox>
I would like to dynamically add element with the id "leaderBoardElement" (so an AnchorPane + Text) into my VBox.
How can i do that?
Edit: I also tried with this solution:How to understand and use `<fx:root>` , in JavaFX?
but i keep getting nothing. When i do vbox.getChildren().add(new MyComponent());
called in my WinnerControlleri get nothing.
WinnerController class:
public class WinnerController implements Initializable {
#FXML
private VBox leaderBoard;
#FXML
public void initialize(URL location, ResourceBundle resources) {
System.out.println("Winner Init");
MyComponent test = new MyComponent();
System.out.println(test);
// leaderBoard.getChildren().add(new MyComponent());
}
}
My Component class:
public class MyComponent extends AnchorPane {
#FXML
private TextField textField ;
#FXML
private Button button ;
public MyComponent() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("MyComponent.fxml"));
loader.setController(this);
loader.setRoot(this);
loader.load();
textField.setText("HELLO!");
} catch (IOException exc) {
// handle exception
System.out.println("ELEMENT NOT CREATE!!!");
}
}
}
Winner.fxml:
<AnchorPane id="paneWinner" fx:id="paneWinner" prefHeight="800.0"
prefWidth="1280.0" stylesheets="#winner.css" xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.gui.WinnerController">
<children>
<VBox fx:id="leaderBoard" layoutX="434.0" layoutY="125.0" prefHeight="250.0" prefWidth="413.0" spacing="10.0" />
MyComponent.fxml:
<fx:root type="javafx.scene.layout.AnchorPane" fx:id="leaderBoardElement" id="leaderBoardElement">
<TextField fx:id="textField" />
<Button fx:id="button" />
And i call the creation and loading of Winner.fxml from another class like this:
FXMLLoader loader = new FXMLLoader(getClass().getResource("/Winner.fxml"));
if (loader!=null)
System.out.println("LOADER NOT NULL!!");
try{
System.out.println("TRY!");
Parent root = (Parent) loader.load();
if(root!=null)
System.out.println("ROOT NOT NULL!!");
Scene startedGame = new Scene(root, 1280, 800, Color.WHITE);
if(startedGame!=null)
System.out.println("SCENE NOT NULL!");
Stage window = (Stage) paneCarta0.getScene().getWindow();
if (window!=null)
System.out.println("WINDOW NOT NULL!!");
window.setScene(startedGame);
window.show();
catch (IOException Exception) {
System.out.println("View not found. Error while loading");
}
The problem is inside the new MyComponent(), where probably i get an exception and it propagates to my main caller. I've tried everything but i can't figure out why it can't create the MyComponent object.
If your custom component is in a separate FXML file you can do this.
In your WinnerController class:
#FXML
private void initialize() throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getRessource("customElement.fxml"));
fxmlLoader.setController(new CustomElementController()); //Or just specify the Controller in the FXML file
myVBox.getChildren().add(fxmlLoader.load());
}
Edit: In this solution there should be NO <fx:include> tags in your main fxml

How to create an FXML handler that accepts parameters?

I am developing a small, fake, Mail Client in JavaFXML.
It offers a Listview with messages written on a txt file, a TextArea which prints selected message and some buttons.
Here's an Image of the Main View: https://ibb.co/iKN2rm
I already took care of "New Message" Button, which is launching a new FXML View and works well.
Here's an Image of the "New_Message" View: https://ibb.co/hQf5Bm
Now I'm trying to implement the "Reply" button, which should launch the same View as before (New Message) but set on all three TextFields strings taken from the Main View, such as Message's text, recipient and Message Argument.
Below the New Message Button Handler:
private void handle_new(ActionEvent event) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/Client/Resources/new_utente1.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.setTitle("New Message");
stage.setScene(new Scene(root1));
stage.show();
} catch (Exception ecc) {
System.out.println("ERROR: " + ecc.getMessage());
}
}
I tried to implement the handle_reply method, but I'm not able to add parameters because FXML file won't find the method if I do so.
Below a small part of the FXML file:
<TextArea fx:id = "testo" editable="false" layoutX="283.0" layoutY="53.0" prefHeight="450.0" prefWidth="490.0" promptText="Select a message and it will be displayed here..." />
<Button id="nuovo" layoutX="46.0" layoutY="521.0" onAction = "#handle_new" mnemonicParsing="false" prefHeight="25.0" prefWidth="173.0" text="New Message" />
<Button id="reply" layoutX="283.0" layoutY="521.0" onAction = "#handle_reply" mnemonicParsing="false" prefHeight="25.0" prefWidth="132.0" text="Reply" />
My question is: How do I implement the "handle_reply" method as described before?
Thank you
Create a Controller class
then connect this class to your view
private void handle_new(ActionEvent event) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/Client/Resources/new_utente1.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
CController ctr = fxmlLoader.getController();
ctr.setLabelText("asdsad");
Stage stage = new Stage();
stage.setTitle("New Message");
stage.setScene(new Scene(root1));
stage.show();
} catch (Exception ecc) {
System.out.println("ERROR: " + ecc.getMessage());
}
}
and in your Controllers you'll do
public class CController implements Initializable {
#FXML TextArea testo;
#Override
public void initialize(URL location, ResourceBundle resources) {}
public void setLabelText(String text){testo.setText(text);}
}
Unfortunately it's not possible to use handlers with parameters. You should use different handler in each case.
Had this problem some time ago too. As a solution you could perform small refactoring to minimize code duplicating.
And if you know javascript you can play with this instead of using java handlers: https://docs.oracle.com/javase/8/javafx/api/javafx/fxml/doc-files/introduction_to_fxml.html#scripting
Edit: also you can check this answer: https://stackoverflow.com/a/37902780/5572007 (pretty the same)

FXMLLoader load time in JDK 8u92

In my JavaFX application I have to load many fxml files (200+) in the same time. I have decided to load them in background Task just like in https://stackoverflow.com/a/34878843 answear. Everything works fine (load time was acceptable) until JDK update. Newest version of JDK lengthened load time 3-4 times.
I have checked previous JDK releases and that problem appears from the JDK 8u92.
To test that issue I created new simple JavaFX FXML Application in Netbeans 8.1 and use only generated classes and fxml. Creating view from code works fine.
Application class:
public class FXMLLoaderTest extends Application {
private static Executor ex = Executors.newCachedThreadPool();
//private static Executor ex = Executors.newFixedThreadPool(400);
//private static Executor ex = Executors.newSingleThreadExecutor();
#Override
public void start(Stage stage) throws Exception {
VBox box = new VBox();
ScrollPane root = new ScrollPane(box);
Button b = new Button("GENERATE");
b.setOnAction(e -> {
IntStream.range(0, 1000).forEach(i -> {
Task<Parent> task = new Task<Parent>() {
#Override
protected Parent call() throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
Parent root = null;
try {
root = loader.load();
} catch (IOException ex) {
Logger.getLogger(Loader.class.getName()).log(Level.SEVERE, null, ex);
}
// StackPane root= new StackPane();
// Button click = new Button("Click");
// root.setPrefSize(300, 300);
// root.getChildren().add(click);
return root;
}
};
task.setOnSucceeded(ev -> {
final Parent parent = task.getValue();
box.getChildren().add(parent);
});
task.setOnFailed(ev -> task.getException().printStackTrace());
ex.execute(task);
});
});
box.getChildren().add(b);
Scene scene = new Scene(root, 400, 500);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
FXMLDocument.fxml
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxmlloader.FXMLDocumentController">
<children>
<Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
<Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" />
</children>
</AnchorPane>
FXMLDocumentController.java
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
label.setText("Hello World!");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
I have tested this on several computers and result was always the same. On JDK 8u91 fxml files load fast. I have checked release note of 8u92 and I haven't found any changes in FXMLLoader class.
Has anybody encounter this issue? Mayby I am doing something wrong then please correct me.

how do I get an element in JavaFx using an id?

I am new to FXML and I am trying to create a handler for all of the button clicks using a switch. However, in order to do so, I need to get the elements using and id. I have tried the following but for some reason (maybe because I am doing it in the controller class and not on the main) I get a stack overflow exception.
public class ViewController {
public Button exitBtn;
public ViewController() throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("mainWindow.fxml"));
Scene scene = new Scene(root);
exitBtn = (Button) scene.lookup("#exitBtn");
}
}
So how will I get an element (for example a button) using it's id as a reference?
The fxml block for the button is:
<Button fx:id="exitBtn" contentDisplay="CENTER" mnemonicParsing="false"
onAction="#handleButtonClick" text="Exit" HBox.hgrow="NEVER" HBox.margin="$x1"/>
Use a controller class, so that you don't need to use a lookup. The FXMLLoader will inject the fields into the controller for you. The injection is guaranteed to happen before the initialize() method (if you have one) is called
public class ViewController {
#FXML
private Button exitBtn ;
#FXML
private Button openBtn ;
public void initialize() {
// initialization here, if needed...
}
#FXML
private void handleButtonClick(ActionEvent event) {
// I really don't recommend using a single handler like this,
// but it will work
if (event.getSource() == exitBtn) {
exitBtn.getScene().getWindow().hide();
} else if (event.getSource() == openBtn) {
// do open action...
}
// etc...
}
}
Specify the controller class in the root element of your FXML:
<!-- imports etc... -->
<SomePane xmlns="..." fx:controller="my.package.ViewController">
<!-- ... -->
<Button fx:id="exitBtn" contentDisplay="CENTER" mnemonicParsing="false" onAction="#handleButtonClick" text="Exit" HBox.hgrow="NEVER" HBox.margin="$x1" />
<Button fx:id="openBtn" contentDisplay="CENTER" mnemonicParsing="false" onAction="#handleButtonClick" text="Open" HBox.hgrow="NEVER" HBox.margin="$x1" />
</SomePane>
Finally, load the FXML from a class other than your controller class (maybe, but not necessarily, your Application class) with
Parent root = FXMLLoader.load(getClass().getResource("path/to/fxml"));
Scene scene = new Scene(root);
// etc...

Simple Javafx TreeView throws Nullpointer Exception

I've got the following classes:
Main:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
BorderPane root = FXMLLoader.load(getClass().getResource("../view/PersonOverview.fxml"));
AnchorPane view2 = FXMLLoader.load(getClass().getResource("../view/view2.fxml"));
root.setLeft(view2);
primaryStage.setScene(new Scene(root, 1000, 600));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
TreeController:
public class TreeController implements Initializable {
//Set icon for folder
Node folderIcon = new ImageView(new Image(this.getClass().getResourceAsStream("../icon/icon.jpg")));
//Set root
TreeItem<String> root;
#FXML TreeView<String> tree;
//Set other Items
private TreeItem<String> item1 = new TreeItem<String>("item1", folderIcon);
private TreeItem<String> item2 = new TreeItem<String>("item2", folderIcon);
private TreeItem<String> item3 = new TreeItem<String>("item3", folderIcon);
private TreeItem<String> item4 = new TreeItem<String>("item4", folderIcon);
private TreeItem<String> item5 = new TreeItem<String>("item5", folderIcon);
//Add Children to root
private void makeChildren() {
root.getChildren().add(item1);
root.getChildren().add(item2);
root.getChildren().add(item3);
root.getChildren().add(item4);
root.getChildren().add(item5);
}
#Override
public void initialize(URL location, ResourceBundle resources) {
root = new TreeItem<String>("root", folderIcon);
makeChildren();
root.setExpanded(true);
tree.setRoot(root);
}
}
And of course my view2 fxml file:
<AnchorPane
maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity"
prefHeight="400.0" prefWidth="354.0"
xmlns="http://javafx.com/javafx/8.0.40"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="control.TreeController">
<children>
<TreeView
layoutX="69.0" layoutY="118.0"
prefHeight="400.0" prefWidth="354.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
Now the problem I have is that it will throw a Nullpointer Exception at tree.setRoot(root);
And a ConstructLoad Exception in my Main at:
AnchorPane view2 = FXMLLoader.load(getClass().getResource("../view/view2.fxml"));
I'm still learning this stuff but I was told that when using FXML, you don't need to initialize TreeViews using "new" as the #FXML annotation will already take care of this with tree.setRoot(root).
Sorry for such a noobish question but I've been googling for the past 2 hours and haven't gotten any wiser.
I'm still learning this stuff but I was told that when using FXML, you don't need to initialize TreeViews using "new" as the #FXML annotation will already take care of this with tree.setRoot(root).
You guessed right, but in order for JavaFX to inject your Treeview (= make the "new" for you) you need to declare something like:
<Treeview fx:id="tree" />
in view2.fxml.
With the fx:id attribute setted to the same name as your Treeview variable in Java code.

Categories