FXML BorderPane size not changing despite using setMinSize() and setPrefSize() in JavaFX application

53 Views Asked by At

I'm facing an issue where the size of my BorderPane in an FXML file (mainLayoutBorderPane) is not changing, remaining at (0,0), despite attempting to set it using setMinSize() or setPrefSize() in a custom hook styleSetup() I wrote (I expect it to execute after my controller initialization).

I think I'm misunderstanding some concepts about the lifetime of scenes and controllers in JavaFX, or I might have made a mistake in the FXML code. Let me explain my problem.

This is the entry point of my app, where I set the admin login page as the starting scene with a SceneManager:

@SpringBootApplication
public class App extends Application {
    private ConfigurableApplicationContext springContext;

    @Override
    public void init() throws Exception {
        springContext = SpringApplication.run(App.class);
    }

    @Override
    public void start(Stage stage) throws IOException {
        stage.setWidth(AppConfig.LOGIN_STAGE_WIDTH);
        stage.setHeight(AppConfig.LOGIN_STAGE_HEIGHT);
        stage.initStyle(StageStyle.UNDECORATED);
        SceneManager sceneManager = new SceneManager(stage);
        sceneManager.setSpringContext(springContext);
        sceneManager.switchingScene("/dev/bananaback/fxml/admin_login_page.fxml");
        sceneManager.showRootStage();
    }

    @Override
    public void stop() throws Exception {
        springContext.stop();
    }

    public static void main(String[] args) {
        launch(App.class, args);
    }

}

I use a SceneManager to support switching scenes in my JavaFX application, which involves holding the root stage and a hash map of scenes. To integrate Spring dependency injection (DI) into my code, I made some modifications based on an answer I found on Stack Overflow here. These modifications include writing a setSpringContext() method to retrieve the springContext for the loader.setControllerFactory() method.

Despite implementing these changes, I'm encountering issues with the size of a BorderPane in one of my FXML files (mainLayoutBorderPane). Despite setting its size using setMinSize() or setPrefSize() within a custom hook method styleSetup(), the size remains at (0,0).

I suspect there might be misunderstandings regarding the lifecycle of scenes and controllers in JavaFX, or I may have made mistakes in the FXML code. Let me elaborate on my problem.

At the entry point of my application, I set the admin login page as the starting scene using the SceneManager. Here's how I've structured my code:

public class SceneManager {
    private final Stage rootStage;
    private ConfigurableApplicationContext springContext;

    public SceneManager(Stage rootStage) {
        if (rootStage == null) {
            throw new IllegalArgumentException();
        }
        this.rootStage = rootStage;
    }

    public void setSpringContext(ConfigurableApplicationContext springContext) {
        this.springContext = springContext;
    }

    private final Map<String, Scene> scenes = new HashMap<>();

    public void switchingScene(String url) {
        Scene scene = scenes.computeIfAbsent(url, u -> {
            FXMLLoader loader = new FXMLLoader(getClass().getResource(u));
            loader.setControllerFactory(springContext::getBean);
            try {
                Pane p = loader.load();
                BaseController controller = loader.getController();
                controller.setSceneManager(this);
                controller.styleSetup();
                return new Scene(p);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        });
        rootStage.setScene(scene);
    }

    public void setStageSize(double x, double y) {
        rootStage.setWidth(x);
        rootStage.setHeight(y);
        rootStage.centerOnScreen();
    }

    public void showRootStage() {
        this.rootStage.show();
    }

    public void closeStage() {
        this.rootStage.close();
    }
}

My admin_login_page.xml:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<HBox
    fx:id="containerHBox"
    xmlns="http://javafx.com/javafx"
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="dev.bananaback.controllers.LoginController"
    stylesheets="@../css/admin_login_page.css">

    <!-- Left panel containing login form -->
    <VBox fx:id="leftPanelVBox" styleClass="left-panel">
        <StackPane fx:id="leftPanelPane">
            <Label fx:id="smallTitle" text="Sign in to dashboard" styleClass="title"/>
            <Label fx:id="phoneNumberLabel" text="Phone number" styleClass="field-title"/>
            <TextField fx:id="phoneNumberField" promptText="Type your phone number" styleClass="input-field"/>
            <Label fx:id="passwordLabel" text="Password" styleClass="field-title"/>
            <PasswordField fx:id="passwordField" promptText="Password" styleClass="input-field"/>
            <Button fx:id="loginButton" text="Login" styleClass="rounded-button" onAction="#loginUser"/>
            <Label fx:id="errorMessageLabel" styleClass="error"/>
        </StackPane>
    </VBox>

