Key solution elements
Don't use a SubScene
, a SubScene is only necessary when you need to include another scene with different attributes in your scene (for example, you have a scene with 2D controls and you want to include a 3D depth-sorted SubScene). You don't have such a situation, so you don't need a SubScene.
The solution provided uses a technique known as nested controllers.
In the parent fxml file, include child fxml files:
<Pane>
<fx:include source="group-editor.fxml" fx:id="groupEditor"/>
<fx:include source="person-editor.fxml" fx:id="personEditor"/>
</Pane>
In the parent controller provide @FXML
references for both the child controllers and the root nodes of the child scene graphs (which are identified by the fx:id
values). The FXMLLoader will automatically populate these values when the parent controller is loaded.
@FXML
private VBox groupEditor;
@FXML
private GroupEditorController groupEditorController;
@FXML
private VBox personEditor;
@FXML
private PersonEditorController personEditorController;
Have the parent controller only show one child at a time, depending upon the application state or user selection:
groupEditor.setVisible(false);
groupEditorControl.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
groupEditor.setVisible(isSelected);
personEditor.setVisible(!isSelected);
});
In each of the child controllers, define setters which allow you to set the parent controller, and invoke those setters in the initialize
function of the parent controller.
groupEditorController.setDepartmentController(this);
personEditorController.setDepartmentController(this);
Now that you have done that, the initialize()
function of a child controller can make use of both the elements which are defined for the child controller and any elements defined for the parent controller:
saveButton.setOnAction(event ->
db.savePerson(
departmentController.getDepartmentName(),
firstName.getText(),
lastName.getText()
)
);
Minimal application example
I will will try to provide the minimal amount of code needed to provide an answer, based upon your stated existing FXML structure and a mock database, even though such an answer will not be optimal for all applications (perhaps including yours).
Even with a minimal application, what you are asking for is quite involved, so there is a bit of code required in different files and classes.
Sample Solution with Nested Controllers
For the sample, it imagines you are editing information about a department. You can set the department name. You can save either people or groups associated with the department to the department database via different forms.
The solution is based on james-d's nested controller example, but simplified from that and modified greatly to more closely match the description in your question of the specific problem that you are trying to solve.
To gain a better understanding of the solution approach, read the fxml documentation section on nested controllers, which should give you some idea of how the solution works.
All code is in a package named controllers
.
Database.java
Mock database.
package controllers;
public class Database {
private static Database instance = new Database();
public static Database getInstance() {
return instance;
}
public void savePerson(String department, String firstName, String lastName) {
System.out.println("Saved person " + firstName + " " + lastName + " into department " + department);
}
public void saveGroup(String department, String groupName, String description) {
System.out.println("Saved group " + groupName + " with description " + description + " into department " + department);
}
}
DepartmentEditorApp.java
Main application class
package controllers;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class DepartmentEditorApp extends Application {
@Override
public void start(Stage stage) throws IOException {
Scene scene = new Scene(
FXMLLoader.load(
getClass().getResource(
"department-editor.fxml"
)
)
);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
department-editor.fxml
Main application layout, includes other fxml files for editing either groups or people.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="controllers.DepartmentController" spacing="10">
<padding>
<Insets topRightBottomLeft="10" />
</padding>
<HBox spacing="10">
<Label text="Department:"/>
<TextField fx:id="departmentName" text="Engineering"/>
</HBox>
<RadioButton text="Group Editor" fx:id="groupEditorControl"/>
<Pane>
<fx:include source="person-editor.fxml" fx:id="personEditor"/>
<fx:include source="group-editor.fxml" fx:id="groupEditor"/>
</Pane>
</VBox>
DepartmentController.java
Controls the main application display.
package controllers;
import javafx.fxml.FXML;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
public class DepartmentController {
@FXML
private TextField departmentName;
@FXML
private RadioButton groupEditorControl;
@FXML
private VBox groupEditor;
@FXML
private GroupEditorController groupEditorController;
@FXML
private VBox personEditor;
@FXML
private PersonEditorController personEditorController;
public void initialize() {
groupEditor.setVisible(false);
groupEditorControl.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
groupEditor.setVisible(isSelected);
personEditor.setVisible(!isSelected);
});
groupEditorController.setDepartmentController(this);
personEditorController.setDepartmentController(this);
}
public String getDepartmentName() {
return departmentName.getText();
}
}
person-editor.fxml
A child data entry form.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="controllers.PersonEditorController" spacing="10">
<Label text="Person Editor"/>
<GridPane>
<Label text="First Name:">
<GridPane.columnIndex>0</GridPane.columnIndex>
<GridPane.rowIndex>0</GridPane.rowIndex>
</Label>
<Label text="Last Name:">
<GridPane.columnIndex>0</GridPane.columnIndex>
<GridPane.rowIndex>1</GridPane.rowIndex>
</Label>
<TextField fx:id="firstName" text="Kevin">
<GridPane.columnIndex>1</GridPane.columnIndex>
<GridPane.rowIndex>0</GridPane.rowIndex>
</TextField>
<TextField fx:id="lastName" text="Anderson">
<GridPane.columnIndex>1</GridPane.columnIndex>
<GridPane.rowIndex>1</GridPane.rowIndex>
</TextField>
</GridPane>
<Button text="Save" fx:id="saveButton"/>
</VBox>
PersonEditorController.java
A controller for a child form.
package controllers;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
public class PersonEditorController {
private Database db = Database.getInstance();
private DepartmentController departmentController;
@FXML
public Button saveButton;
@FXML
private TextField firstName;
@FXML
private TextField lastName;
public void initialize() {
saveButton.setOnAction(event ->
db.savePerson(
departmentController.getDepartmentName(),
firstName.getText(),
lastName.getText()
)
);
}
public void setDepartmentController(DepartmentController departmentController) {
this.departmentController = departmentController;
}
}
group-editor.fxml
Another child data entry form.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="controllers.GroupEditorController" spacing="10">
<Label text="Group Editor"/>
<GridPane >
<Label text="Group Name:">
<GridPane.columnIndex>0</GridPane.columnIndex>
<GridPane.rowIndex>0</GridPane.rowIndex>
</Label>
<Label text="Group Description:">
<GridPane.columnIndex>0</GridPane.columnIndex>
<GridPane.rowIndex>1</GridPane.rowIndex>
</Label>
<TextField fx:id="groupName" text="Hardware">
<GridPane.columnIndex>1</GridPane.columnIndex>
<GridPane.rowIndex>0</GridPane.rowIndex>
</TextField>
<TextField fx:id="groupDescription" text="Nuts and Bolts">
<GridPane.columnIndex>1</GridPane.columnIndex>
<GridPane.rowIndex>1</GridPane.rowIndex>
</TextField>
</GridPane>
<Button text="Save" fx:id="saveButton"/>
</VBox>
GroupEditorController.java
Another controller for a child entry form.
package controllers;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
public class GroupEditorController {
private Database db = Database.getInstance();
private DepartmentController departmentController;
@FXML
private Button saveButton;
@FXML
private TextField groupName;
@FXML
private TextField groupDescription;
public void initialize() {
saveButton.setOnAction(event ->
db.saveGroup(
departmentController.getDepartmentName(),
groupName.getText(),
groupDescription.getText()
)
);
}
public void setDepartmentController(DepartmentController departmentController) {
this.departmentController = departmentController;
}
}
Alternatives
There are other ways this could be done rather than the nested controller approach demonstrated here. For example, see the answer to Loading new fxml in the same scene, which just replaces a pane in the main controller frame as needed rather than nested includes with panes hiding and showing as shown below.
Recommended: A more robust architecture for your application
What you should do is provide a more versatile architecture for your application and code to fit those architectural constructs. Example elements of such an improved architecture may be mvvm with a DAO accessor pattern. If it were me, I'd also introduce Spring injection of model objects and services into your controllers and use Spring Data for the data storage. However, describing such an architecture and how to apply it to solve your problems is out of scope for a StackOverflow answer. Also, be careful trying to apply more elaborate architectures, frameworks and libraries that you may have difficulty understanding and using.