Search code examples
javafxfxml

javafx fxml - How to make Subscene the same area as the GridPane cell?


I have a program that will have a 2D section on the right, and a 3D section on the left. I use a GridPane to have all of the buttons and other stuff laid out, and then the 3D SubScene takes up the entire first column of the GridPane.
However, I am only able to set the size of the SubScene using hard-coded values, but I would like the SubScene to take up the entire width and height of the GridPane cells that it is inside of. Is there a way to do this?
Note: The GridPane's first column (where the subscene is) changes size when the window is resized, hence why I cannot just hard code the values.

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

<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.SubScene?>
<?import javafx.scene.Group?>
<?import javafx.scene.shape.Box?>
<?import javafx.scene.PerspectiveCamera?>

<BorderPane fx:controller="Controller"
    xmlns:fx="http://javafx.com/fxml" styleClass="root">

    <top>
        <MenuBar>
            <menus>
                <Menu text="File">
                    <items>
                        <MenuItem text="New"
                            onAction="#handleNewSimulationAction" />
                        <MenuItem text="Save"
                            onAction="#handleSaveSimulationAction" />
                        <MenuItem text="Load"
                            onAction="#handleLoadSimulationAction" />
                    </items>
                </Menu>
            </menus>
        </MenuBar>
    </top>

    <center>
        <GridPane hgap="10" vgap="10">

            <rowConstraints>
                <RowConstraints vgrow="NEVER" />
                <RowConstraints vgrow="ALWAYS" />
            </rowConstraints>

            <columnConstraints>
                <ColumnConstraints hgrow="ALWAYS" />
            </columnConstraints>

            <gridLinesVisible>true</gridLinesVisible>

            <SubScene fx:id="subscene" width="50" height="50"
                GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.rowSpan="6">

                <root>
                    <Region />
                </root>

            </SubScene>

            <Button text="Add" GridPane.columnIndex="1"
                GridPane.rowIndex="0" onAction="#handleAddAction" />

            <Button text="Edit" GridPane.columnIndex="2"
                GridPane.rowIndex="0" onAction="#handleEditAction" />

            <Button text="Delete" GridPane.columnIndex="3"
                GridPane.rowIndex="0" onAction="#handleDeleteAction" />

            <ScrollPane GridPane.columnIndex="1" GridPane.rowIndex="1"
                GridPane.columnSpan="3" />

            <Label text="Speed" GridPane.columnIndex="1"
                GridPane.rowIndex="2" />

            <Slider GridPane.columnIndex="2" GridPane.rowIndex="2"
                GridPane.columnSpan="2" />

            <Label text="Gravity" GridPane.columnIndex="1"
                GridPane.rowIndex="3" />

            <TextField GridPane.columnIndex="2" GridPane.rowIndex="3"
                GridPane.columnSpan="2" />

            <HBox GridPane.columnIndex="1" GridPane.rowIndex="4">
                <Button text="&lt;&lt;" onAction="#handleQuickRewindAction" />
                <Button text="&lt;" onAction="#handleRewindAction" />
            </HBox>

            <Text text="0.00" GridPane.columnIndex="2" GridPane.rowIndex="4" />

            <HBox GridPane.columnIndex="3" GridPane.rowIndex="4">
                <Button text="&gt;" onAction="#handleFastForwardAction" />
                <Button text="&gt;&gt;"
                    onAction="#handleQuickFastForwardAction" />
            </HBox>

            <Button text="Start" GridPane.columnIndex="2"
                GridPane.rowIndex="5" onAction="#handlePauseAction" />

        </GridPane>
    </center>

    <stylesheets>
        <URL value="@style.css" />
    </stylesheets>

</BorderPane>

The grey square in the image is the SubScene, with hard coded values so that it is visible. If values for the width and height are not hardcoded, it will simply not appear on the screen.