    <!-- Right panel containing big text -->
    <VBox fx:id="rightPanelVBox" styleClass="right-panel">
        <StackPane fx:id="rightPanelPane">
            <Label fx:id="welcomeLabel" text="Take their money :)" styleClass="welcome"/>
            <Label fx:id="smallWelcomeLabel" text="Empty their wallet!" styleClass="welcome-small"/>
        </StackPane>
    </VBox>
</HBox>

I use fx:controller to specify which controller associated with that fxml. The elements in this fxml is just the structure, all the sizes and locations is set in the controller.

package dev.bananaback.controllers;
// ... import statements

@Component
public class LoginController extends BaseController implements Initializable {

    @FXML
    private VBox leftPanelVBox;

    @FXML
    private VBox rightPanelVBox;

    @FXML
    private HBox containerHBox;

    @FXML
    private Label smallTitle;

    @FXML
    private StackPane leftPanelPane;

    @FXML
    private StackPane rightPanelPane;

    @FXML
    private TextField phoneNumberField;

    @FXML
    private PasswordField passwordField;

    @FXML
    private Label phoneNumberLabel;

    @FXML
    private Label passwordLabel;

    @FXML
    private Label welcomeLabel;

    @FXML
    private Label smallWelcomeLabel;

    @FXML
    private Button loginButton;

    @FXML
    private void loginUser() throws IOException {
        // sceneManager.closeStage();
        sceneManager.switchingScene("/dev/bananaback/fxml/main_layout.fxml");
    }

    public LoginController() {
        System.out.println("Login controller constructed");
    }

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        System.out.println("Login controller initialized");
    }

    @Override
    public void styleSetup() {
        double halfWidth = AppConfig.LOGIN_STAGE_WIDTH / 2.0;
        leftPanelVBox.setPrefWidth(halfWidth);
        rightPanelVBox.setPrefWidth(halfWidth);

        leftPanelPane.setPrefWidth(halfWidth);
        leftPanelPane.setPrefHeight(AppConfig.LOGIN_STAGE_HEIGHT);
        leftPanelPane.setLayoutX(0);
        leftPanelPane.setLayoutY(0);

        rightPanelPane.setPrefWidth(halfWidth);
        rightPanelPane.setPrefHeight(AppConfig.LOGIN_STAGE_HEIGHT);
        rightPanelPane.setLayoutX(halfWidth);
        rightPanelPane.setLayoutY(0);

        smallTitle.setTranslateX(0);
        smallTitle.setTranslateY(-(AppConfig.LOGIN_STAGE_HEIGHT / 2 - 145));

        phoneNumberLabel.setTranslateX(-100);
        phoneNumberLabel.setTranslateY(-80);

        passwordLabel.setTranslateX(-115);
        passwordLabel.setTranslateY(-10);

        phoneNumberField.setPrefWidth(280);
        phoneNumberField.setMaxWidth(280);
        phoneNumberField.setPrefHeight(35);
        phoneNumberField.setTranslateX(0);
        phoneNumberField.setTranslateY(-50);

        passwordField.setPrefWidth(280);
        passwordField.setMaxWidth(280);
        passwordField.setPrefHeight(35);
        passwordField.setTranslateX(0);
        passwordField.setTranslateY(20);

        loginButton.setTranslateX(0);
        loginButton.setTranslateY(80);

        welcomeLabel.setTranslateX(0);
        welcomeLabel.setTranslateY(-30);

        smallWelcomeLabel.setTranslateX(-100);
        smallWelcomeLabel.setTranslateY(10);

        System.out.println("Login controlle style set");

    }

}

All of my controllers extends this BaseController:

public abstract class BaseController {
    protected SceneManager sceneManager;

    public void setSceneManager(SceneManager sceneManager) {
        this.sceneManager = sceneManager;
    }

    public abstract void styleSetup();
}

I add an abstract method styleSetup() to call it after retrieving the controller in SceneManager swtich scene method:

