How to initialize dialog window in nested controller - javafx

I'm developing a GUI application using Java8 and JavaFX. The main window has a button that should open new window (with it's own fxml). So far I've been loading the fxml each time the button was pressed but since the new window has tons of controls it (surprisingly) takes some time (aprox 0.5-1s) to open the popup, and thus I've changed the code so that the main controller loads the popup fxml in it's initialize method and whenever the button is clicked the pre loaded window is just shown.
It all works good but now I can't set the initOwner(...) on the new window since I don't have access to the window object in the initilize method. I know I don't have to set the initOwner, but then I have two application windows on the start menu (which I want to avoid). Any ideas how to go around this issue?
Also, what is the standard way of showing new windows/dialogs in JavaFX, should i load an fxml each time an event occurs or just show/hide the preloaded one?

You could load the FXML once in the initialize() method, then lazily initialize the dialog window when you need it. I.e.
public class Controller {
private Parent dialogPane ;
private Stage dialog ;
#FXML
private Button button ;
public void initialize() throws IOException {
dialogPane = FXMLLoader.load(getClass().getResource("dialog.fxml"));
}
#FXML
private void handleButtonPress() {
getDialog().show();
}
private Stage getDialog() {
if (dialog == null) {
Scene scene = new Scene(dialogPane);
dialog = new Stage();
dialog.setScene(scene);
dialog.initOwner(button.getScene().getWindow());
}
return dialog ;
}
}

You can lazy load the fxml. Namely not when app starts or not on every button click, but when it is requested:
private Parent popupPane;
private PopupPaneController popupPaneController;
private void openPopup( ActionEvent event ) {
if (popupPane == null) {
popupPane = FXMLLoader.load(...);
popupPaneController = // get it from FXMLLoader. Search this site for how
}
popupPaneController.updatePopupContent(newVals);
Stage popup = new Stage();
popup.initOwner(primaryStage);
stage.setScene(new ScrollPane(popupPane));
stage.show();
}
Note that if you cache the content of popup window, you can set initOwner() later. Also checkout the Alert class as an alternative for popup. See examples of it.
While showing the preloaded scene/window you may need to update the data shown in the popup. for this implement an updatePopupContent(newVals) method in popup controller's class, and call it on every button click as in code above.

Related

How to pass data from child JFXDrawer controller to a node residing in parent controller in JavaFX?

I have a parent controller say HomeController
It has a node SidePanel (JFXDrawer) with SidePanelController and a node anchorPane with varying controller.
HomeController
|
/ \
/ \
/ \
anchorPane SidePanel
(Controller (Controller = SidePanelController)
= varies)
The anchorPane node should load multiple fxml views with menu buttons clicked from SidePanelController.
The problem here is in SidePanelController since the buttons are inside it, I cannot directly load onanchorPane as for SidePanelController the node anchorPane does not exists
This question seems duplicate of this but its not because the parent controller is waiting for scene to close so it fetches back the data to parent controller. But in my case, every time I click on menu button, it will load a view accordingly.
Can anybody provide resources for making controller for JFXDrawer (as child node).
If say, I have a side navigation drawer sidepanel.fxml like this
And I have a HomeScreen like this
So by using following code, I stacked drawer in my homecontroller
try {
SidePanelController controller = new SidePanelController();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/sidepanel.fxml"));
loader.setController(controller);
Parent root = loader.load();
drawer.setSidePane(root);
} catch (IOException err) {
err.printStackTrace();
}
Finally, I will be getting this as combined output
Now what I want is whenever I try to click on say Lorry Receipt button on Side Navigation Panel, it should trigger the node residing on Parent controller. Even the event which will pass data back to parent controller without closing the child node will work.
As #Sedrick suggested, I initialized events on all buttons of SidePanelController in HomeController (Parent) itself. At first it returned NPE, but later I let the buttons initialize and then fetch it back to parent controller.
So here is the solution. It might be non-ethical, so other alternatives still appreciated.
public class SidePanelController implements Initializable {
#FXML
private JFXButton btn_lr;
#FXML
private JFXButton btn_shipment;
#FXML
private JFXButton btn_add_inward;
}
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
//Your other activities when sidedrawer/pane initializes
}
//After I initialize, I would like all of these buttons to be fetched to my Parent controller. So instead of me passing each button separately, I made a list to save my time.
public ObservableList<JFXButton> fetchAllButtons(){
ObservableList<JFXButton> allbuttons = FXCollections.observableArrayList(btn_lr, btn_add_inward, btn_shipment);
return allbuttons;
}
Now in HomeController or ParentController, I fetch all these buttons and create events for it separately. So here goes the code.
public class HomeController implements Initializable {
#FXML
private JFXHamburger hamburger;
#FXML
private JFXDrawer drawer;
//Creating Hamburger Task to toggle sidebar
HamburgerBackArrowBasicTransition burgerTask;
//Declaring sidepanelcontroller globally because I need it in multiple methods.
SidePanelController controller = new SidePanelController();
//Finally a list to fetch the list of all buttons from SidePanelController
ObservableList<JFXButton> sidebuttons;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
//Using hamburger to toggle my side drawer
burgerTask = new HamburgerBackArrowBasicTransition(hamburger);
//Initializing the scene of drawer/SidePanel FXML file on Home.fxml or parent fxml itself
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/sidepanel.fxml"));
loader.setController(controller);
Parent root = loader.load();
drawer.setSidePane(root);
sidebuttons = controller.fetchAllButtons();
//Make sure not to declare this before initializing or else it returns NPE
} catch (Exception err) {
err.printStackTrace();
}
//Finally you can declare your tasks for each button by declaring events on each of those buttons. I thought only common but different thing you can between buttons is the name of it. So I used switch statement just to point out each button separately. (I know this might be unethical, but works for me though)
for (JFXButton button: sidebuttons) {
switch (button.getText()){
case "Lorry Receipt":
button.setOnAction(actionEvent -> {
//Your actions here when button with name Lorry Receipt is pressed
});
break;
case "Shipment Memo":
button.setOnAction(actionEvent -> {
//Your actions when button with name shipment memo is pressed
});
break;
case "Inward Challan":
button.setOnAction(actionEvent -> {
//Your actions when button with name Inward Challan is pressed
});
break;
}
}
}
}
Other Advantage I found with this is, I don't have to show ProgressBar/ProgressIndicator of each scene separately. Since Child Component's ActionEvent is on ParentNode, the ProgressBar/Indicator can be binded to it and works like a charm.

