0

controller for new task scene which contains a subScene:

public class newTaskController {




static private DBHandler dbHandler = new DBHandler();
static private Connection connection;

static {
    try {
        connection = dbHandler.getDBConnection();
    } catch (ClassNotFoundException | SQLException e) {
        e.printStackTrace();
    }
}

static private JdbcPreparedStatement preparedStatement;


private final ObservableList<String> proxies =
        FXCollections.observableArrayList();


FXMLLoader supremeWebsiteLoader = new FXMLLoader(getClass().getResource("Resources/supremeWebsite.fxml"));
Parent supremeWebsitePane = supremeWebsiteLoader.load();

FXMLLoader advancedSupremeWebsiteLoader = new FXMLLoader(getClass().getResource("Resources/advancedSupremeWebsite.fxml"));
Parent advancedSupremeWebsitePane = advancedSupremeWebsiteLoader.load();

FXMLLoader palaceWebsiteLoader = new FXMLLoader(getClass().getResource("Resources/palaceWebsite.fxml"));
Parent palaceWebsitePane = palaceWebsiteLoader.load();


@FXML
private SubScene subScene;

@FXML
private ComboBox<String> website;

@FXML
private ComboBox<String> profile;

@FXML
private ComboBox<String> proxyList;


@FXML
void getAdvanced(ActionEvent event) {
    String website_value = website.getValue();
    if (website_value.equals("Supreme")) {


        subScene.setRoot(advancedSupremeWebsitePane);

    }
    if (website_value.equals("Palace")) {

        //    subScene.setRoot(advancedPalaceWebsitePane);
        // does not yet exist

    } else {


    }
}

@FXML
void getGeneral(ActionEvent event) {
    String website_value = (String) website.getValue();
    if (website_value.equals("Supreme")) {


        subScene.setRoot(supremeWebsitePane);

    }
    if (website_value.equals("Palace")) {


        subScene.setRoot(palaceWebsitePane);

    }
}

public newTaskController() throws IOException {
}


@FXML
void cancel(ActionEvent event) {
    Stage primaryStage = (Stage) ((Node) event.getSource()).getScene().getWindow();
    primaryStage.close();

}

@FXML
void save(ActionEvent event) {


}

@FXML
void saveAndExit(ActionEvent event) {


}

@FXML
void updateWebsiteSelection(ActionEvent event) {
    String website_value = website.getValue();
    if (website_value.equals("Supreme")) {

        subScene.setRoot(supremeWebsitePane);



    }
    if (website_value.equals("Palace")) {

        subScene.setRoot(palaceWebsitePane);

    }

}

@FXML
void initialize() throws SQLException {


    profile.getSelectionModel().clearSelection();
    profile.getItems().clear();
    ProfileList.updateProfilesList();
    profile.setItems(ProfileList.getProfiles());
    profile.getSelectionModel().select(0);


    proxies.add("localhost");
    proxyList.setItems(proxies);

    website.getSelectionModel().select("Supreme");

    subScene.setRoot(supremeWebsitePane);
    proxyList.getSelectionModel().select(0);


}

}

Image shows newTask scene which has a blank subScene

when the application is launched the subScene can show any of two fxml documents representing different fields for users to input data into, for example:

newTask scene with subScene populated

the save button on this window is located on the newTask scene however when I click save the newTask controller needs to get data from 2 other controllers (supremeGeneralController and supremeAdvancedController), to then be saved to a database, however, I am having issues with conflicting static and non-static methods.

I'm not massively familiar with java so any help is appreciated

  • 2
    You should be able to get a reference to the child controller using loader.getController(); and then invoke actions on it. – purring pigeon Dec 30 '19 at 21:18
  • 2
    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. Instead you can use any Pane subclass to hold content. Additionally, I would recommend refactoring the data access out of the controller, perhaps using an accessor pattern similar to: [JavaFX MySQL connection example](https://stackoverflow.com/questions/25651641/javafx-mysql-connection-example-please). – jewelsea Dec 30 '19 at 21:21
  • @purring pigeon would you be able to show me a basic code example please –  Dec 31 '19 at 08:18
  • An example for nested controllers: https://github.com/james-d/Nested-Controller-Example/tree/master/src/nestedcontrollerexample – jewelsea Dec 31 '19 at 10:04
  • @jewelsea could you also provide a very basic code example for your solution to see if it fits my use case sufficiently, just to reiterate when I hit the save button i need to pull the values of two separate text fields (with an fx-id) –  Dec 31 '19 at 12:00

1 Answers1

1

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.

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • Thank you so much for such a detailed explanation I will have a look at this in more detail when I get some time –  Jan 01 '20 at 22:11
  • If it answers your question, mark the question as answered. – jewelsea Jan 01 '20 at 23:53