public void switchingScene(String url) {
        Scene scene = scenes.computeIfAbsent(url, u -> {
            FXMLLoader loader = new FXMLLoader(getClass().getResource(u));
            loader.setControllerFactory(springContext::getBean);
            try {
                Pane p = loader.load();
                BaseController controller = loader.getController();
                controller.setSceneManager(this);
                controller.styleSetup(); // <----- 
                return new Scene(p);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        });
        rootStage.setScene(scene);
    }

And for this login view, everything works well as I expected. The style of the FXML is set successfully in the styleSetup() method of the controller. However, the problem arises when I attempt to switch to the main layout view from the LoginController, where the main layout consists of a BorderPane containing a navigation bar on the top and the rest being the main content.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>

<BorderPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="dev.bananaback.controllers.MainLayoutController"
            fx:id="mainLayoutBorderPane">

    <!-- Navigation Bar -->
    <VBox id="navBar" BorderPane.alignment="TOP_LEFT">
        <fx:include source="nav_bar.fxml" />
    </VBox>

    <!-- Main Content -->
    <VBox id="mainContent" BorderPane.alignment="BOTTOM_LEFT">
        <fx:include source="main_content.fxml" />
    </VBox>
    
</BorderPane>

The style is messed up after switching to the main layout view. Upon investigation, I discovered that the BorderPane size is 0. To address this issue, I attempted to change the size in my MainLayoutController's styleSetup() method:

package dev.bananaback.controllers;

import java.net.URL;
import java.util.ResourceBundle;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import dev.bananaback.AppConfig;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.BorderPane;

@Component
public class MainLayoutController extends BaseController implements Initializable {
    private MainContentController mainContentController;
    private NavBarController navBarController;

    @FXML
    private BorderPane mainLayoutBorderPane;

    public MainLayoutController() {
        System.out.println("Main layout controller constructed");

    }

    @Autowired
    public MainLayoutController(MainContentController mainContentController, NavBarController navBarController) {
        this.mainContentController = mainContentController;
        this.navBarController = navBarController;
        System.out.println("Main layout controller constructed");
    }

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        navBarController.sayHello();
        System.out.println("Main layout controller initialized");
    }

    @Override
    public void styleSetup() {

        mainLayoutBorderPane.setMinSize(AppConfig.MAIN_STAGE_WIDTH,
                AppConfig.MAIN_STAGE_HEIGHT - AppConfig.MAIN_STAGE_NAV_BAR_HEIGHT);
        mainLayoutBorderPane.setPrefSize(AppConfig.MAIN_STAGE_WIDTH,
                AppConfig.MAIN_STAGE_HEIGHT - AppConfig.MAIN_STAGE_NAV_BAR_HEIGHT);

        System.out.println(mainLayoutBorderPane.getWidth());
        System.out.println(mainLayoutBorderPane.getHeight());
        System.out.println("Main layout controller style set");

    }
}

I expected the BorderPane size to change after setting it in the styleSetup() method of MainLayoutController, but it remains unchanged. Upon further investigation, I attempted to print some debugging information, and here are the results:

2024-03-21T12:33:02.374+07:00  INFO 20668 --- [JavaFX-Launcher] o.s.boot.SpringApplication               : Starting application using Java 17.0.8 with PID 20668 (started by LENOVO in d:\Java_related\complexscene)
2024-03-21T12:33:02.379+07:00  INFO 20668 --- [JavaFX-Launcher] o.s.boot.SpringApplication               : No active profile set, falling back to 1 default profile: "default"
Login controller constructed <---
Main layout controller constructed <---
2024-03-21T12:33:03.094+07:00  INFO 20668 --- [JavaFX-Launcher] o.s.boot.SpringApplication               : Started application in 1.111 seconds (process running for 1.837)
Login controller initialized <---
Login controlle style set <---
Hello world! <---
Main layout controller initialized <---
0.0
0.0
Main layout controller style set <---

The size of the border pane is still 0. I've observed that the style set method is called after the controller is initialized. Additionally, I've printed something in the constructor and noticed that only one instance of MainLayoutController is created. However, I don't know what mistake I've made here. Please help me fix this. You can find my code at the following GitHub repository: SO-JavaFX-help_request. Any assistance would be greatly appreciated. Thank you so much for your help.

0

There are 0 best solutions below