This question already has answers here:
JavaFX : Pass parameters while instantiating controller class
(2 answers)
Closed 23 days ago.
Edit: I believe I found the correct answer to my problem after all.
Original Post:
I'm currently trying to create an application with JavaFX and an EventBus-System. To do this, I have to pass the EventBus as constructor argument to other classes when instantiating them. However I don't know how to do this while also using an FXMLLoader to load my .fxml-Files.
My code currently looks somethinng like this:
Main Class
public class MyApplication extends Application {
public void start(Stage stage) throws Exception {
EventBus eventBus = new EventBus();
>>> Here would be code that creates an Object of MainView, passing eventBus as constructor argument. <<<
Scene scene = new Scene(mainView);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This class inherits from BorderPane and I want to create an object of it using fxmlLoader (I think. I'm not sure if it works like that)
puplic class MainView extends BorderPane {
private EventBus eventBus;
public MainView(EventBus eventBus) {
this.eventBus = eventBus;
... other code
}
}
I also have a controller for MainView (don't know if thats important to mention)
public class MainViewController {
>>> several JavaFX Elements like buttons, labels etc and their associated functionalities like onActions and such... <<<<
}
And of course there is an .fxml-File that contains the actual design of the MainView that I created with SceneBuilder, but I won't post it here since it doesn't seem necessary. But I sould probably mention that this .fxml-File contains a BorderPane as it's highest node. I think that makes sense, since my MainView extends BorderPane.
My Problem is that I ever created my own class that extends BorderPane and needs a Constructor parameter before and I don't really know how to create an instance of it.
In the past I did something like this:
FXMLLoader loader = new FXMLLoader();
BorderPane root = loader.load(getClass().getResourceAsStream("MainView.fxml"));
Scene scene = new Scene(root);
stage.show();
I of course looked for solutions online but those posts talk about passing arguments between windows and such.
Thanks in advance for your help.
You can use a dynamic root in the FXML.
Briefly, the FXML will look like:
<fx:root
type="javafx.scene.layout.BorderPane"
xmlns:fx="http://javafx.com/fxml"
fx:controller="com.mycompany.myproject.MainViewController">
<!-- Controls etc. -->
</fx:root>
And then in the MainView constructor do
public class MainView extends BorderPane {
private EventBus eventBus;
public MainView(EventBus eventBus) {
this.eventBus = eventBus;
FXMLLoader loader = new FXMLLoader(getClass().getResourceAsStream("MainView.fxml"));
loader.setRoot(this);
loader.load();
}
}
The controller will just behave the same as the controller in an FXML with a static root. That is, you could communicate with the controller if needed with:
public MainView(EventBus eventBus) {
this.eventBus = eventBus;
FXMLLoader loader = new FXMLLoader(getClass().getResourceAsStream("MainView.fxml"));
loader.setRoot(this);
loader.load();
MainViewController controller = loader.getController();
// etc...
}
or if you needed to instantiate it by hand, omit the fx:controller attribute from the FXML file, and then do
public MainView(EventBus eventBus) {
this.eventBus = eventBus;
MainViewController controller = new MainViewController(...);
FXMLLoader loader = new FXMLLoader(getClass().getResourceAsStream("MainView.fxml"));
loader.setRoot(this);
loader.setController(controller);
loader.load();
}
Related
I have 2 Controllers and I want to pass values from second controller to first controller, here are code sample:
FXMLController.java
private int paramAnswers;
private int paramNotificationTime;
private int paramNotificationDelay;
#FXML
private void handleMenuItemOptionsAction(ActionEvent event) throws IOException{
Parent root = FXMLLoader.load(getClass().getResource("/fxml/Options.fxml")); // UNDECORATED*
Scene scene = new Scene(root, Color.TRANSPARENT);
final Stage stage = new Stage();
stage.setTitle("Options");
stage.setScene(scene);
stage.show();
}
public void setOptionsParams(int paramAnswers, int paramNotificationTime, int paramNotificationDelay){
this.paramAnswers = paramAnswers;
this.paramNotificationTime = paramNotificationTime;
this.paramNotificationDelay = paramNotificationDelay;
}
and second controller:
OptionsController.java
private FXMLController parentController;
private int paramAnswers;
private int paramNotificationTime;
private int paramNotificationDelay;
#Override
public void initialize(URL location, ResourceBundle resources) {
.... }
#FXML
private void handleButtonSaveAction(ActionEvent event) throws IOException{
/*Pass these parameteres OptionsController parameters back to the FXMLController like parentController.setOptionsParams(paramAnswers, paramNotificationTime, paramNotificationDelay);
*/
(((Button)event.getSource()).getScene().getWindow())).close();
}
Arleady tried with parsing FXMLControler as .this into OptionsController initialize method, tried making listers and bunch of other resolved problems on stackoverflow but it just don't want work :< I need to pass that atributes back to FXMLController and close child window, so my main app would change behavior depending on passed values... :X
For any help I will be grateful
you can have a function in the second controller let's say passParams() set it's parameters what every you want to pass to that controller and from the first controller when you click on a button or something
this line
FXMLLoader.load(getClass().getResource("/fxml/Options.fxml"));
need to be changed to
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Options.fxml"));
Parent root = (Parent) loader.load();
OptionsController controller = loader.getController();
EDIT
you need to pass the first controller to the second controller
controller.setParentController(this); // this is the first controller
in Second controller
FirstController mController;
public void setParams(FirstController controller) {
this.mController = controller;
}
now in the button click function you use the mController you got from the previous step
mController.setOptionsParams(...); //send the params collected from the textfields
this function is implemented in the FirstController
Note: a more general way to do this by using call-backs it's the same but your code depends on interfaces not concrete classes by implementing a general interface in FirstController that have the setOptionParams() method
First you add a callback interface to your parent controller class:
public interface OptionCallback {
public void setOptionsParams(int paramAnswers, int paramNotificationTime, int paramNotificationDelay);
}
public class YourParentController implements Initializable, OptionCallback {
...
}
Using the FXMLLoader object you can get a hold of your child controller object and pass the parent object to it:
FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("yourFXML.fxml"));
Parent root = (Parent) loader.load();
YourChildController yourChildController = loader.<YourChildController>getController();
yourChildConrtoller.registerCallback(this);
In the childController you save that callback:
private OptionCallback optionCallback;
public void registerCallback(OptionCallback callback) {
optionCallback = callback;
}
And whenever results are ready you use it to pass it to parent:
optionCallback.setOptionsParams(...);
I am experementing with the "new" JavaFX and it worked very well.
Now I am at a point which is incomprehensible for me. I have a Controller for my View and I want to load my Controller from a main-method so the controller can load a view or do whatever it likes.
My problem ist, that I have to load my FXML-File with the FXMLLoader.load() method. The FXMLLoader himselfe loads the controller. So in fact, with my method I will load the controller two times: I load the controller with XController xcontroller = new XController(); and inside that controller I load te view with the FXMLLoader.load() which will load the controller again.
do I have to use FXMLLoader or can I let my controller load the view with an other method?
edit I want to use the Presentation-Abstraction-Control (PAC) Pattern (variation of MVC), that's why I think it's importand to let the controller load the View.
the main class
public class Main extends Application
{
Override
public void start(Stage primaryStage)
{
LoginController loginController = null;
try
{
loginController = new LoginController();
loginController.loadSceneInto(primaryStage);
primaryStage.show();
}
.......
public static void main(String[] args)
{
launch(args);
}
}
the controller
public class LoginController
{
.....
public void loadSceneInto(Stage stage) throws IOException
{
this.stage = stage;
Scene scene = null;
Pane root = null;
try
{
root = FXMLLoader.load(
getClass().getResource(
this.initialView.getPath()
)
);
scene = new Scene(root, initialWidth, initialHeight);
this.stage.setTitle(this.stageTitle);
this.stage.setScene(scene);
this.centralizeStage();
}
.....
}
}
If I understand correctly, instead of
root = FXMLLoader.load(
getClass().getResource(
this.initialView.getPath()
)
);
Just do
FXMLLoader loader = new FXMLLoader(
getClass().getResource(this.initialView.getPath());
);
loader.setController(this);
root = loader.load();
You will need to remove the fx:controller attribute from the FXML file for this to work.
public class ProjectxController implements Initializable {
#FXML
private AnchorPane LandingPane;
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
private void onLoad(ActionEvent event) throws IOException{
pane = FXMLLoader.load(getClass().getResource("connectedPage.fxml"));
LandingPane.getChildren().setAll(pane);
}
Note: Having two fxml files Frontpage.fxml and ConnectedPage.fxml with one controller projectxController i.e coded above
Frontpage.fxml has one button that will load connectedPage.fxml.
Connectedpage.fxml has one label
Now I want to set the Label text after connectedpage.fxml is loaded
public class ProjectX extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FrontPage.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
When you create you fxml document you have to give a unique id for you Label:
The same you have done for the AnchorPane.
Example
#FXML
private Label myLabel;
Then inside the initialize method of your fxml controller class you can modify it as you want.
If you problem is how to access the Label from another controller you can use a get method or pass an instance of the controller you need . All that after the fxml has successfully loaded .
I recommend also that you use different controllers for different fxml files , cause it is more clear .
Have a search on fxml loading techniques and how to connect different controllers over the web and you will find what you need .
I want to apologize in advance, as this is discussed previously in "Passing Parameters Directly From the Caller to the Controller", but I followed every possible solution I found and still I can't get it work.
I have difficulty in passing a parameter from one controller to another.
Specifically:
LoginController passes username to MainController.
When I click login button LoginController sets the username to the MainController. But, when Main.fxml is loaded username is NULL.
As trying to figure it out, I would like to ask:
When MainController initialize() method gets called? I suppose by the time LoginController calls st.show(); ?
If the previous is correct, then why MainController username is NULL, since we have already set its value in LoginController using mainController.setUsername(username) ?
Any help would be appreciated.
This is my code.
LoginController.java
public class LoginController implements Initializable {
...
#FXML TextField username;
#FXML public void actionLoginButton() {
...
Stage st = new Stage();
FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml"));
Region root = (Region) loader.load();
Scene scene = new Scene(root);
st.setScene(scene);
MainController mainController = loader.<MainController>getController();
mainController.setUsername(username.getText());
st.show();
}
...
}
MainController.java
public class MainController implements Initializable {
...
String username;
#FXML Label lblWelcomeUser;
public void setUsername(String usrname) {
this.username = usrname;
}
#Override
public void initialize(URL url, ResourceBundle rb) {
...
lblWelcomeUser.setText("Welcome, " + this.username);
...
}
...
}
The issue is the timing of your call to set username.
The initialize() method of MainController is called during the execution of the following statement:
Region root = (Region) loader.load();
At this time, the username field in MainController is null and so its value is reported as null in the welcome message. Your call to setUsername() occurs after the MainController.initialize() method has completed.
In general, the initialize() method of controller classes loaded with the FXML loader should never try to do anything with instance fields whose values have not been injected by the FXML loader. These instance fields will not have been initialized at the time that the initialize() method is invoked.
In this code:
public class ESM extends Application {
private Stage primaryStage;
#FXML
private ToolBar mainToolBar;
#Override
public void start(final Stage stage) throws Exception {
try{
this.primaryStage = stage;
Parent root = FXMLLoader.load(getClass().getResource("/nz/co/great_ape/esm3/main_window.fxml"));
Scene scene = new Scene(root, 800, 700);
// Setup main stage to be full screen, no min or max buttons.
// TODO: How will this handle multiple screens? Apparently not well :-(
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
primaryStage.setX(bounds.getMinX());
primaryStage.setY(bounds.getMinY());
primaryStage.setWidth(bounds.getWidth());
primaryStage.setHeight(bounds.getHeight());
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setTitle("ESM three");
primaryStage.setScene(scene);
primaryStage.show();
System.out.println("This will fail because mainToolBar is null. Why?");
assert mainToolBar != null : "fx:id=\"mainToolBar\" was null check your FXML ";
} catch (Exception ex) {
Logger.getLogger(ESM.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* Use initialize() to setup widgets from scenebuilder files, it is
* called by FXMLLoader.
*/
#FXML
public void initialize(){
System.out.println("initialize() But when here all is good and mainToolBar is a ToolBar.");
assert mainToolBar != null : "fx:id=\"mainToolBar\" was null check your FXML ";
}
/**
* The main() method is ignored in correctly deployed JavaFX application.
* main() serves only as fallback in case the application can not be
* launched through deployment artifacts, e.g., in IDEs with limited FX
* support.
*
* #param args The command line arguments.
*/
public static void main(String[] args) {
launch(args);
}
}
I cant see why it's got a value in the initialise() but in the start it's null. When debuging it's clear that initiialize() is called by FXMLLOader from inside start()
I was going to post the fxml but it does not seem to work as nothig shows in the preview. Any way, it's a real basic file, a BordePane and a ToolBar.
Any clues?
Always create a new class for your FXML Controller, don't try to reuse an Application class as a Controller class.
An Application instance is created by the JavaFX application launcher.
A Controller instance is created by the JavaFX FXML loader.
You don't supply the FXML that you use, but I am going to guess that it has it's Controller class erroneously set to be your application class.
So in your code, what happens is:
An instance of the application is created when you run the program (via the launch method).
In your application start method, you invoke the FXMLLoader, which instantiates a new Controller (in your case a new instance of the application class).
The FXMLLoader injects the #FXML tagged members into the new application object and invokes the initialize on the new object.
But your original application object doesn't know anything about the new application object and hence doesn't have a menu bar set in it.
In summary, to fix this:
Create a new controller class that the FXMLLoader can instantiate.
Change your fxml to reference the new controller class.
If your application really needs to reference the controller, then you can use the getController method on the FXML loader and in your controller class provide public methods to retrieve required elements (like your menu bar). See my answer to Passing Parameters JavaFX FXML for some more examples of this method.
import javafx.scene.control.ToolBar;
import javafx.fxml.FXML;
public class ESMController {
#FXML
private ToolBar mainToolBar;
public ToolBar getMainToolBar() { return mainToolBar; }
#FXML
public void initialize(){
assert mainToolBar != null : "fx:id=\"mainToolBar\" was null check your FXML ";
}
}