Share model with nested controller - java

I'm trying to build a simple GUI with JavaFX using SceneBuilder, where I'm using a MenuItem (in Main.fxml) to select a root folder. The folder's contents are then listed in a TextArea that again is wrapped in a TabPane (FileListTab.fxml, nested FXML that is included in Main.fxml).
I used this post as a starting point to get used to MVC. Unfortunately I don't know how to make my nested FXML listen or be bound to the outer one since I'm not explicitly calling it. Right now I'm stuck just to display my chosen folder in a label.
My minimal working code right now looks like this:
Main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="MainController">
<top>
<MenuBar BorderPane.alignment="CENTER">
<menus>
<Menu mnemonicParsing="false" text="File">
<items>
<MenuItem mnemonicParsing="false" onAction="#browseInputFolder" text="Open folder" />
</items>
</Menu>
</menus>
</MenuBar>
</top>
<center>
<TabPane prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
<tabs>
<Tab text="File listing">
<content>
<fx:include fx:id="analysisTab" source="FileListTab.fxml" />
</content>
</Tab>
</tabs>
</TabPane>
</center>
</BorderPane>
FileListTab.fxml
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" spacing="15.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="FileListController">
<children>
<HBox spacing="10.0">
<children>
<Label minWidth="100.0" text="Root folder:" />
<Label fx:id="label_rootFolder" />
</children>
</HBox>
<TextArea prefHeight="200.0" prefWidth="200.0" />
<HBox spacing="10.0">
<children>
<Label minWidth="100.0" text="Found files:" />
<Label fx:id="label_filesFound" />
</children>
</HBox>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</VBox>
Model.java (the supposed to be shared model between controllers)
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Model {
private StringProperty rootFolder;
public String getRootFolder() {
return rootFolderProperty().get();
}
public StringProperty rootFolderProperty() {
if (rootFolder == null)
rootFolder = new SimpleStringProperty();
return rootFolder;
}
public void setRootFolder(String rootFolder) {
this.rootFolderProperty().set(rootFolder);
}
}
NestedGUI.java (Main class)
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.io.IOException;
public class NestedGUI extends Application {
Model model = new Model();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Parent root = null;
try {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getClassLoader().getResource("Main.fxml"));
root = (BorderPane) fxmlLoader.load();
MainController controller = fxmlLoader.getController();
controller.setModel(model);
// This openes another window with the tab's content that is actually displaying the selected root folder
/* FXMLLoader fxmlLoader2 = new FXMLLoader();
fxmlLoader2.setLocation(getClass().getClassLoader().getResource("FileListTab.fxml"));
VBox vBox = (VBox) fxmlLoader2.load();
FileListController listController = fxmlLoader2.getController();
listController.setModel(model);
Scene scene = new Scene(vBox);
Stage stage = new Stage();
stage.setScene(scene);
stage.show();*/
} catch (IOException e) {
e.printStackTrace();
}
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
MainController.java
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import java.io.File;
public class MainController {
Model model;
public void setModel(Model model) {
this.model = model;
}
public void browseInputFolder() {
DirectoryChooser chooser = new DirectoryChooser();
chooser.setTitle("Select folder");
File folder = chooser.showDialog(new Stage());
if (folder == null)
return;
String inputFolderPath = folder.getAbsolutePath() + File.separator;
model.setRootFolder(inputFolderPath);
System.out.print(inputFolderPath);
}
}
FileListController.java
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class FileListController {
Model model;
#FXML
Label label_rootFolder;
public void setModel(Model model) {
label_rootFolder.textProperty().unbind();
this.model = model;
label_rootFolder.textProperty().bind(model.rootFolderProperty());
}
}
I looked through various posts here on SO, but either I didn't understand the answers or others had different problems.
Can somebody give me some pointers? (hints to solve this, code snippets, links...) It looks like a pretty basic FXML-problem, but I just don't get it.

Simple solution
One option is just to inject the "nested controller" into the main controller as described in the FXML documentation.
The rule is that the field name for the controller should be the fx:id for the <fx:include> with the string "Controller" appended. So in your case, fx:id="analysisTab" and so the field will be FileListController analysisTabController. Once you have done that, you can pass the model to the nested controller when it is set in the main controller:
public class MainController {
Model model;
#FXML
private FileListController analysisTabController ;
public void setModel(Model model) {
this.model = model;
analysisTabController.setModel(model);
}
// ...
}
Advanced solution
One drawback to the simple solution above is that you have to propagate the model manually to all nested controllers, which can become tricky to maintain (especially if you have multiple levels of <fx:include>s). Another drawback is that you are setting the model after the controller is created and initialized (so, for example, the model is not available in the initialize() method, which is where you would most naturally like to use it).
A more advanced approach is to set a controllerFactory on the FXMLLoader. The controllerFactory is a function that maps the controller class (specified by the fx:controller attribute in the fxml file) to an object (almost always an instance of that class) that will be used as the controller. The default controller factory just invokes the no-argument constructor on the class. You can use this to invoke a constructor taking the model, so the model is available as soon as the controller is instantiated.
If you set a controller factory, the same controller factory is used for any included fxml files.
So you could rewrite your controllers to have constructors taking a model instance:
public class MainController {
private final Model model;
public MainController(Model model) {
this.model = model;
}
public void browseInputFolder() {
DirectoryChooser chooser = new DirectoryChooser();
chooser.setTitle("Select folder");
File folder = chooser.showDialog(new Stage());
if (folder == null)
return;
String inputFolderPath = folder.getAbsolutePath() + File.separator;
model.setRootFolder(inputFolderPath);
System.out.print(inputFolderPath);
}
}
and in the FileListController this means you can now access the model directly in the initialize() method:
public class FileListController {
private final Model model;
#FXML
Label label_rootFolder;
public FileListController(Model model) {
this.model = model ;
}
public void initialize() {
label_rootFolder.textProperty().bind(model.rootFolderProperty());
}
}
Now your application class needs to create a controller factory that invokes these constructors. This is the tricky part: you probably want to use some reflection here and implement logic of the form: "if the controller class has a constructor taking a model, invoke it with the (shared) model instance; otherwise invoke the default constructor". This looks like:
public class NestedGUI extends Application {
Model model = new Model();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Parent root = null;
try {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getClassLoader().getResource("Main.fxml"));
fxmlLoader.setControllerFactory((Class<?> type) -> {
try {
for (Constructor<?> c : type.getConstructors()) {
if (c.getParameterCount() == 1 && c.getParameterTypes()[0] == Model.class) {
return c.newInstance(model);
}
}
// default behavior: invoke no-arg constructor:
return type.newInstance();
} catch (Exception exc) {
throw new RuntimeException(exc);
}
});
root = (BorderPane) fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
At this point you are basically one step along the way to creating a dependency injection framework (you are injecting the model into the controllers using a factory class...)! So you might just consider using one instead of creating one from scratch. afterburner.fx is a popular dependency-injection framework for JavaFX, and the core of the implementation is essentially the ideas in the code above.

An option I prefer is to use a custom component, instead of the <fx:include fx:id="analysisTab" source="FileListTab.fxml" />. So in Main.fxml, replace the <fx:include> line with:
<FileList fx:id="fileList"></FileList>
FileList is our new, custom component. You must also add <?import yourpackage.*?> to the top of Main.fxml, to make the classes of yourpackage available to FXML. (Obviously, yourpackage is the package containing all the classes and files in this question.)
Add below is the class for the custom component in yourpackage.FileList.java; mostly your code from the FileListController + the code required to load the FXML. Note however that it extends a JavaFX component, the VBox, making it a FXML component itself. The VBox was the root component in your FileListTab.fxml and must also be declared in the type attribute of the FileList.fxml below.
package yourpackage;
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
public class FileList extends VBox {
private Model model; // NOT REALLY NEEDED! KEEPING IT BECAUSE YOUR FileListController HAD IT TOO...
#FXML
Label label_rootFolder;
public FileList() {
java.net.URL url = getClass().getResource("/yourpackage/FileList.fxml");
FXMLLoader fxmlLoader = new FXMLLoader(url);
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
}
catch( IOException e ) {
throw new RuntimeException(e);
}
}
public void setModel(Model model) {
label_rootFolder.textProperty().unbind();
this.model = model; // NOT REALLY NEEDED!
label_rootFolder.textProperty().bind(model.rootFolderProperty());
}
}
And the FileList.fxml. This is your own FileListTab.fxml, having its root VBox node replaced by the fx:root and the type="javafx.scene.layout.VBox" attribute, keeping all other attributes the same:
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2"
type="javafx.scene.layout.VBox"
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" spacing="15.0"
>
<children>
<HBox spacing="10.0">
<children>
<Label minWidth="100.0" text="Root folder:" />
<Label fx:id="label_rootFolder" />
</children>
</HBox>
<TextArea prefHeight="200.0" prefWidth="200.0" />
<HBox spacing="10.0">
<children>
<Label minWidth="100.0" text="Found files:" />
<Label fx:id="label_filesFound" />
</children>
</HBox>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</fx:root>
Did you notice the fx:id in the <FileList> component above? You can now inject it in the MainController:
#FXML
private FileList fileList;
And propagate the setModel() call:
// in MainController
public void setModel(Model model) {
this.model = model;
this.fileList.setModel(model);
}