Solution

  • Firstly, there may be other ways as well, but this is my take on this issue :).

    I would like to recommend you to rethink about the layout implementation. I think it would be better to keep the right side part in its own gridPane and you primarily deal with only 2 sections (center- subScene and right- form layout). That way you don't unneccesarily mess with layout by keeping too many eggs in one basket.

    And regarding the SubScene, it does not fit in any of the standard layouts behavior (like no min/pref/max sizes). It pretty much has the same features as Shape, where you need to explicity set the width/height when required.

    So for that, you need to do some explicit calculations to determine the width/height of the SubScene whenever the main scene size is changed.

    Below is the quick demo for changes to your code: (If your window is a TRANSPARENT stage, then in the calculations, you may need to consider the window header size as well)

    enter image description here

    FXML Code:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.*?>
    <?import javafx.scene.layout.*?>
    <?import javafx.scene.SubScene?>
    <?import javafx.scene.text.Text?>
    <?import javafx.geometry.Insets?>
    <BorderPane fx:id="mainLayout" fx:controller="Controller" xmlns:fx="http://javafx.com/fxml"
                styleClass="root">
        <top>
            <MenuBar fx:id="menuBar">
                <menus>
                    <Menu text="File">
                        <items>
                            <MenuItem text="New"
                                      onAction="#handleNewSimulationAction"/>
                            <MenuItem text="Save"
                                      onAction="#handleSaveSimulationAction"/>
                            <MenuItem text="Load"
                                      onAction="#handleLoadSimulationAction"/>
                        </items>
                    </Menu>
                </menus>
            </MenuBar>
        </top>
        <right>
            <GridPane fx:id="sideLayout" hgap="10" vgap="10">
                <rowConstraints>
                    <RowConstraints vgrow="NEVER"/>
                    <RowConstraints vgrow="ALWAYS"/>
                </rowConstraints>
                <gridLinesVisible>true</gridLinesVisible>
                <padding>
                    <Insets left="10"/>
                </padding>
                <Button text="Add" GridPane.columnIndex="0" GridPane.rowIndex="0" onAction="#handleAddAction"
                        prefWidth="-1" minWidth="-Infinity"/>
                <Button text="Edit" GridPane.columnIndex="1" GridPane.rowIndex="0"
                        onAction="#handleEditAction" prefWidth="-1" minWidth="-Infinity"/>
                <Button text="Delete" GridPane.columnIndex="2" GridPane.rowIndex="0"
                        onAction="#handleDeleteAction" prefWidth="-1" minWidth="-Infinity"/>
    
                <ScrollPane GridPane.columnIndex="0" GridPane.rowIndex="1" GridPane.columnSpan="3"/>
    
                <Label text="Speed" GridPane.columnIndex="0" GridPane.rowIndex="2"/>
                <StackPane GridPane.columnIndex="1" GridPane.rowIndex="2" GridPane.columnSpan="2"/>
    
                <Label text="Gravity" GridPane.columnIndex="0" GridPane.rowIndex="3"/>
                <StackPane GridPane.columnIndex="1" GridPane.rowIndex="3" GridPane.columnSpan="2"/>
    
                <HBox GridPane.columnIndex="0" GridPane.rowIndex="4">
                    <Button text="&lt;&lt;" onAction="#handleQuickRewindAction" prefWidth="-1"
                            minWidth="-Infinity"/>
                    <Button text="&lt;" onAction="#handleRewindAction" prefWidth="-1" minWidth="-Infinity"/>
                </HBox>
                <Text text="0.00" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
                <HBox GridPane.columnIndex="2" GridPane.rowIndex="4">
                    <Button text="&gt;" onAction="#handleFastForwardAction" prefWidth="-1"
                            minWidth="-Infinity"/>
                    <Button text="&gt;&gt;"
                            onAction="#handleQuickFastForwardAction" prefWidth="-1" minWidth="-Infinity"/>
                </HBox>
    
                <Button text="Start" GridPane.columnIndex="1" GridPane.rowIndex="5"
                        onAction="#handlePauseAction" prefWidth="-1" minWidth="-Infinity"/>
            </GridPane>
        </right>
        <center>
            <SubScene fx:id="subscene">
                <root>
                    <StackPane style="-fx-background-color:yellow;-fx-border-width:1px;-fx-border-color:red;">
                        <Label text="Sub Scene" style="-fx-font-size:16px;"/>
                    </StackPane>
                </root>
            </SubScene>
        </center>
    </BorderPane>
    

    Controller code:

    @FXML
    private MenuBar menuBar;
    
    @FXML
    private SubScene subscene;
    
    @FXML
    private GridPane sideLayout;
    
    @FXML
    public void initialize() {
        subscene.sceneProperty().addListener((ob, ol, scene) -> {
            scene.heightProperty().addListener((obs, old, height) -> updateHeight());
            scene.widthProperty().addListener((obs, old, width) -> updateWidth());
        });
        sideLayout.heightProperty().addListener((obs, old, height) -> updateHeight());
        sideLayout.widthProperty().addListener((obs, old, width) -> updateWidth());
    }
    
    private void updateHeight() {
        double sceneHeight = subscene.getScene().getHeight();
        subscene.setHeight(sceneHeight - menuBar.getHeight());
    }
    
    private void updateWidth() {
        double sceneWidth = subscene.getScene().getWidth();
        subscene.setWidth(sceneWidth - sideLayout.getWidth());
    }