How do I switch between layouts in Javax? [duplicate]

I have 2 fxml files:
Layout (header, menubars and content)
Anchorpane (it's supposed to be placed inside the content from the other fxml file)
I would like to know how can I load the second file inside the content space from the "Master" scene. And is that a good thing to do working in javaFX or is it better to load a new scene?
I'm trying to do something like this, but it doesn't work:
#FXML
private AnchorPane content;
#FXML
private void handleButtonAction(ActionEvent event) {
content = (AnchorPane) FXMLLoader.load("vista2.fxml");
}
Thanks for the help.
Why your code does not work
The loader creates a new AnchorPane, but you never add the new pane to a parent in the scene graph.
Quick Fix
Instead of:
content = (AnchorPane) FXMLLoader.load("vista2.fxml");
Write:
content.getChildren().setAll(FXMLLoader.load("vista2.fxml"));
Replacing the content children with your new vista. The content itself remains in the scene graph, so when you set it's children, you are also attaching them to the scene graph at the same time.
You might need to play around with layout (e.g. work with auto resizing layouts like StackPanes rather than AnchorPanes) to get the exact behaviour you want.
Rather than just adopting the quick fix, I would advise reviewing the simple framework linked below as that might provide you with a more general purpose mechanism to get the behaviour you want.
Reference FXML Navigation Framework
I created a small framework for swapping fxml controlled content panes in and out of a portion of the main scene.
The mechanism of the framework is the same as suggested in kithril's answer.
A main pane for the outer fxml acts as a holder for child panes.
The main controller for the outer fxml supplies a public method that can be used to swap the child panes.
A convenience navigator class is statically initialized with the main controller for the outer layout.
The navigator provides a public static method to load a new child pane into the main container (by invoking a method on the main controller).
Child panes are generated in the navigator by their respective fxml loaders.
Why a Framework
The framework seems like overkill for answering your question, and perhaps it is. However, I have found that the two most asked topic related to FXML are:
Navigation between panes generated by FXML (this question).
How to pass data between FXML controllers.
So I felt that a small demo framework was warranted for this case.
Sample Framework Output
The first screen shows the application layout displaying the first vista. The contents are a header which is defined in the main application layout and an aliceblue colored interchangable child content pane.
In the next screen, the user has navigated to the second vista, which retains the constant header from the main layout and replaces the original child pane with a new coral colored child content pane. The new child has been loaded from a new fxml file.
Looking for Something More Substantial?
A lightweight framework which is more extensive and better supported than the sample framework from this question is afterburner.fx.
Looking for Something Even Simpler?
Just swap out the scene root: Changing Scenes in JavaFX.
Other Options?
Animated Transitions and others: Switch between panes in JavaFX
Im not sure about how effective this is, but seems to be just fine and what's more, much simpler to methods above.
https://www.youtube.com/watch?v=LDVztNtJWOo
As far as I understood what is happening here is this(its really similiar to what is happening in Start() method in application class) :
private void buttonGoToWindow3Action(ActionEvent event) throws IOException{
Parent window3; //we need to load the layout that we want to swap
window3 = (StackPane)FXMLLoader.load(getClass().getResource("/ScenePackage/FXMLWindow3"));
Scene newScene; //then we create a new scene with our new layout
newScene = new Scene(window3);
Stage mainWindow; //Here is the magic. We get the reference to main Stage.
mainWindow = (Stage) ((Node)event.getSource()).getScene().getWindow();
mainWindow.setScene(newScene); //here we simply set the new scene
}
However Im not a java expert and quite new to programing so it would be good if someone experienced would evaluate it.
EDIT:
Ive found even simpler method;
Go to MainApplication class and make static Stage parentWindow.
public static Stage parentWindow;
#Override
public void start(Stage stage) throws Exception {
parentWindow = stage;
Parent root = FXMLLoader.load(getClass().getResource("/ScenePackage/FXMLMainScene.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Now you get acces to your main Stage so anywhere in a program you can do something like that to change the scene:
Parent window1;
window1 = FXMLLoader.load(getClass().getResource("/ScenePackage/FXMLWindow1.fxml"));
//Scene newSceneWindow1 = new Scene(window1);
Stage mainStage;
//mainStage = (Stage) ((Node)event.getSource()).getScene().getWindow();
mainStage = MainApplication.parentWindow;
mainStage.getScene().setRoot(newSceneWindow1); //we dont need to change whole sceene, only set new root.
Others may have a better solution, but my solution has been to have a simple container like VBox in the outer fxml, then load the new content and add it as a child of the container. If you're only loading one or two forms, this might be the way go to go. However, for a more complete framework, I found this blog post helpful: https://blogs.oracle.com/acaicedo/entry/managing_multiple_screens_in_javafx1 She has source code for her framework which includes fancy transitions. Although it's meant to manage top level scenes, I found it easy to adapt for managing inner content regions too.
My example of the mask.
Using:
Main.getNavigation().load(View2.URL_FXML).Show();
Main.getNavigation().GoBack();
In this case, I recommend you to use custom component instead. First create a custom component for your content:
class Content2 extends AnchorPane {
Content() {
FXMLLoader loader = new FXMLLoader(getClass().getResource("vista2.fxml");
loader.setRoot(this);
loader.setController(this);
loader.load();
}
}
Replace the AnchorPane markup in the root of your vista2.fxml file with fx:root:
<fx:root type="javafx.scene.layout.AnchorPane" xmlns:fx="http://javafx.com/fxml">
...
</fx:root>
Then, you can do this simply by using custom event binding and arrow function. Add event handler property to your Content class:
private final ObjectProperty<EventHandler<ActionEvent>> propertyOnPreviousButtonClick = new SimpleObjectProperty<EventHandler<ActionEvent>>();
#FXML
private void onPreviousButtonClick(ActionEvent event) {
propertyOnPreviousButtonClick.get().handle(event)
}
public void setOnPreviousButtonClick(EventHandler<ActionEvent> handler) {
propertyOnPreviousButtonClick.set(handler);
}
Finally, bind your custom event handler in your java code or fxml:
#FXML
onNextButtonClick() {
Content2 content2 = new Content2();
content2.setOnPreviousButtonClick((event) -> {
Content1 content1 = new Content1();
layout.getChildren().clear();
layout.getChildren().add(content1);
});
layout.getChildren().clear();
layout.getChildren().add(content2);
}
If you don't want to add content dynamically, just setVisible() to true or false
Got stuck up in this too
Tried out most of the answers, wasn't what I wanted so I just used the ideals given to do this:
public class Main extends Application {
public static Stage homeStage;
#Override
public void start(Stage primaryStage) throws Exception{
homeStage = primaryStage;
Parent root = FXMLLoader.load(getClass().getResource("mainView.fxml"));
root.getStylesheets().add(getClass().getResource("stylesheet/custom.css").toExternalForm());
homeStage.setTitle("Classification of Living Organisms");
homeStage.setScene(new Scene(root, 600, 500));
homeStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
this is my main class. Main.java with the landing window/page mainView.fxml.
Used a little of #Tomasz idea, although confused me a lil before I did this in my mainController.java class:
public void gotoSubMenu(Event event) {
Parent window1;
try {
window1 = FXMLLoader.load(getClass().getResource("src/displayView.fxml"));
Stage window1Stage;
Scene window1Scene = new Scene(window1, 600, 500);
window1Stage = Main.homeStage;
window1Stage.setScene(window1Scene);
} catch (IOException e) {
e.printStackTrace();
}
}
created a new Parent window called 'window1' that loaded the second fxml file called 'displayView.fxml' in the src directory.
created an object of the main view stage and set the scene to the newly created scene whose root is window1.
Hope this helps the ones coming into #JavaFX now.
If you're looking for a way to make the button call the new fxml file, this worked for me.
#FXML
private void mainBClicked(ActionEvent event) throws IOException {
Stage stage;
Parent root;
stage=(Stage) ((Button)(event.getSource())).getScene().getWindow();
root = FXMLLoader.load(getClass().getResource("MainMenu.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Keeping the same scene container but changing the view inside the scene container...
Say the scene container you want to pass a new view and controller into is a GridPane layout named sceneContainer.
Establish a FXMLLoader object of the new view.
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Notifications.fxml"));
Create a matching container and loading the contents of the new view into it.
GridPane yourNewView = fxmlLoader.load();
set the new view to the sceneContainer. (setAll clears all children first)
sceneContainer.getChildren().setAll(yourNewView);
Get the controller object for the new view and call your method that starts the class logic
Notifications notifications = fxmlLoader.getController();
notifications.passUserName(userName);
A full example would look like this :
#FXML
public void setNotificationsViewToScene() {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Notifications.fxml"));
GridPane yourNewView = fxmlLoader.load();
sceneContainer.getChildren().setAll(yourNewView);
Notifications notifications = fxmlLoader.getController();
notifications.passUserName(userName);
} catch (IOException e) {
e.printStackTrace();
}
}
Wow, >8 years old with lots of complicated answers and no mention of the >10 year old solution.
There is a dedicated fx:include element for nesting FXML files. It's not in the SceneBuilder library, so you need to write it manually. SceneBuilder can load and display nested FXML just fine though.
<AnchorPane fx:id="content">
<children>
<fx:include fx:id="nestedVista" source="vista2.fxml" />
</children>
</AnchorPane>
You can reference the nested Pane and controller using the fx:id and fx:id+"Controller"
#FXML
Pane nestedVista;
#FXML
VistaController nestedVistaController;
Documentation link: https://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html#nested_controllers

JavaFX: get controller object from node [duplicate]

I have 2 fxml files:
Layout (header, menubars and content)
Anchorpane (it's supposed to be placed inside the content from the other fxml file)
I would like to know how can I load the second file inside the content space from the "Master" scene. And is that a good thing to do working in javaFX or is it better to load a new scene?
I'm trying to do something like this, but it doesn't work:
#FXML
private AnchorPane content;
#FXML
private void handleButtonAction(ActionEvent event) {
content = (AnchorPane) FXMLLoader.load("vista2.fxml");
}
Thanks for the help.
Why your code does not work
The loader creates a new AnchorPane, but you never add the new pane to a parent in the scene graph.
Quick Fix
Instead of:
content = (AnchorPane) FXMLLoader.load("vista2.fxml");
Write:
content.getChildren().setAll(FXMLLoader.load("vista2.fxml"));
Replacing the content children with your new vista. The content itself remains in the scene graph, so when you set it's children, you are also attaching them to the scene graph at the same time.
You might need to play around with layout (e.g. work with auto resizing layouts like StackPanes rather than AnchorPanes) to get the exact behaviour you want.
Rather than just adopting the quick fix, I would advise reviewing the simple framework linked below as that might provide you with a more general purpose mechanism to get the behaviour you want.
Reference FXML Navigation Framework
I created a small framework for swapping fxml controlled content panes in and out of a portion of the main scene.
The mechanism of the framework is the same as suggested in kithril's answer.
A main pane for the outer fxml acts as a holder for child panes.
The main controller for the outer fxml supplies a public method that can be used to swap the child panes.
A convenience navigator class is statically initialized with the main controller for the outer layout.
The navigator provides a public static method to load a new child pane into the main container (by invoking a method on the main controller).
Child panes are generated in the navigator by their respective fxml loaders.
Why a Framework
The framework seems like overkill for answering your question, and perhaps it is. However, I have found that the two most asked topic related to FXML are:
Navigation between panes generated by FXML (this question).
How to pass data between FXML controllers.
So I felt that a small demo framework was warranted for this case.
Sample Framework Output
The first screen shows the application layout displaying the first vista. The contents are a header which is defined in the main application layout and an aliceblue colored interchangable child content pane.
In the next screen, the user has navigated to the second vista, which retains the constant header from the main layout and replaces the original child pane with a new coral colored child content pane. The new child has been loaded from a new fxml file.
Looking for Something More Substantial?
A lightweight framework which is more extensive and better supported than the sample framework from this question is afterburner.fx.
Looking for Something Even Simpler?
Just swap out the scene root: Changing Scenes in JavaFX.
Other Options?
Animated Transitions and others: Switch between panes in JavaFX
Im not sure about how effective this is, but seems to be just fine and what's more, much simpler to methods above.
https://www.youtube.com/watch?v=LDVztNtJWOo
As far as I understood what is happening here is this(its really similiar to what is happening in Start() method in application class) :
private void buttonGoToWindow3Action(ActionEvent event) throws IOException{
Parent window3; //we need to load the layout that we want to swap
window3 = (StackPane)FXMLLoader.load(getClass().getResource("/ScenePackage/FXMLWindow3"));
Scene newScene; //then we create a new scene with our new layout
newScene = new Scene(window3);
Stage mainWindow; //Here is the magic. We get the reference to main Stage.
mainWindow = (Stage) ((Node)event.getSource()).getScene().getWindow();
mainWindow.setScene(newScene); //here we simply set the new scene
}
However Im not a java expert and quite new to programing so it would be good if someone experienced would evaluate it.
EDIT:
Ive found even simpler method;
Go to MainApplication class and make static Stage parentWindow.
public static Stage parentWindow;
#Override
public void start(Stage stage) throws Exception {
parentWindow = stage;
Parent root = FXMLLoader.load(getClass().getResource("/ScenePackage/FXMLMainScene.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Now you get acces to your main Stage so anywhere in a program you can do something like that to change the scene:
Parent window1;
window1 = FXMLLoader.load(getClass().getResource("/ScenePackage/FXMLWindow1.fxml"));
//Scene newSceneWindow1 = new Scene(window1);
Stage mainStage;
//mainStage = (Stage) ((Node)event.getSource()).getScene().getWindow();
mainStage = MainApplication.parentWindow;
mainStage.getScene().setRoot(newSceneWindow1); //we dont need to change whole sceene, only set new root.
Others may have a better solution, but my solution has been to have a simple container like VBox in the outer fxml, then load the new content and add it as a child of the container. If you're only loading one or two forms, this might be the way go to go. However, for a more complete framework, I found this blog post helpful: https://blogs.oracle.com/acaicedo/entry/managing_multiple_screens_in_javafx1 She has source code for her framework which includes fancy transitions. Although it's meant to manage top level scenes, I found it easy to adapt for managing inner content regions too.
My example of the mask.
Using:
Main.getNavigation().load(View2.URL_FXML).Show();
Main.getNavigation().GoBack();
In this case, I recommend you to use custom component instead. First create a custom component for your content:
class Content2 extends AnchorPane {
Content() {
FXMLLoader loader = new FXMLLoader(getClass().getResource("vista2.fxml");
loader.setRoot(this);
loader.setController(this);
loader.load();
}
}
Replace the AnchorPane markup in the root of your vista2.fxml file with fx:root:
<fx:root type="javafx.scene.layout.AnchorPane" xmlns:fx="http://javafx.com/fxml">
...
</fx:root>
Then, you can do this simply by using custom event binding and arrow function. Add event handler property to your Content class:
private final ObjectProperty<EventHandler<ActionEvent>> propertyOnPreviousButtonClick = new SimpleObjectProperty<EventHandler<ActionEvent>>();
#FXML
private void onPreviousButtonClick(ActionEvent event) {
propertyOnPreviousButtonClick.get().handle(event)
}
public void setOnPreviousButtonClick(EventHandler<ActionEvent> handler) {
propertyOnPreviousButtonClick.set(handler);
}
Finally, bind your custom event handler in your java code or fxml:
#FXML
onNextButtonClick() {
Content2 content2 = new Content2();
content2.setOnPreviousButtonClick((event) -> {
Content1 content1 = new Content1();
layout.getChildren().clear();
layout.getChildren().add(content1);
});
layout.getChildren().clear();
layout.getChildren().add(content2);
}
If you don't want to add content dynamically, just setVisible() to true or false
Got stuck up in this too
Tried out most of the answers, wasn't what I wanted so I just used the ideals given to do this:
public class Main extends Application {
public static Stage homeStage;
#Override
public void start(Stage primaryStage) throws Exception{
homeStage = primaryStage;
Parent root = FXMLLoader.load(getClass().getResource("mainView.fxml"));
root.getStylesheets().add(getClass().getResource("stylesheet/custom.css").toExternalForm());
homeStage.setTitle("Classification of Living Organisms");
homeStage.setScene(new Scene(root, 600, 500));
homeStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
this is my main class. Main.java with the landing window/page mainView.fxml.
Used a little of #Tomasz idea, although confused me a lil before I did this in my mainController.java class:
public void gotoSubMenu(Event event) {
Parent window1;
try {
window1 = FXMLLoader.load(getClass().getResource("src/displayView.fxml"));
Stage window1Stage;
Scene window1Scene = new Scene(window1, 600, 500);
window1Stage = Main.homeStage;
window1Stage.setScene(window1Scene);
} catch (IOException e) {
e.printStackTrace();
}
}
created a new Parent window called 'window1' that loaded the second fxml file called 'displayView.fxml' in the src directory.
created an object of the main view stage and set the scene to the newly created scene whose root is window1.
Hope this helps the ones coming into #JavaFX now.
If you're looking for a way to make the button call the new fxml file, this worked for me.
#FXML
private void mainBClicked(ActionEvent event) throws IOException {
Stage stage;
Parent root;
stage=(Stage) ((Button)(event.getSource())).getScene().getWindow();
root = FXMLLoader.load(getClass().getResource("MainMenu.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Keeping the same scene container but changing the view inside the scene container...
Say the scene container you want to pass a new view and controller into is a GridPane layout named sceneContainer.
Establish a FXMLLoader object of the new view.
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Notifications.fxml"));
Create a matching container and loading the contents of the new view into it.
GridPane yourNewView = fxmlLoader.load();
set the new view to the sceneContainer. (setAll clears all children first)
sceneContainer.getChildren().setAll(yourNewView);
Get the controller object for the new view and call your method that starts the class logic
Notifications notifications = fxmlLoader.getController();
notifications.passUserName(userName);
A full example would look like this :
#FXML
public void setNotificationsViewToScene() {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Notifications.fxml"));
GridPane yourNewView = fxmlLoader.load();
sceneContainer.getChildren().setAll(yourNewView);
Notifications notifications = fxmlLoader.getController();
notifications.passUserName(userName);
} catch (IOException e) {
e.printStackTrace();
}
}
Wow, >8 years old with lots of complicated answers and no mention of the >10 year old solution.
There is a dedicated fx:include element for nesting FXML files. It's not in the SceneBuilder library, so you need to write it manually. SceneBuilder can load and display nested FXML just fine though.
<AnchorPane fx:id="content">
<children>
<fx:include fx:id="nestedVista" source="vista2.fxml" />
</children>
</AnchorPane>
You can reference the nested Pane and controller using the fx:id and fx:id+"Controller"
#FXML
Pane nestedVista;
#FXML
VistaController nestedVistaController;
Documentation link: https://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html#nested_controllers

Loading new fxml in the same scene

I have 2 fxml files:
Layout (header, menubars and content)
Anchorpane (it's supposed to be placed inside the content from the other fxml file)
I would like to know how can I load the second file inside the content space from the "Master" scene. And is that a good thing to do working in javaFX or is it better to load a new scene?
I'm trying to do something like this, but it doesn't work:
#FXML
private AnchorPane content;
#FXML
private void handleButtonAction(ActionEvent event) {
content = (AnchorPane) FXMLLoader.load("vista2.fxml");
}
Thanks for the help.
Why your code does not work
The loader creates a new AnchorPane, but you never add the new pane to a parent in the scene graph.
Quick Fix
Instead of:
content = (AnchorPane) FXMLLoader.load("vista2.fxml");
Write:
content.getChildren().setAll(FXMLLoader.load("vista2.fxml"));
Replacing the content children with your new vista. The content itself remains in the scene graph, so when you set it's children, you are also attaching them to the scene graph at the same time.
You might need to play around with layout (e.g. work with auto resizing layouts like StackPanes rather than AnchorPanes) to get the exact behaviour you want.
Rather than just adopting the quick fix, I would advise reviewing the simple framework linked below as that might provide you with a more general purpose mechanism to get the behaviour you want.
Reference FXML Navigation Framework
I created a small framework for swapping fxml controlled content panes in and out of a portion of the main scene.
The mechanism of the framework is the same as suggested in kithril's answer.
A main pane for the outer fxml acts as a holder for child panes.
The main controller for the outer fxml supplies a public method that can be used to swap the child panes.
A convenience navigator class is statically initialized with the main controller for the outer layout.
The navigator provides a public static method to load a new child pane into the main container (by invoking a method on the main controller).
Child panes are generated in the navigator by their respective fxml loaders.
Why a Framework
The framework seems like overkill for answering your question, and perhaps it is. However, I have found that the two most asked topic related to FXML are:
Navigation between panes generated by FXML (this question).
How to pass data between FXML controllers.
So I felt that a small demo framework was warranted for this case.
Sample Framework Output
The first screen shows the application layout displaying the first vista. The contents are a header which is defined in the main application layout and an aliceblue colored interchangable child content pane.
In the next screen, the user has navigated to the second vista, which retains the constant header from the main layout and replaces the original child pane with a new coral colored child content pane. The new child has been loaded from a new fxml file.
Looking for Something More Substantial?
A lightweight framework which is more extensive and better supported than the sample framework from this question is afterburner.fx.
Looking for Something Even Simpler?
Just swap out the scene root: Changing Scenes in JavaFX.
Other Options?
Animated Transitions and others: Switch between panes in JavaFX
Im not sure about how effective this is, but seems to be just fine and what's more, much simpler to methods above.
https://www.youtube.com/watch?v=LDVztNtJWOo
As far as I understood what is happening here is this(its really similiar to what is happening in Start() method in application class) :
private void buttonGoToWindow3Action(ActionEvent event) throws IOException{
Parent window3; //we need to load the layout that we want to swap
window3 = (StackPane)FXMLLoader.load(getClass().getResource("/ScenePackage/FXMLWindow3"));
Scene newScene; //then we create a new scene with our new layout
newScene = new Scene(window3);
Stage mainWindow; //Here is the magic. We get the reference to main Stage.
mainWindow = (Stage) ((Node)event.getSource()).getScene().getWindow();
mainWindow.setScene(newScene); //here we simply set the new scene
}
However Im not a java expert and quite new to programing so it would be good if someone experienced would evaluate it.
EDIT:
Ive found even simpler method;
Go to MainApplication class and make static Stage parentWindow.
public static Stage parentWindow;
#Override
public void start(Stage stage) throws Exception {
parentWindow = stage;
Parent root = FXMLLoader.load(getClass().getResource("/ScenePackage/FXMLMainScene.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Now you get acces to your main Stage so anywhere in a program you can do something like that to change the scene:
Parent window1;
window1 = FXMLLoader.load(getClass().getResource("/ScenePackage/FXMLWindow1.fxml"));
//Scene newSceneWindow1 = new Scene(window1);
Stage mainStage;
//mainStage = (Stage) ((Node)event.getSource()).getScene().getWindow();
mainStage = MainApplication.parentWindow;
mainStage.getScene().setRoot(newSceneWindow1); //we dont need to change whole sceene, only set new root.
Others may have a better solution, but my solution has been to have a simple container like VBox in the outer fxml, then load the new content and add it as a child of the container. If you're only loading one or two forms, this might be the way go to go. However, for a more complete framework, I found this blog post helpful: https://blogs.oracle.com/acaicedo/entry/managing_multiple_screens_in_javafx1 She has source code for her framework which includes fancy transitions. Although it's meant to manage top level scenes, I found it easy to adapt for managing inner content regions too.
My example of the mask.
Using:
Main.getNavigation().load(View2.URL_FXML).Show();
Main.getNavigation().GoBack();
In this case, I recommend you to use custom component instead. First create a custom component for your content:
class Content2 extends AnchorPane {
Content() {
FXMLLoader loader = new FXMLLoader(getClass().getResource("vista2.fxml");
loader.setRoot(this);
loader.setController(this);
loader.load();
}
}
Replace the AnchorPane markup in the root of your vista2.fxml file with fx:root:
<fx:root type="javafx.scene.layout.AnchorPane" xmlns:fx="http://javafx.com/fxml">
...
</fx:root>
Then, you can do this simply by using custom event binding and arrow function. Add event handler property to your Content class:
private final ObjectProperty<EventHandler<ActionEvent>> propertyOnPreviousButtonClick = new SimpleObjectProperty<EventHandler<ActionEvent>>();
#FXML
private void onPreviousButtonClick(ActionEvent event) {
propertyOnPreviousButtonClick.get().handle(event)
}
public void setOnPreviousButtonClick(EventHandler<ActionEvent> handler) {
propertyOnPreviousButtonClick.set(handler);
}
Finally, bind your custom event handler in your java code or fxml:
#FXML
onNextButtonClick() {
Content2 content2 = new Content2();
content2.setOnPreviousButtonClick((event) -> {
Content1 content1 = new Content1();
layout.getChildren().clear();
layout.getChildren().add(content1);
});
layout.getChildren().clear();
layout.getChildren().add(content2);
}
If you don't want to add content dynamically, just setVisible() to true or false
Got stuck up in this too
Tried out most of the answers, wasn't what I wanted so I just used the ideals given to do this:
public class Main extends Application {
public static Stage homeStage;
#Override
public void start(Stage primaryStage) throws Exception{
homeStage = primaryStage;
Parent root = FXMLLoader.load(getClass().getResource("mainView.fxml"));
root.getStylesheets().add(getClass().getResource("stylesheet/custom.css").toExternalForm());
homeStage.setTitle("Classification of Living Organisms");
homeStage.setScene(new Scene(root, 600, 500));
homeStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
this is my main class. Main.java with the landing window/page mainView.fxml.
Used a little of #Tomasz idea, although confused me a lil before I did this in my mainController.java class:
public void gotoSubMenu(Event event) {
Parent window1;
try {
window1 = FXMLLoader.load(getClass().getResource("src/displayView.fxml"));
Stage window1Stage;
Scene window1Scene = new Scene(window1, 600, 500);
window1Stage = Main.homeStage;
window1Stage.setScene(window1Scene);
} catch (IOException e) {
e.printStackTrace();
}
}
created a new Parent window called 'window1' that loaded the second fxml file called 'displayView.fxml' in the src directory.
created an object of the main view stage and set the scene to the newly created scene whose root is window1.
Hope this helps the ones coming into #JavaFX now.
If you're looking for a way to make the button call the new fxml file, this worked for me.
#FXML
private void mainBClicked(ActionEvent event) throws IOException {
Stage stage;
Parent root;
stage=(Stage) ((Button)(event.getSource())).getScene().getWindow();
root = FXMLLoader.load(getClass().getResource("MainMenu.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
Keeping the same scene container but changing the view inside the scene container...
Say the scene container you want to pass a new view and controller into is a GridPane layout named sceneContainer.
Establish a FXMLLoader object of the new view.
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Notifications.fxml"));
Create a matching container and loading the contents of the new view into it.
GridPane yourNewView = fxmlLoader.load();
set the new view to the sceneContainer. (setAll clears all children first)
sceneContainer.getChildren().setAll(yourNewView);
Get the controller object for the new view and call your method that starts the class logic
Notifications notifications = fxmlLoader.getController();
notifications.passUserName(userName);
A full example would look like this :
#FXML
public void setNotificationsViewToScene() {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Notifications.fxml"));
GridPane yourNewView = fxmlLoader.load();
sceneContainer.getChildren().setAll(yourNewView);
Notifications notifications = fxmlLoader.getController();
notifications.passUserName(userName);
} catch (IOException e) {
e.printStackTrace();
}
}
Wow, >8 years old with lots of complicated answers and no mention of the >10 year old solution.
There is a dedicated fx:include element for nesting FXML files. It's not in the SceneBuilder library, so you need to write it manually. SceneBuilder can load and display nested FXML just fine though.
<AnchorPane fx:id="content">
<children>
<fx:include fx:id="nestedVista" source="vista2.fxml" />
</children>
</AnchorPane>
You can reference the nested Pane and controller using the fx:id and fx:id+"Controller"
#FXML
Pane nestedVista;
#FXML
VistaController nestedVistaController;
Documentation link: https://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html#nested_controllers

How to get stage from controller during initialization?

I want to handle stage events (i.e. hiding) from my controller class. So all I have to do is to add a listener like this:
((Stage) myPane.getScene().getWindow()).setOn*whatIwant*(...);
But the problem is that initialization starts right after this code:
Parent root = FXMLLoader.load(getClass().getResource("MyGui.fxml"));
And before this code:
Scene scene = new Scene(root);
stage.setScene(scene);
Thus getScene returns null.
The only workaround I found by myself is to add a listener to myPane.sceneProperty, and when it becomes not null I get scene, add to it's windowProperty my listener handling which I finally retrieve stage. And it all ends with setting desired listeners to stage events.
I think there are too many listeners.
Is it the only way to solve my problem?
You can get the instance of the controller from the FXMLLoader after initialization via getController(), but you need to instantiate an FXMLLoader instead of using the static methods then.
I'd pass the stage after calling load() directly to the controller afterwards:
FXMLLoader loader = new FXMLLoader(getClass().getResource("MyGui.fxml"));
Parent root = (Parent)loader.load();
MyController controller = (MyController)loader.getController();
controller.setStageAndSetupListeners(stage); // or what you want to do
All you need is to give the AnchorPane an ID, and then you can get the Stage from that.
#FXML private AnchorPane ap;
Stage stage = (Stage) ap.getScene().getWindow();
From here, you can add in the Listener that you need.
Edit: As stated by EarthMind below, it doesn't have to be the AnchorPane element; it can be any element that you've defined.
I know it's not the answer you want, but IMO the proposed solutions are not good (and your own way is).
Why?
Because they depend on the application state. In JavaFX, a control, a scene and a stage do not depend on each other. This means a control can live without being added to a scene and a scene can exist without being attached to a stage. And then, at a time instant t1, control can get attached to a scene and at instant t2, that scene can be added to a stage (and that explains why they are observable properties of each other).
So the approach that suggests getting the controller reference and invoking a method, passing the stage to it adds a state to your application. This means you need to invoke that method at the right moment, just after the stage is created. In other words, you need to follow an order now:
1- Create the stage
2- Pass this created stage to the controller via a method.
You cannot (or should not) change this order in this approach. So you lost statelessness. And in software, generally, state is evil. Ideally, methods should not require any call order.
So what is the right solution?
There are two alternatives:
1- Your approach, in the controller listening properties to get the stage. I think this is the right approach. Like this:
pane.sceneProperty().addListener((observableScene, oldScene, newScene) -> {
if (oldScene == null && newScene != null) {
// scene is set for the first time. Now its the time to listen stage changes.
newScene.windowProperty().addListener((observableWindow, oldWindow, newWindow) -> {
if (oldWindow == null && newWindow != null) {
// stage is set. now is the right time to do whatever we need to the stage in the controller.
((Stage) newWindow).maximizedProperty().addListener((a, b, c) -> {
if (c) {
System.out.println("I am maximized!");
}
});
}
});
}
});
2- You do what you need to do where you create the Stage (and that's not what you want):
Stage stage = new Stage();
stage.maximizedProperty().addListener((a, b, c) -> {
if (c) {
System.out.println("I am maximized!");
}
});
stage.setScene(someScene);
...
The simplest way to get stage object in controller is:
Add an extra method in own created controller class like (it will be a setter method to set the stage in controller class),
private Stage myStage;
public void setStage(Stage stage) {
myStage = stage;
}
Get controller in start method and set stage
FXMLLoader loader = new FXMLLoader(getClass().getResource("MyFXML.fxml"));
OwnController controller = loader.getController();
controller.setStage(this.stage);
Now you can access the stage in controller
Platform.runLater works to prevent execution until initialization is complete. In this case, i want to refresh a list view every time I resize the window width.
Platform.runLater(() -> {
((Stage) listView.getScene().getWindow()).widthProperty().addListener((obs, oldVal, newVal) -> {
listView.refresh();
});
});
in your case
Platform.runLater(()->{
((Stage)myPane.getScene().getWindow()).setOn*whatIwant*(...);
});
Assign fx:id or declare variable to/of any node: anchorpane, button, etc. Then add event handler to it and within that event handler insert the given code below:
Stage stage = (Stage)((Node)((EventObject) eventVariable).getSource()).getScene().getWindow();
Hope, this works for you!!
You can get with node.getScene, if you don't call from Platform.runLater, the result is a null value.
example null value:
node.getScene();
example no null value:
Platform.runLater(() -> {
node.getScene().addEventFilter(KeyEvent.KEY_PRESSED, event -> {
//your event
});
});

Resources