Related

"Welcome, null" but I want to see Welcome, {nameOfLoggedInUser} [duplicate]

How can I pass parameters to a secondary window in javafx? Is there a way to communicate with the corresponding controller?
For example:
The user chooses a customer from a TableView and a new window is opened, showing the customer's info.
Stage newStage = new Stage();
try
{
AnchorPane page = (AnchorPane) FXMLLoader.load(HectorGestion.class.getResource(fxmlResource));
Scene scene = new Scene(page);
newStage.setScene(scene);
newStage.setTitle(windowTitle);
newStage.setResizable(isResizable);
if(showRightAway)
{
newStage.show();
}
}
newStage would be the new window. The problem is, I can't find a way to tell the controller where to look for the customer's info (by passing the id as parameter).
Any ideas?
Using MVC
Most of this answer focuses on a direct call to pass a parameter from a calling class to the controller.
If instead, you want to decouple the caller and controller and use a more general architecture involving a model class with settable and listenable properties to achieve inter-controller communication, see the following basic overview:
Applying MVC With JavaFx
Recommended Approach
This answer enumerates different mechanisms for passing parameters to FXML controllers.
For small applications I highly recommend passing parameters directly from the caller to the controller - it's simple, straightforward and requires no extra frameworks.
For larger, more complicated applications, it would be worthwhile investigating if you want to use Dependency Injection or Event Bus mechanisms within your application.
Passing Parameters Directly From the Caller to the Controller
Pass custom data to an FXML controller by retrieving the controller from the FXML loader instance and calling a method on the controller to initialize it with the required data values.
Something like the following code:
public Stage showCustomerDialog(Customer customer) {
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"customerDialog.fxml"
)
);
Stage stage = new Stage(StageStyle.DECORATED);
stage.setScene(
new Scene(loader.load())
);
CustomerDialogController controller = loader.getController();
controller.initData(customer);
stage.show();
return stage;
}
...
class CustomerDialogController {
#FXML private Label customerName;
void initialize() {}
void initData(Customer customer) {
customerName.setText(customer.getName());
}
}
A new FXMLLoader is constructed as shown in the sample code i.e. new FXMLLoader(location). The location is a URL and you can generate such a URL from an FXML resource by:
new FXMLLoader(getClass().getResource("sample.fxml"));
Be careful NOT to use a static load function on the FXMLLoader, or you will not be able to get your controller from your loader instance.
FXMLLoader instances themselves never know anything about domain objects. You do not directly pass application specific domain objects into the FXMLLoader constructor, instead you:
Construct an FXMLLoader based upon fxml markup at a specified location
Get a controller from the FXMLLoader instance.
Invoke methods on the retrieved controller to provide the controller with references to the domain objects.
This blog (by another writer) provides an alternate, but similar, example.
Setting a Controller on the FXMLLoader
CustomerDialogController dialogController =
new CustomerDialogController(param1, param2);
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"customerDialog.fxml"
)
);
loader.setController(dialogController);
Pane mainPane = loader.load();
You can construct a new controller in code, passing any parameters you want from your caller into the controller constructor. Once you have constructed a controller, you can set it on an FXMLLoader instance before you invoke the load() instance method.
To set a controller on a loader (in JavaFX 2.x) you CANNOT also define a fx:controller attribute in your fxml file.
Due to the limitation on the fx:controller definition in FXML, I personally prefer getting the controller from the FXMLLoader rather than setting the controller into the FXMLLoader.
Having the Controller Retrieve Parameters from an External Static Method
This method is exemplified by Sergey's answer to Javafx 2.0 How-to Application.getParameters() in a Controller.java file.
Use Dependency Injection
FXMLLoader supports dependency injection systems like Guice, Spring or Java EE CDI by allowing you to set a custom controller factory on the FXMLLoader. This provides a callback that you can use to create the controller instance with dependent values injected by the respective dependency injection system.
An example of JavaFX application and controller dependency injection with Spring is provided in the answer to:
Adding Spring Dependency Injection in JavaFX (JPA Repo, Service)
A really nice, clean dependency injection approach is exemplified by the afterburner.fx framework with a sample air-hacks application that uses it. afterburner.fx relies on JEE6 javax.inject to perform the dependency injection.
Use an Event Bus
Greg Brown, the original FXML specification creator and implementor, often suggests considering use of an event bus, such as the Guava EventBus, for communication between FXML instantiated controllers and other application logic.
The EventBus is a simple but powerful publish/subscribe API with annotations that allows POJOs to communicate with each other anywhere in a JVM without having to refer to each other.
Follow-up Q&A
on first method, why do you return Stage? The method can be void as well because you already giving the command show(); just before return stage;. How do you plan usage by returning the Stage
It is a functional solution to a problem. A stage is returned from the showCustomerDialog function so that a reference to it can be stored by an external class which may wish to do something, such as hide the stage based on a button click in the main window, at a later time. An alternate, object-oriented solution could encapsulate the functionality and stage reference inside a CustomerDialog object or have a CustomerDialog extend Stage. A full example for an object-oriented interface to a custom dialog encapsulating FXML, controller and model data is beyond the scope of this answer, but may make a worthwhile blog post for anybody inclined to create one.
Additional information supplied by StackOverflow user named #dzim
Example for Spring Boot Dependency Injection
The question of how to do it "The Spring Boot Way", there was a discussion about JavaFX 2, which I anserwered in the attached permalink.
The approach is still valid and tested in March 2016, on Spring Boot v1.3.3.RELEASE:
https://stackoverflow.com/a/36310391/1281217
Sometimes, you might want to pass results back to the caller, in which case you can check out the answer to the related question:
JavaFX FXML Parameter passing from Controller A to B and back
I realize this is a very old post and has some great answers already,
but I wanted to make a simple MCVE to demonstrate one such approach and allow new coders a way to quickly see the concept in action.
In this example, we will use 5 files:
Main.java - Simply used to start the application and call the first controller.
Controller1.java - The controller for the first FXML layout.
Controller2.java - The controller for the second FXML layout.
Layout1.fxml - The FXML layout for the first scene.
Layout2.fxml - The FXML layout for the second scene.
All files are listed in their entirety at the bottom of this post.
The Goal: To demonstrate passing values from Controller1 to Controller2 and vice versa.
The Program Flow:
The first scene contains a TextField, a Button, and a Label. When the Button is clicked, the second window is loaded and displayed, including the text entered in the TextField.
Within the second scene, there is also a TextField, a Button, and a Label. The Label will display the text entered in the TextField on the first scene.
Upon entering text in the second scene's TextField and clicking its Button, the first scene's Label is updated to show the entered text.
This is a very simple demonstration and could surely stand for some improvement, but should make the concept very clear.
The code itself is also commented with some details of what is happening and how.
THE CODE
Main.java:
import javafx.application.Application;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Create the first controller, which loads Layout1.fxml within its own constructor
Controller1 controller1 = new Controller1();
// Show the new stage
controller1.showStage();
}
}
Controller1.java:
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
public class Controller1 {
// Holds this controller's Stage
private final Stage thisStage;
// Define the nodes from the Layout1.fxml file. This allows them to be referenced within the controller
#FXML
private TextField txtToSecondController;
#FXML
private Button btnOpenLayout2;
#FXML
private Label lblFromController2;
public Controller1() {
// Create the new stage
thisStage = new Stage();
// Load the FXML file
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout1.fxml"));
// Set this class as the controller
loader.setController(this);
// Load the scene
thisStage.setScene(new Scene(loader.load()));
// Setup the window/stage
thisStage.setTitle("Passing Controllers Example - Layout1");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Show the stage that was loaded in the constructor
*/
public void showStage() {
thisStage.showAndWait();
}
/**
* The initialize() method allows you set setup your scene, adding actions, configuring nodes, etc.
*/
#FXML
private void initialize() {
// Add an action for the "Open Layout2" button
btnOpenLayout2.setOnAction(event -> openLayout2());
}
/**
* Performs the action of loading and showing Layout2
*/
private void openLayout2() {
// Create the second controller, which loads its own FXML file. We pass a reference to this controller
// using the keyword [this]; that allows the second controller to access the methods contained in here.
Controller2 controller2 = new Controller2(this);
// Show the new stage/window
controller2.showStage();
}
/**
* Returns the text entered into txtToSecondController. This allows other controllers/classes to view that data.
*/
public String getEnteredText() {
return txtToSecondController.getText();
}
/**
* Allows other controllers to set the text of this layout's Label
*/
public void setTextFromController2(String text) {
lblFromController2.setText(text);
}
}
Controller2.java:
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
public class Controller2 {
// Holds this controller's Stage
private Stage thisStage;
// Will hold a reference to the first controller, allowing us to access the methods found there.
private final Controller1 controller1;
// Add references to the controls in Layout2.fxml
#FXML
private Label lblFromController1;
#FXML
private TextField txtToFirstController;
#FXML
private Button btnSetLayout1Text;
public Controller2(Controller1 controller1) {
// We received the first controller, now let's make it usable throughout this controller.
this.controller1 = controller1;
// Create the new stage
thisStage = new Stage();
// Load the FXML file
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout2.fxml"));
// Set this class as the controller
loader.setController(this);
// Load the scene
thisStage.setScene(new Scene(loader.load()));
// Setup the window/stage
thisStage.setTitle("Passing Controllers Example - Layout2");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Show the stage that was loaded in the constructor
*/
public void showStage() {
thisStage.showAndWait();
}
#FXML
private void initialize() {
// Set the label to whatever the text entered on Layout1 is
lblFromController1.setText(controller1.getEnteredText());
// Set the action for the button
btnSetLayout1Text.setOnAction(event -> setTextOnLayout1());
}
/**
* Calls the "setTextFromController2()" method on the first controller to update its Label
*/
private void setTextOnLayout1() {
controller1.setTextFromController2(txtToFirstController.getText());
}
}
Layout1.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
<VBox alignment="CENTER" spacing="10.0">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<Label style="-fx-font-weight: bold;" text="This is Layout1!"/>
<HBox alignment="CENTER_LEFT" spacing="10.0">
<Label text="Enter Text:"/>
<TextField fx:id="txtToSecondController"/>
<Button fx:id="btnOpenLayout2" mnemonicParsing="false" text="Open Layout2"/>
</HBox>
<VBox alignment="CENTER">
<Label text="Text From Controller2:"/>
<Label fx:id="lblFromController2" text="Nothing Yet!"/>
</VBox>
</VBox>
</AnchorPane>
Layout2.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
<VBox alignment="CENTER" spacing="10.0">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<Label style="-fx-font-weight: bold;" text="Welcome to Layout 2!"/>
<VBox alignment="CENTER">
<Label text="Text From Controller1:"/>
<Label fx:id="lblFromController1" text="Nothing Yet!"/>
</VBox>
<HBox alignment="CENTER_LEFT" spacing="10.0">
<Label text="Enter Text:"/>
<TextField fx:id="txtToFirstController"/>
<Button fx:id="btnSetLayout1Text" mnemonicParsing="false" text="Set Text on Layout1"/>
</HBox>
</VBox>
</AnchorPane>
Here is an example for passing parameters to a fxml document through namespace.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/null" xmlns:fx="http://javafx.com/fxml/1">
<BorderPane>
<center>
<Label text="$labelText"/>
</center>
</BorderPane>
</VBox>
Define value External Text for namespace variable labelText:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class NamespaceParameterExampleApplication extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws IOException {
final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("namespace-parameter-example.fxml"));
fxmlLoader.getNamespace()
.put("labelText", "External Text");
final Parent root = fxmlLoader.load();
primaryStage.setTitle("Namespace Parameter Example");
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
}
javafx.scene.Node class has a pair of methods
setUserData(Object)
and
Object getUserData()
Which you could use to add your info to the Node.
So, you can call page.setUserData(info);
And controller can check, if info is set. Also, you could use ObjectProperty for back-forward data transfering, if needed.
Observe a documentation here:
http://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html
Before the phrase "In the first version, the handleButtonAction() is tagged with #FXML to allow markup defined in the controller's document to invoke it. In the second example, the button field is annotated to allow the loader to set its value. The initialize() method is similarly annotated."
So, you need to associate a controller with a node, and set a user data to the node.
This WORKS ..
Remember first time you print the passing value you will get null,
You can use it after your windows loaded , same for everything you want to code for any other component.
First Controller
try {
Stage st = new Stage();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/inty360/free/form/MainOnline.fxml"));
Parent sceneMain = loader.load();
MainOnlineController controller = loader.<MainOnlineController>getController();
controller.initVariable(99L);
Scene scene = new Scene(sceneMain);
st.setScene(scene);
st.setMaximized(true);
st.setTitle("My App");
st.show();
} catch (IOException ex) {
Logger.getLogger(LoginController.class.getName()).log(Level.SEVERE, null, ex);
}
Another Controller
public void initVariable(Long id_usuario){
this.id_usuario = id_usuario;
label_usuario_nombre.setText(id_usuario.toString());
}
You have to create one Context Class.
public class Context {
private final static Context instance = new Context();
public static Context getInstance() {
return instance;
}
private Connection con;
public void setConnection(Connection con)
{
this.con=con;
}
public Connection getConnection() {
return con;
}
private TabRoughController tabRough;
public void setTabRough(TabRoughController tabRough) {
this.tabRough=tabRough;
}
public TabRoughController getTabRough() {
return tabRough;
}
}
You have to just set instance of controller in initialization using
Context.getInstance().setTabRough(this);
and you can use it from your whole application just using
TabRoughController cont=Context.getInstance().getTabRough();
Now you can pass parameter to any controller from whole application.
Yes you can.
You need to add in the first controller:
YourController controller = loader.getController();
controller.setclient(client);
Then in the second one declare a client, then at the bottom of your controller:
public void setclien(Client c) {
this.client = c;
}
Here is an example for using a controller injected by Guice.
/**
* Loads a FXML file and injects its controller from the given Guice {#code Provider}
*/
public abstract class GuiceFxmlLoader {
public GuiceFxmlLoader(Stage stage, Provider<?> provider) {
mStage = Objects.requireNonNull(stage);
mProvider = Objects.requireNonNull(provider);
}
/**
* #return the FXML file name
*/
public abstract String getFileName();
/**
* Load FXML, set its controller with given {#code Provider}, and add it to {#code Stage}.
*/
public void loadView() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource(getFileName()));
loader.setControllerFactory(p -> mProvider.get());
Node view = loader.load();
setViewInStage(view);
}
catch (IOException ex) {
LOGGER.error("Failed to load FXML: " + getFileName(), ex);
}
}
private void setViewInStage(Node view) {
BorderPane pane = (BorderPane)mStage.getScene().getRoot();
pane.setCenter(view);
}
private static final Logger LOGGER = Logger.getLogger(GuiceFxmlLoader.class);
private final Stage mStage;
private final Provider<?> mProvider;
}
Here is a concrete implementation of the loader:
public class ConcreteViewLoader extends GuiceFxmlLoader {
#Inject
public ConcreteViewLoader(Stage stage, Provider<MyController> provider) {
super(stage, provider);
}
#Override
public String getFileName() {
return "my_view.fxml";
}
}
Note this example loads the view into the center of a BoarderPane that is the root of the Scene in the Stage. This is irrelevant to the example (implementation detail of my specific use case) but decided to leave it in as some may find it useful.
You can decide to use a public observable list to store public data, or just create a public setter method to store data and retrieve from the corresponding controller
Why answer a 6 year old question ?
One the most fundamental concepts working with any programming language is how to navigate from one (window, form or page) to another. Also while doing this navigation the developer often wants to pass data from one (window, form or page) and display or use the data passed
While most of the answers here provide good to excellent examples how to accomplish this we thought we would kick it up a notch or two or three
We said three because we will navigate between three (window, form or page) and use the concept of static variables to pass data around the (window, form or page)
We will also include some decision making code while we navigate
public class Start extends Application {
#Override
public void start(Stage stage) throws Exception {
// This is MAIN Class which runs first
Parent root = FXMLLoader.load(getClass().getResource("start.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setResizable(false);// This sets the value for all stages
stage.setTitle("Start Page");
stage.show();
stage.sizeToScene();
}
public static void main(String[] args) {
launch(args);
}
}
Start Controller
public class startController implements Initializable {
#FXML Pane startPane,pageonePane;
#FXML Button btnPageOne;
#FXML TextField txtStartValue;
public Stage stage;
public static int intSETonStartController;
String strSETonStartController;
#FXML
private void toPageOne() throws IOException{
strSETonStartController = txtStartValue.getText().trim();
// yourString != null && yourString.trim().length() > 0
// int L = testText.length();
// if(L == 0){
// System.out.println("LENGTH IS "+L);
// return;
// }
/* if (testText.matches("[1-2]") && !testText.matches("^\\s*$"))
Second Match is regex for White Space NOT TESTED !
*/
String testText = txtStartValue.getText().trim();
// NOTICE IF YOU REMOVE THE * CHARACTER FROM "[1-2]*"
// NO NEED TO CHECK LENGTH it also permited 12 or 11 as valid entry
// =================================================================
if (testText.matches("[1-2]")) {
intSETonStartController = Integer.parseInt(strSETonStartController);
}else{
txtStartValue.setText("Enter 1 OR 2");
return;
}
System.out.println("You Entered = "+intSETonStartController);
stage = (Stage)startPane.getScene().getWindow();// pane you are ON
pageonePane = FXMLLoader.load(getClass().getResource("pageone.fxml"));// pane you are GOING TO
Scene scene = new Scene(pageonePane);// pane you are GOING TO
stage.setScene(scene);
stage.setTitle("Page One");
stage.show();
stage.sizeToScene();
stage.centerOnScreen();
}
private void doGET(){
// Why this testing ?
// strSENTbackFROMPageoneController is null because it is set on Pageone
// =====================================================================
txtStartValue.setText(strSENTbackFROMPageoneController);
if(intSETonStartController == 1){
txtStartValue.setText(str);
}
System.out.println("== doGET WAS RUN ==");
if(txtStartValue.getText() == null){
txtStartValue.setText("");
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// This Method runs every time startController is LOADED
doGET();
}
}
Page One Controller
public class PageoneController implements Initializable {
#FXML Pane startPane,pageonePane,pagetwoPane;
#FXML Button btnOne,btnTwo;
#FXML TextField txtPageOneValue;
public static String strSENTbackFROMPageoneController;
public Stage stage;
#FXML
private void onBTNONE() throws IOException{
stage = (Stage)pageonePane.getScene().getWindow();// pane you are ON
pagetwoPane = FXMLLoader.load(getClass().getResource("pagetwo.fxml"));// pane you are GOING TO
Scene scene = new Scene(pagetwoPane);// pane you are GOING TO
stage.setScene(scene);
stage.setTitle("Page Two");
stage.show();
stage.sizeToScene();
stage.centerOnScreen();
}
#FXML
private void onBTNTWO() throws IOException{
if(intSETonStartController == 2){
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Alert");
alert.setHeaderText("YES to change Text Sent Back");
alert.setResizable(false);
alert.setContentText("Select YES to send 'Alert YES Pressed' Text Back\n"
+ "\nSelect CANCEL send no Text Back\r");// NOTE this is a Carriage return\r
ButtonType buttonTypeYes = new ButtonType("YES");
ButtonType buttonTypeCancel = new ButtonType("CANCEL", ButtonData.CANCEL_CLOSE);
alert.getButtonTypes().setAll(buttonTypeYes, buttonTypeCancel);
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == buttonTypeYes){
txtPageOneValue.setText("Alert YES Pressed");
} else {
System.out.println("canceled");
txtPageOneValue.setText("");
onBack();// Optional
}
}
}
#FXML
private void onBack() throws IOException{
strSENTbackFROMPageoneController = txtPageOneValue.getText();
System.out.println("Text Returned = "+strSENTbackFROMPageoneController);
stage = (Stage)pageonePane.getScene().getWindow();
startPane = FXMLLoader.load(getClass().getResource("start.fxml"));
Scene scene = new Scene(startPane);
stage.setScene(scene);
stage.setTitle("Start Page");
stage.show();
stage.sizeToScene();
stage.centerOnScreen();
}
private void doTEST(){
String fromSTART = String.valueOf(intSETonStartController);
txtPageOneValue.setText("SENT "+fromSTART);
if(intSETonStartController == 1){
btnOne.setVisible(true);
btnTwo.setVisible(false);
System.out.println("INTEGER Value Entered = "+intSETonStartController);
}else{
btnOne.setVisible(false);
btnTwo.setVisible(true);
System.out.println("INTEGER Value Entered = "+intSETonStartController);
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
doTEST();
}
}
Page Two Controller
public class PagetwoController implements Initializable {
#FXML Pane startPane,pagetwoPane;
public Stage stage;
public static String str;
#FXML
private void toStart() throws IOException{
str = "You ON Page Two";
stage = (Stage)pagetwoPane.getScene().getWindow();// pane you are ON
startPane = FXMLLoader.load(getClass().getResource("start.fxml"));// pane you are GOING TO
Scene scene = new Scene(startPane);// pane you are GOING TO
stage.setScene(scene);
stage.setTitle("Start Page");
stage.show();
stage.sizeToScene();
stage.centerOnScreen();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
Below are all the FXML files
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane id="AnchorPane" fx:id="pagetwoPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atwopage.PagetwoController">
<children>
<Button layoutX="227.0" layoutY="62.0" mnemonicParsing="false" onAction="#toStart" text="To Start Page">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Button>
</children>
</AnchorPane>
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane id="AnchorPane" fx:id="startPane" prefHeight="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atwopage.startController">
<children>
<Label focusTraversable="false" layoutX="115.0" layoutY="47.0" text="This is the Start Pane">
<font>
<Font size="18.0" />
</font>
</Label>
<Button fx:id="btnPageOne" focusTraversable="false" layoutX="137.0" layoutY="100.0" mnemonicParsing="false" onAction="#toPageOne" text="To Page One">
<font>
<Font size="18.0" />
</font>
</Button>
<Label focusTraversable="false" layoutX="26.0" layoutY="150.0" text="Enter 1 OR 2">
<font>
<Font size="18.0" />
</font>
</Label>
<TextField fx:id="txtStartValue" layoutX="137.0" layoutY="148.0" prefHeight="28.0" prefWidth="150.0" />
</children>
</AnchorPane>
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane id="AnchorPane" fx:id="pageonePane" prefHeight="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atwopage.PageoneController">
<children>
<Label focusTraversable="false" layoutX="111.0" layoutY="35.0" text="This is Page One Pane">
<font>
<Font size="18.0" />
</font>
</Label>
<Button focusTraversable="false" layoutX="167.0" layoutY="97.0" mnemonicParsing="false" onAction="#onBack" text="BACK">
<font>
<Font size="18.0" />
</font></Button>
<Button fx:id="btnOne" focusTraversable="false" layoutX="19.0" layoutY="97.0" mnemonicParsing="false" onAction="#onBTNONE" text="Button One" visible="false">
<font>
<Font size="18.0" />
</font>
</Button>
<Button fx:id="btnTwo" focusTraversable="false" layoutX="267.0" layoutY="97.0" mnemonicParsing="false" onAction="#onBTNTWO" text="Button Two">
<font>
<Font size="18.0" />
</font>
</Button>
<Label focusTraversable="false" layoutX="19.0" layoutY="152.0" text="Send Anything BACK">
<font>
<Font size="18.0" />
</font>
</Label>
<TextField fx:id="txtPageOneValue" layoutX="195.0" layoutY="150.0" prefHeight="28.0" prefWidth="150.0" />
</children>
</AnchorPane>

JavaFX : remove a component from the component itself

I have a simple JavaFX application with main controller. In this main controller I dynamically add this component :
Added component:
FXML code of this component (paramValue.fxml) :
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.HBox?>
<HBox alignment="CENTER" prefHeight="40.0" prefWidth="833.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.demoSpreadsSheet.ParamValueController">
<children>
<Label prefHeight="95.0" prefWidth="83.0" text="Paramètre : " />
<TextField fx:id="paramName" prefHeight="32.0" prefWidth="300.0" />
<Label prefHeight="95.0" prefWidth="83.0" text="Valeur : ">
<HBox.margin>
<Insets left="40.0" />
</HBox.margin>
</Label>
<TextField fx:id="paramValue" prefHeight="32.0" prefWidth="300.0" />
<Button mnemonicParsing="false" prefHeight="25.0" prefWidth="12.0">
<HBox.margin>
<Insets left="20.0" />
</HBox.margin>
<graphic>
<ImageView fitHeight="26.0" fitWidth="21.0" onMouseClicked="#openSpreadSheet" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="#../../Images/spreadsheet.png" />
</image>
</ImageView>
</graphic>
</Button>
<Button mnemonicParsing="false" onAction="#removeParamValue" prefHeight="25.0" prefWidth="12.0">
<graphic>
<ImageView fitHeight="26.0" fitWidth="21.0" onMouseClicked="#openSpreadSheet" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="#../../Images/remove.png" />
</image>
</ImageView>
</graphic>
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</Button>
</children>
</HBox>
Controller of the component :
package com.example.demoSpreadsSheet;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
public class ParamValueController extends HBox {
#FXML TextField paramName, paramValue;
public ParamValueController() {}
#FXML
public void openSpreadSheet(){
}
#FXML
private void removeParamValue(){
//I don't know if it's possible to remove it here
}
}
The controller where I add my custom component :
package com.example.demoSpreadsSheet;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.io.IOException;
public class DoublonController extends BorderPane {
#FXML VBox paramValueList;
public DoublonController(){}
#FXML
public void initialize() throws IOException {
paramValueList.getChildren().addAll(createParamValueComponent(), createParamValueComponent());
}
private HBox createParamValueComponent() throws IOException {
FXMLLoader paramValueLoader = new FXMLLoader(DoublonController.class.getResource("paramValue.fxml"));
return paramValueLoader.load();
}
#FXML
private void addParamValue() throws IOException {
this.paramValueList.getChildren().add(createParamValueComponent());
}
}
I want to remove this component when the user click on the red cross. The problem is that the redcross calls the function "removeParamValue()" within the class that I want to remove which i think is impossible..
Do you have a workaround ?
Thanks in advance.
There are at least two approaches you can use.
Get the Parent
As mentioned by #kleopatra in a question comment, you can get the parent of the root FXML node from within the "child" controller, check if it is an instance of Pane and, if it is, then modify the parent's children list.
For example:
#FXML
private void removeParamValue() {
var parent = container.getParent();
if (parent instanceof Pane pane) {
pane.getChildren().remove(container);
}
}
This requires you to inject the root FXML element into the controller. I made the name of the field container, whose type would be HBox, but you can name it anything you'd like (just make sure add the corresponding fx:id attribute to the FXML file). I also made use of pattern matching for instanceof, which was finalized in Java 16. If you are not using at least Java 16, then you'll need to manually cast parent to a Pane.
Problem With Your Code
Note you can't use this.getParent(), because the controller itself has never been added to the scene graph. In fact, with how your code is currently written, your controller class should not be extending HBox at all (and I assume a similar problem with your other controller class). Perhaps you meant to use fx:root, but that would involve changing both the root element of your FXML file and how you load the FXML file. Read the linked documentation for more information.
In other words, unless you want to use fx:root, remove the extends HBox and extends BorderPane from your controller classes.
Use a "Callback"
The other approach is to use a callback. Add a method to your ParamValueController which accepts some functional interface (e.g., Runnable):
public void setOnRemove(Runnable action) {
// also declare a field to store the Runnable in
this.action = action;
}
Then your "remove param value" method would look like:
#FXML
private void removeParamValue() {
if (action != null) {
action.run();
}
}
And you'd modify the code that loads the "child" FXML like so:
private HBox createParamValueComponent() throws IOException {
FXMLLoader paramValueLoader = new FXMLLoader(DoublonController.class.getResource("paramValue.fxml"));
HBox root = paramValueLoader.load();
// invoked *after* you load the FXML file
ParamValueController controller = paramValueLoader.getController();
controller.setOnRemove(() -> paramValueList.getChildren().remove(root));
return root;
}
Personally, I prefer this approach as it's safer, more flexible, and in my opinion clearer. But it is also more work than the first approach.

How to load a JavaFX controller with parameters? [duplicate]

How can I pass parameters to a secondary window in javafx? Is there a way to communicate with the corresponding controller?
For example:
The user chooses a customer from a TableView and a new window is opened, showing the customer's info.
Stage newStage = new Stage();
try
{
AnchorPane page = (AnchorPane) FXMLLoader.load(HectorGestion.class.getResource(fxmlResource));
Scene scene = new Scene(page);
newStage.setScene(scene);
newStage.setTitle(windowTitle);
newStage.setResizable(isResizable);
if(showRightAway)
{
newStage.show();
}
}
newStage would be the new window. The problem is, I can't find a way to tell the controller where to look for the customer's info (by passing the id as parameter).
Any ideas?
Using MVC
Most of this answer focuses on a direct call to pass a parameter from a calling class to the controller.
If instead, you want to decouple the caller and controller and use a more general architecture involving a model class with settable and listenable properties to achieve inter-controller communication, see the following basic overview:
Applying MVC With JavaFx
Recommended Approach
This answer enumerates different mechanisms for passing parameters to FXML controllers.
For small applications I highly recommend passing parameters directly from the caller to the controller - it's simple, straightforward and requires no extra frameworks.
For larger, more complicated applications, it would be worthwhile investigating if you want to use Dependency Injection or Event Bus mechanisms within your application.
Passing Parameters Directly From the Caller to the Controller
Pass custom data to an FXML controller by retrieving the controller from the FXML loader instance and calling a method on the controller to initialize it with the required data values.
Something like the following code:
public Stage showCustomerDialog(Customer customer) {
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"customerDialog.fxml"
)
);
Stage stage = new Stage(StageStyle.DECORATED);
stage.setScene(
new Scene(loader.load())
);
CustomerDialogController controller = loader.getController();
controller.initData(customer);
stage.show();
return stage;
}
...
class CustomerDialogController {
#FXML private Label customerName;
void initialize() {}
void initData(Customer customer) {
customerName.setText(customer.getName());
}
}
A new FXMLLoader is constructed as shown in the sample code i.e. new FXMLLoader(location). The location is a URL and you can generate such a URL from an FXML resource by:
new FXMLLoader(getClass().getResource("sample.fxml"));
Be careful NOT to use a static load function on the FXMLLoader, or you will not be able to get your controller from your loader instance.
FXMLLoader instances themselves never know anything about domain objects. You do not directly pass application specific domain objects into the FXMLLoader constructor, instead you:
Construct an FXMLLoader based upon fxml markup at a specified location
Get a controller from the FXMLLoader instance.
Invoke methods on the retrieved controller to provide the controller with references to the domain objects.
This blog (by another writer) provides an alternate, but similar, example.
Setting a Controller on the FXMLLoader
CustomerDialogController dialogController =
new CustomerDialogController(param1, param2);
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"customerDialog.fxml"
)
);
loader.setController(dialogController);
Pane mainPane = loader.load();
You can construct a new controller in code, passing any parameters you want from your caller into the controller constructor. Once you have constructed a controller, you can set it on an FXMLLoader instance before you invoke the load() instance method.
To set a controller on a loader (in JavaFX 2.x) you CANNOT also define a fx:controller attribute in your fxml file.
Due to the limitation on the fx:controller definition in FXML, I personally prefer getting the controller from the FXMLLoader rather than setting the controller into the FXMLLoader.
Having the Controller Retrieve Parameters from an External Static Method
This method is exemplified by Sergey's answer to Javafx 2.0 How-to Application.getParameters() in a Controller.java file.
Use Dependency Injection
FXMLLoader supports dependency injection systems like Guice, Spring or Java EE CDI by allowing you to set a custom controller factory on the FXMLLoader. This provides a callback that you can use to create the controller instance with dependent values injected by the respective dependency injection system.
An example of JavaFX application and controller dependency injection with Spring is provided in the answer to:
Adding Spring Dependency Injection in JavaFX (JPA Repo, Service)
A really nice, clean dependency injection approach is exemplified by the afterburner.fx framework with a sample air-hacks application that uses it. afterburner.fx relies on JEE6 javax.inject to perform the dependency injection.
Use an Event Bus
Greg Brown, the original FXML specification creator and implementor, often suggests considering use of an event bus, such as the Guava EventBus, for communication between FXML instantiated controllers and other application logic.
The EventBus is a simple but powerful publish/subscribe API with annotations that allows POJOs to communicate with each other anywhere in a JVM without having to refer to each other.
Follow-up Q&A
on first method, why do you return Stage? The method can be void as well because you already giving the command show(); just before return stage;. How do you plan usage by returning the Stage
It is a functional solution to a problem. A stage is returned from the showCustomerDialog function so that a reference to it can be stored by an external class which may wish to do something, such as hide the stage based on a button click in the main window, at a later time. An alternate, object-oriented solution could encapsulate the functionality and stage reference inside a CustomerDialog object or have a CustomerDialog extend Stage. A full example for an object-oriented interface to a custom dialog encapsulating FXML, controller and model data is beyond the scope of this answer, but may make a worthwhile blog post for anybody inclined to create one.
Additional information supplied by StackOverflow user named #dzim
Example for Spring Boot Dependency Injection
The question of how to do it "The Spring Boot Way", there was a discussion about JavaFX 2, which I anserwered in the attached permalink.
The approach is still valid and tested in March 2016, on Spring Boot v1.3.3.RELEASE:
https://stackoverflow.com/a/36310391/1281217
Sometimes, you might want to pass results back to the caller, in which case you can check out the answer to the related question:
JavaFX FXML Parameter passing from Controller A to B and back
I realize this is a very old post and has some great answers already,
but I wanted to make a simple MCVE to demonstrate one such approach and allow new coders a way to quickly see the concept in action.
In this example, we will use 5 files:
Main.java - Simply used to start the application and call the first controller.
Controller1.java - The controller for the first FXML layout.
Controller2.java - The controller for the second FXML layout.
Layout1.fxml - The FXML layout for the first scene.
Layout2.fxml - The FXML layout for the second scene.
All files are listed in their entirety at the bottom of this post.
The Goal: To demonstrate passing values from Controller1 to Controller2 and vice versa.
The Program Flow:
The first scene contains a TextField, a Button, and a Label. When the Button is clicked, the second window is loaded and displayed, including the text entered in the TextField.
Within the second scene, there is also a TextField, a Button, and a Label. The Label will display the text entered in the TextField on the first scene.
Upon entering text in the second scene's TextField and clicking its Button, the first scene's Label is updated to show the entered text.
This is a very simple demonstration and could surely stand for some improvement, but should make the concept very clear.
The code itself is also commented with some details of what is happening and how.
THE CODE
Main.java:
import javafx.application.Application;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Create the first controller, which loads Layout1.fxml within its own constructor
Controller1 controller1 = new Controller1();
// Show the new stage
controller1.showStage();
}
}
Controller1.java:
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
public class Controller1 {
// Holds this controller's Stage
private final Stage thisStage;
// Define the nodes from the Layout1.fxml file. This allows them to be referenced within the controller
#FXML
private TextField txtToSecondController;
#FXML
private Button btnOpenLayout2;
#FXML
private Label lblFromController2;
public Controller1() {
// Create the new stage
thisStage = new Stage();
// Load the FXML file
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout1.fxml"));
// Set this class as the controller
loader.setController(this);
// Load the scene
thisStage.setScene(new Scene(loader.load()));
// Setup the window/stage
thisStage.setTitle("Passing Controllers Example - Layout1");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Show the stage that was loaded in the constructor
*/
public void showStage() {
thisStage.showAndWait();
}
/**
* The initialize() method allows you set setup your scene, adding actions, configuring nodes, etc.
*/
#FXML
private void initialize() {
// Add an action for the "Open Layout2" button
btnOpenLayout2.setOnAction(event -> openLayout2());
}
/**
* Performs the action of loading and showing Layout2
*/
private void openLayout2() {
// Create the second controller, which loads its own FXML file. We pass a reference to this controller
// using the keyword [this]; that allows the second controller to access the methods contained in here.
Controller2 controller2 = new Controller2(this);
// Show the new stage/window
controller2.showStage();
}
/**
* Returns the text entered into txtToSecondController. This allows other controllers/classes to view that data.
*/
public String getEnteredText() {
return txtToSecondController.getText();
}
/**
* Allows other controllers to set the text of this layout's Label
*/
public void setTextFromController2(String text) {
lblFromController2.setText(text);
}
}
Controller2.java:
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
public class Controller2 {
// Holds this controller's Stage
private Stage thisStage;
// Will hold a reference to the first controller, allowing us to access the methods found there.
private final Controller1 controller1;
// Add references to the controls in Layout2.fxml
#FXML
private Label lblFromController1;
#FXML
private TextField txtToFirstController;
#FXML
private Button btnSetLayout1Text;
public Controller2(Controller1 controller1) {
// We received the first controller, now let's make it usable throughout this controller.
this.controller1 = controller1;
// Create the new stage
thisStage = new Stage();
// Load the FXML file
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Layout2.fxml"));
// Set this class as the controller
loader.setController(this);
// Load the scene
thisStage.setScene(new Scene(loader.load()));
// Setup the window/stage
thisStage.setTitle("Passing Controllers Example - Layout2");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Show the stage that was loaded in the constructor
*/
public void showStage() {
thisStage.showAndWait();
}
#FXML
private void initialize() {
// Set the label to whatever the text entered on Layout1 is
lblFromController1.setText(controller1.getEnteredText());
// Set the action for the button
btnSetLayout1Text.setOnAction(event -> setTextOnLayout1());
}
/**
* Calls the "setTextFromController2()" method on the first controller to update its Label
*/
private void setTextOnLayout1() {
controller1.setTextFromController2(txtToFirstController.getText());
}
}
Layout1.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
<VBox alignment="CENTER" spacing="10.0">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<Label style="-fx-font-weight: bold;" text="This is Layout1!"/>
<HBox alignment="CENTER_LEFT" spacing="10.0">
<Label text="Enter Text:"/>
<TextField fx:id="txtToSecondController"/>
<Button fx:id="btnOpenLayout2" mnemonicParsing="false" text="Open Layout2"/>
</HBox>
<VBox alignment="CENTER">
<Label text="Text From Controller2:"/>
<Label fx:id="lblFromController2" text="Nothing Yet!"/>
</VBox>
</VBox>
</AnchorPane>
Layout2.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1">
<VBox alignment="CENTER" spacing="10.0">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<Label style="-fx-font-weight: bold;" text="Welcome to Layout 2!"/>
<VBox alignment="CENTER">
<Label text="Text From Controller1:"/>
<Label fx:id="lblFromController1" text="Nothing Yet!"/>
</VBox>
<HBox alignment="CENTER_LEFT" spacing="10.0">
<Label text="Enter Text:"/>
<TextField fx:id="txtToFirstController"/>
<Button fx:id="btnSetLayout1Text" mnemonicParsing="false" text="Set Text on Layout1"/>
</HBox>
</VBox>
</AnchorPane>
Here is an example for passing parameters to a fxml document through namespace.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/null" xmlns:fx="http://javafx.com/fxml/1">
<BorderPane>
<center>
<Label text="$labelText"/>
</center>
</BorderPane>
</VBox>
Define value External Text for namespace variable labelText:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class NamespaceParameterExampleApplication extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws IOException {
final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("namespace-parameter-example.fxml"));
fxmlLoader.getNamespace()
.put("labelText", "External Text");
final Parent root = fxmlLoader.load();
primaryStage.setTitle("Namespace Parameter Example");
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
}
javafx.scene.Node class has a pair of methods
setUserData(Object)
and
Object getUserData()
Which you could use to add your info to the Node.
So, you can call page.setUserData(info);
And controller can check, if info is set. Also, you could use ObjectProperty for back-forward data transfering, if needed.
Observe a documentation here:
http://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html
Before the phrase "In the first version, the handleButtonAction() is tagged with #FXML to allow markup defined in the controller's document to invoke it. In the second example, the button field is annotated to allow the loader to set its value. The initialize() method is similarly annotated."
So, you need to associate a controller with a node, and set a user data to the node.
This WORKS ..
Remember first time you print the passing value you will get null,
You can use it after your windows loaded , same for everything you want to code for any other component.
First Controller
try {
Stage st = new Stage();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/inty360/free/form/MainOnline.fxml"));
Parent sceneMain = loader.load();
MainOnlineController controller = loader.<MainOnlineController>getController();
controller.initVariable(99L);
Scene scene = new Scene(sceneMain);
st.setScene(scene);
st.setMaximized(true);
st.setTitle("My App");
st.show();
} catch (IOException ex) {
Logger.getLogger(LoginController.class.getName()).log(Level.SEVERE, null, ex);
}
Another Controller
public void initVariable(Long id_usuario){
this.id_usuario = id_usuario;
label_usuario_nombre.setText(id_usuario.toString());
}
You have to create one Context Class.
public class Context {
private final static Context instance = new Context();
public static Context getInstance() {
return instance;
}
private Connection con;
public void setConnection(Connection con)
{
this.con=con;
}
public Connection getConnection() {
return con;
}
private TabRoughController tabRough;
public void setTabRough(TabRoughController tabRough) {
this.tabRough=tabRough;
}
public TabRoughController getTabRough() {
return tabRough;
}
}
You have to just set instance of controller in initialization using
Context.getInstance().setTabRough(this);
and you can use it from your whole application just using
TabRoughController cont=Context.getInstance().getTabRough();
Now you can pass parameter to any controller from whole application.
Yes you can.
You need to add in the first controller:
YourController controller = loader.getController();
controller.setclient(client);
Then in the second one declare a client, then at the bottom of your controller:
public void setclien(Client c) {
this.client = c;
}
Here is an example for using a controller injected by Guice.
/**
* Loads a FXML file and injects its controller from the given Guice {#code Provider}
*/
public abstract class GuiceFxmlLoader {
public GuiceFxmlLoader(Stage stage, Provider<?> provider) {
mStage = Objects.requireNonNull(stage);
mProvider = Objects.requireNonNull(provider);
}
/**
* #return the FXML file name
*/
public abstract String getFileName();
/**
* Load FXML, set its controller with given {#code Provider}, and add it to {#code Stage}.
*/
public void loadView() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource(getFileName()));
loader.setControllerFactory(p -> mProvider.get());
Node view = loader.load();
setViewInStage(view);
}
catch (IOException ex) {
LOGGER.error("Failed to load FXML: " + getFileName(), ex);
}
}
private void setViewInStage(Node view) {
BorderPane pane = (BorderPane)mStage.getScene().getRoot();
pane.setCenter(view);
}
private static final Logger LOGGER = Logger.getLogger(GuiceFxmlLoader.class);
private final Stage mStage;
private final Provider<?> mProvider;
}
Here is a concrete implementation of the loader:
public class ConcreteViewLoader extends GuiceFxmlLoader {
#Inject
public ConcreteViewLoader(Stage stage, Provider<MyController> provider) {
super(stage, provider);
}
#Override
public String getFileName() {
return "my_view.fxml";
}
}
Note this example loads the view into the center of a BoarderPane that is the root of the Scene in the Stage. This is irrelevant to the example (implementation detail of my specific use case) but decided to leave it in as some may find it useful.
You can decide to use a public observable list to store public data, or just create a public setter method to store data and retrieve from the corresponding controller
Why answer a 6 year old question ?
One the most fundamental concepts working with any programming language is how to navigate from one (window, form or page) to another. Also while doing this navigation the developer often wants to pass data from one (window, form or page) and display or use the data passed
While most of the answers here provide good to excellent examples how to accomplish this we thought we would kick it up a notch or two or three
We said three because we will navigate between three (window, form or page) and use the concept of static variables to pass data around the (window, form or page)
We will also include some decision making code while we navigate
public class Start extends Application {
#Override
public void start(Stage stage) throws Exception {
// This is MAIN Class which runs first
Parent root = FXMLLoader.load(getClass().getResource("start.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setResizable(false);// This sets the value for all stages
stage.setTitle("Start Page");
stage.show();
stage.sizeToScene();
}
public static void main(String[] args) {
launch(args);
}
}
Start Controller
public class startController implements Initializable {
#FXML Pane startPane,pageonePane;
#FXML Button btnPageOne;
#FXML TextField txtStartValue;
public Stage stage;
public static int intSETonStartController;
String strSETonStartController;
#FXML
private void toPageOne() throws IOException{
strSETonStartController = txtStartValue.getText().trim();
// yourString != null && yourString.trim().length() > 0
// int L = testText.length();
// if(L == 0){
// System.out.println("LENGTH IS "+L);
// return;
// }
/* if (testText.matches("[1-2]") && !testText.matches("^\\s*$"))
Second Match is regex for White Space NOT TESTED !
*/
String testText = txtStartValue.getText().trim();
// NOTICE IF YOU REMOVE THE * CHARACTER FROM "[1-2]*"
// NO NEED TO CHECK LENGTH it also permited 12 or 11 as valid entry
// =================================================================
if (testText.matches("[1-2]")) {
intSETonStartController = Integer.parseInt(strSETonStartController);
}else{
txtStartValue.setText("Enter 1 OR 2");
return;
}
System.out.println("You Entered = "+intSETonStartController);
stage = (Stage)startPane.getScene().getWindow();// pane you are ON
pageonePane = FXMLLoader.load(getClass().getResource("pageone.fxml"));// pane you are GOING TO
Scene scene = new Scene(pageonePane);// pane you are GOING TO
stage.setScene(scene);
stage.setTitle("Page One");
stage.show();
stage.sizeToScene();
stage.centerOnScreen();
}
private void doGET(){
// Why this testing ?
// strSENTbackFROMPageoneController is null because it is set on Pageone
// =====================================================================
txtStartValue.setText(strSENTbackFROMPageoneController);
if(intSETonStartController == 1){
txtStartValue.setText(str);
}
System.out.println("== doGET WAS RUN ==");
if(txtStartValue.getText() == null){
txtStartValue.setText("");
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// This Method runs every time startController is LOADED
doGET();
}
}
Page One Controller
public class PageoneController implements Initializable {
#FXML Pane startPane,pageonePane,pagetwoPane;
#FXML Button btnOne,btnTwo;
#FXML TextField txtPageOneValue;
public static String strSENTbackFROMPageoneController;
public Stage stage;
#FXML
private void onBTNONE() throws IOException{
stage = (Stage)pageonePane.getScene().getWindow();// pane you are ON
pagetwoPane = FXMLLoader.load(getClass().getResource("pagetwo.fxml"));// pane you are GOING TO
Scene scene = new Scene(pagetwoPane);// pane you are GOING TO
stage.setScene(scene);
stage.setTitle("Page Two");
stage.show();
stage.sizeToScene();
stage.centerOnScreen();
}
#FXML
private void onBTNTWO() throws IOException{
if(intSETonStartController == 2){
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Alert");
alert.setHeaderText("YES to change Text Sent Back");
alert.setResizable(false);
alert.setContentText("Select YES to send 'Alert YES Pressed' Text Back\n"
+ "\nSelect CANCEL send no Text Back\r");// NOTE this is a Carriage return\r
ButtonType buttonTypeYes = new ButtonType("YES");
ButtonType buttonTypeCancel = new ButtonType("CANCEL", ButtonData.CANCEL_CLOSE);
alert.getButtonTypes().setAll(buttonTypeYes, buttonTypeCancel);
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == buttonTypeYes){
txtPageOneValue.setText("Alert YES Pressed");
} else {
System.out.println("canceled");
txtPageOneValue.setText("");
onBack();// Optional
}
}
}
#FXML
private void onBack() throws IOException{
strSENTbackFROMPageoneController = txtPageOneValue.getText();
System.out.println("Text Returned = "+strSENTbackFROMPageoneController);
stage = (Stage)pageonePane.getScene().getWindow();
startPane = FXMLLoader.load(getClass().getResource("start.fxml"));
Scene scene = new Scene(startPane);
stage.setScene(scene);
stage.setTitle("Start Page");
stage.show();
stage.sizeToScene();
stage.centerOnScreen();
}
private void doTEST(){
String fromSTART = String.valueOf(intSETonStartController);
txtPageOneValue.setText("SENT "+fromSTART);
if(intSETonStartController == 1){
btnOne.setVisible(true);
btnTwo.setVisible(false);
System.out.println("INTEGER Value Entered = "+intSETonStartController);
}else{
btnOne.setVisible(false);
btnTwo.setVisible(true);
System.out.println("INTEGER Value Entered = "+intSETonStartController);
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
doTEST();
}
}
Page Two Controller
public class PagetwoController implements Initializable {
#FXML Pane startPane,pagetwoPane;
public Stage stage;
public static String str;
#FXML
private void toStart() throws IOException{
str = "You ON Page Two";
stage = (Stage)pagetwoPane.getScene().getWindow();// pane you are ON
startPane = FXMLLoader.load(getClass().getResource("start.fxml"));// pane you are GOING TO
Scene scene = new Scene(startPane);// pane you are GOING TO
stage.setScene(scene);
stage.setTitle("Start Page");
stage.show();
stage.sizeToScene();
stage.centerOnScreen();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
Below are all the FXML files
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane id="AnchorPane" fx:id="pagetwoPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atwopage.PagetwoController">
<children>
<Button layoutX="227.0" layoutY="62.0" mnemonicParsing="false" onAction="#toStart" text="To Start Page">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Button>
</children>
</AnchorPane>
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane id="AnchorPane" fx:id="startPane" prefHeight="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atwopage.startController">
<children>
<Label focusTraversable="false" layoutX="115.0" layoutY="47.0" text="This is the Start Pane">
<font>
<Font size="18.0" />
</font>
</Label>
<Button fx:id="btnPageOne" focusTraversable="false" layoutX="137.0" layoutY="100.0" mnemonicParsing="false" onAction="#toPageOne" text="To Page One">
<font>
<Font size="18.0" />
</font>
</Button>
<Label focusTraversable="false" layoutX="26.0" layoutY="150.0" text="Enter 1 OR 2">
<font>
<Font size="18.0" />
</font>
</Label>
<TextField fx:id="txtStartValue" layoutX="137.0" layoutY="148.0" prefHeight="28.0" prefWidth="150.0" />
</children>
</AnchorPane>
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Font?>
<AnchorPane id="AnchorPane" fx:id="pageonePane" prefHeight="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="atwopage.PageoneController">
<children>
<Label focusTraversable="false" layoutX="111.0" layoutY="35.0" text="This is Page One Pane">
<font>
<Font size="18.0" />
</font>
</Label>
<Button focusTraversable="false" layoutX="167.0" layoutY="97.0" mnemonicParsing="false" onAction="#onBack" text="BACK">
<font>
<Font size="18.0" />
</font></Button>
<Button fx:id="btnOne" focusTraversable="false" layoutX="19.0" layoutY="97.0" mnemonicParsing="false" onAction="#onBTNONE" text="Button One" visible="false">
<font>
<Font size="18.0" />
</font>
</Button>
<Button fx:id="btnTwo" focusTraversable="false" layoutX="267.0" layoutY="97.0" mnemonicParsing="false" onAction="#onBTNTWO" text="Button Two">
<font>
<Font size="18.0" />
</font>
</Button>
<Label focusTraversable="false" layoutX="19.0" layoutY="152.0" text="Send Anything BACK">
<font>
<Font size="18.0" />
</font>
</Label>
<TextField fx:id="txtPageOneValue" layoutX="195.0" layoutY="150.0" prefHeight="28.0" prefWidth="150.0" />
</children>
</AnchorPane>

How to set an additional FXML layout to an existing one

so I have two Controllers: the MainControllerand an ImageContainerboth have a FXML layout. In my MainController i set up a SplitPane and inside of it a FlowPane now I want to load the layout of the ImageContainer in the flowpane at runtime:
PROBLEM
How do I place the layout inside the flowpane with pre filled values in textFields, set an image etc.?
Idea
ImageContainer must extend Pane, and in the MainController I have to call the constructor of the ImageContainer and add the ImageContainer to the flowpane:
ImageContainer imgC = new ImageContainer(4,2,"location");
fp_contentFlowPane.getChildren().add(imgC);
Caused by: java.lang.NullPointerException
Caused by: java.lang.reflect.InvocationTargetException
If anyone has an ideas, help is appreciated!
Code snippet Part of ImageContainer Contoller:
public class ImageContainer extends Pane {
public HBox hbx_elementContainer;
public Label lb_likeCount;
public Label lb_commentCount;
public Label lb_location;
public ImageContainer(int likeCount, int commentCount, String location) {
this.lb_likeCount.setText(String.valueOf(likeCount));
this.lb_commentCount.setText(String.valueOf(commentCount));
this.lb_location.setText(location);
Image image = new Image("/sampleFoto.JPG");
iv_feedImage.setImage(image);
}
}
Code snippet Part of MainController Note this is not the whole code:
public class MainScreenController{
public TextField tf_userName;
public ListView lv_listView;
public FlowPane fp_contentFlowPane;
public SplitPane sp_splitPane;
public void onItemClicked(MouseEvent mouseEvent) throws IOException {
int index = lv_listView.getSelectionModel().getSelectedIndex();
if (mouseEvent.getButton().equals(MouseButton.SECONDARY)) {
if (index >= 0) {
lv_listView.getItems().remove(index);
userList.remove(index);
}
}
else{
//fp_contentFlowPane.getChildren().add(new
ImageContainer(5,5,"test"));
ImageContainer imgC = new ImageContainer(4,2,"location");
fp_contentFlowPane.getChildren().add(imgC);
}
}}
Code snippet Main
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.setTitle("Get Viral");
primaryStage.setScene(new Scene(root, 1000, 700));
primaryStage.show();
primaryStage.getIcons().add(new Image("/iconSpectures.jpg"));
}
public static void main(String[] args) {
launch(args);
}
}
FXML of ImageContainer:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.Font?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="200.0" prefWidth="200.0"
xmlns="http://javafx.com/javafx/8.0.172-ea"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.ImageContainer">
<bottom>
<HBox fx:id="hbx_elementContainer" prefHeight="31.0" prefWidth="600.0"
BorderPane.alignment="CENTER">
<children>
<Label fx:id="lb_likeCount" contentDisplay="TOP" text="Label">
<HBox.margin>
<Insets right="10.0" />
</HBox.margin></Label>
<Label fx:id="lb_commentCount" text="Label">
<HBox.margin>
<Insets right="20.0" />
</HBox.margin></Label>
<Label fx:id="lb_location" text="Label" />
<Label fx:id="lb_accountHolder" text="Label" />
<Button mnemonicParsing="false" text="Download">
<font>
<Font name="Arial Bold" size="11.0" />
</font>
<HBox.margin>
<Insets right="10.0" />
</HBox.margin>
</Button>
</children>
</HBox>
</bottom>
<center>
<AnchorPane prefHeight="200.0" prefWidth="200.0"
BorderPane.alignment="CENTER">
<children>
<ImageView fx:id="iv_feedImage" fitHeight="150.0"
fitWidth="200.0"
pickOnBounds="true" preserveRatio="true" AnchorPane.bottomAnchor="0.0"
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"
AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
</center>
</BorderPane>
If you want your ImageContainer to make use of your FXML then you can simply load it in the constructor using FXMLLoader and get rid of the fx:controller="sample.ImageContainer" from your FXML.
Here's a post about when to use which method of setting a controller for an fxml file to use (fx:controller vs FXMLLoader); Because your ImageContainer constructor requires arguments, it's easier imo to use the FXMLLoader method.
public class ImageContainer extends Pane {
private static final PATH_FXML = "/internal/path/to/layout.fxml"
#FXML public HBox hbx_elementContainer;
#FXML public Label lb_likeCount;
#FXML public Label lb_commentCount;
#FXML public Label lb_location;
public ImageContainer(int likeCount, int commentCount, String location) {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(PATH_FXML));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException e) {
throw new RuntimeException(e);
}
this.lb_likeCount.setText(String.valueOf(likeCount));
this.lb_commentCount.setText(String.valueOf(commentCount));
this.lb_location.setText(location);
Image image = new Image("/sampleFoto.JPG");
iv_feedImage.setImage(image);
}
}
For some extra fun stuff: If you define getters and setters on a custom Control, then you can use them in the attributes of that control in FXML.
It's not exactly necessary for your use case, but you can do stuff like this.
Custom Control:
package path.to.my_package;
public class MyControl extends Control {
private String myField;
public String getMyField() { return myField; }
public void setMyField(String myField) { this.myField = myField; }
}
FXML:
<?import path.to.my_package.MyControl ?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1">
<center>
<MyControl myField="myValue"/>
</center>
</BorderPane>

Listing file names using FXML TableView - unable to populate data

I am trying to populate filesnames in a tabular format using FXML.
I am able to display the table but the rows are not getting displayed.
Directory name will be selected by user during runtime.
.
Main.java
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.AnchorPane;
import javafx.fxml.FXMLLoader;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
AnchorPane root =(AnchorPane)FXMLLoader.load(getClass().getResource("Utility.fxml"));
Scene scene = new Scene(root,600,600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.setTitle("testing");
primaryStage.show();
} catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
AppController.java
import java.io.File;
import java.util.Arrays;
import java.util.List;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.stage.DirectoryChooser;
public class AppController
{
#FXML private Label BrowseStatus;
#FXML private TextField TDpath;
#FXML private Button CreateH2H;
#FXML private TableView<String> FileListTable;
#FXML private TableColumn<FilesInDir,String> FileNameCol;
#FXML private ObservableList<String> fidlist;
#FXML
protected void handleBrowseWindowsExplorer(ActionEvent event){
DirectoryChooser TestDataDir = new DirectoryChooser();
TestDataDir.setTitle("Select path");
File selectedDir = TestDataDir.showDialog(null);
if(selectedDir == null){
BrowseStatus.setText("Nothing choosen");
TDpath.setText("");
} else {
TDpath.setText(selectedDir.getAbsolutePath());
FileListTable = new TableView<String>();
FileNameCol.setCellValueFactory(new PropertyValueFactory<FilesIndDir,String>("FileName"));
FileListTable.setPlaceholder(BrowseStatus);
BrowseStatus.setText("Folder has been selected");
File tFile = new File(TDpath.getText());
File[] listOfFiles = tFile.listFiles();
fidlist = FXCollections.observableArrayList();
List<String> fileNameList = null;
for (int i=0; i<listOfFiles.length; i++) {
fileNameList = Arrays.asList(listOfFiles[i].getName());
}
fidlist.addAll(fileNamesList);
FileListTable.setItems(fidlist);
}
}
}
Utility.fxml
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.collection.*?>
<?import javafx.collections.FXCollections?>
<?import javafx.geometry.*?>
<?import javafx.scene.chart.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.web.*?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<AnchorPane fx:controller="application.AppController" id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns:fx="http://javafx.com/fxml">
<children>
<GridPane>
<children>
<MenuBar maxWidth="-Infinity" prefWidth="800.0" GridPane.ColumnIndex="0" GridPane.RowIndex="0">
<menus>
<Menu mnemonicParsing="false" text="File">
<items>
<MenuItem mnemonicParsing="false" text="Close" />
</items>
</Menu>
</menus>
</MenuBar>
<BorderPane GridPane.ColumnIndex="0" GridPane.RowIndex="1">
<top>
<TabPane PrefHeight="700.0" PrefWidth="900.0" tabClosingPolicy="UNAVAILABLE">
<Tab text="Files tab ">
<content>
<GridPane hgap="10" vgap="10">
<padding><Insets top="25" right="25" bottom="10" left="25" /></padding>
<children>
<Label text="Path :" GridPane.ColumnIndex="0" GridPane.RowIndex="1" />
<TextField fx:id="TDpath" GridPane.ColumnIndex="0" GridPane.RowIndex="1" />
<Button text="Browse" onAction="#handleBrowseWindowsExplorer" GridPane.ColumnIndex="3" GridPane.RowIndex="1" />
<VBox prefHeight="300.0" prefWidth="400.0" spacing="6.0" VBox.vgrow="ALWAYS" GridPane.ColumnIndex="0" GridPane.RowIndex="3" GridPane.columnSpan="4" >
<children>
<TableView fx:id="FileListTable" >
<placeholder><Label fx:id="BrowseStatus" text="No files in selected directory" /></placeholder>
<columns>
<TableColumn fx:id="FileNameCol" text="File Names" prefWidth="400">
<cellValueFactory>
<PropertyValueFactory property="FileName" />
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
</children>
</VBox>
</children>
</GridPane>
</content>
</Tab>
</TabPane>
</top>
</BorderPane>
</children>
</GridPane>
</children>
</AnchorPane>
FilesInDir.java
import javafx.beans.property.SimpleStringProperty;
public class FilesInDir {
private final SimpleStringProperty FileName = new SimpleStringProperty();
public FilesInDir(String fName) {
setFileName(fName);
}
public String FileNameProperty() {
return FileName.get();
}
public void setFileName(String fName) {
this.Filename.set(fName);
}
}
In this answer I'll just ignore all the typos, since there seems to be a compilable version of the code and point out the other errors:
FilesInDir
If I got your intention, this class should contain the info for a file that should be displayed. Besides ignoring the naming convention of starting identifiers of class members with a lowercase letter, the real issue here is the FileNameProperty. The property method with the suffix Property has to return the property itself and PropertyValueFactory relies on that fact. It should look like this:
public StringProperty fileNameProperty() {
return FileName;
}
Also you did not use the class as type parameter of your TableView:
#FXML
private TableView<FilesInDir> FileListTable;
AppController.handleBrowseWindowsExplorer
The main issue is in this method:
There's no need to need to make fidlist a field, let alone annotate it with #FXML. In fact there's no need to create a new ObservableList at all.
You recreate the TableView, that was created and injected by the FXMLLoader and do not insert it to the scene graph, but you work with the new TableView, which is not the one that is displayed on screen.
If no file is selected, the TextField is emptied, but the items in the TableView from a previously selected directory remain, leading to inconsistent behaviour.
fileNameList = Arrays.asList(listOfFiles[i].getName()); in the for loop replaces the list with a new List containing a single element every time it's executed.
File tFile = new File(TDpath.getText()); recreates the file from the String that was created by converting the selected file to a String. That's just unnecessary; you should simply use selectedDir
Changing the method like this should work, assuming you fixed the points mentioned for FilesInDir too:
#FXML
protected void handleBrowseWindowsExplorer(ActionEvent event) {
DirectoryChooser TestDataDir = new DirectoryChooser();
TestDataDir.setTitle("Select path");
File selectedDir = TestDataDir.showDialog(null);
if (selectedDir == null) {
BrowseStatus.setText("Nothing choosen");
FileListTable.getItems().clear();
TDpath.setText("");
} else {
TDpath.setText(selectedDir.getAbsolutePath());
BrowseStatus.setText("Folder has been selected");
File[] listOfFiles = selectedDir.listFiles();
ArrayList<FilesInDir> fidlist = new ArrayList<>(listOfFiles.length);
for (File listOfFile : listOfFiles) {
fidlist.add(new FilesInDir(listOfFile.getName()));
}
FileListTable.getItems().setAll(fidlist);
}
}

Categories

Resources