Search code examples
javajavafxfxmlgridpaneborderpane

GridPane not binding to BorderPane


I'm fairly new to JavaFX and FXML. I've coded some java swing but that is a long time ago. To learn something new I'm creating a little board game but I can't seem to line up my GridPane with Rectangles inside it properly (The GridPane is inside a BorderPane). By that I mean (I believe) my GridPane is not binded properly. As you can see in my screenshot the GridPane doesn't move with the BorderPane when I resize the window.

These are my classes:

Main

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        BorderPane root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        Scene scene = new Scene(root, 800, 900);

        primaryStage.setTitle("Testing");
        primaryStage.setScene(scene);
        primaryStage.show();
        primaryStage.toFront();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Controller:

public class Controller implements Initializable {


    @FXML private GridPane gridBoard;
    @FXML private BorderPane borderPane;


    public void initialize(java.net.URL location,
                       java.util.ResourceBundle resources) {

        gridBoard.prefHeightProperty().bind(borderPane.heightProperty());
        gridBoard.prefWidthProperty().bind(borderPane.widthProperty());

    }

    public void fillBoardEvent(ActionEvent event) {

        for(int i = 0; i < 50; i++) {
            for(int j = 0; j < 50; j++) {
                InitiateCells n;

                // This is for creating the rectangles
                // and to give some specific cells 
                // different color
                if(i == 24) {
                    if(j == 19 || j == 20 || j == 21 || j == 22 || j == 23 || j == 24 || j == 25 || j == 26 || j == 27 ||
                        j == 28 || j == 29) {

                        n = new InitiateCells("" + i + "/" + j, 12, 12, i, j, 0);

                    } else {
                        n = new InitiateCells("" + i + "/" + j, 12, 12, i, j);
                    }
                } else {
                    n = new InitiateCells("" + i + "/" + j, 12, 12, i, j);
                }
            }
        }

    }

    public void exitEvent(ActionEvent event) {
        System.exit(0);
    }

    public class InitiateCells extends Rectangle {
        private String name;
        private double width;
        private double height;


        public InitiateCells(String name, double width, double height, int rowIndex, int columnIndex) {
            super(width, height);
            this.name = name;
            this.width = width;
            this.height = height;

            setFill(Color.web("#282828"));
            setStroke(Color.web("#3b3b3b"));
            setStrokeType(StrokeType.OUTSIDE);
            setStrokeWidth(2.0);


            gridBoard.add(this, columnIndex, rowIndex);

    }


        public InitiateCells(String name, double width, double height, int rowIndex, int columnIndex, int value) {
            super(width, height);
            this.name = name;
            this.width = width;
            this.height = height;

            setFill(Color.web("#282828"));
            setStroke(Color.web("#3b3b3b"));
            setStrokeType(StrokeType.OUTSIDE);
            setStrokeWidth(2.0);


            gridBoard.add(this, columnIndex, rowIndex);
    }

Sample.fxml

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="800.0"
        prefWidth="800.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
        fx:controller="sample.Controller" fx:id="borderPane">


    <center>
        <GridPane prefHeight="770.0" prefWidth="800.0" BorderPane.alignment="CENTER" fx:id="gridBoard" >
            <children>

            </children>
        </GridPane>
    </center>

    <top>
        <VBox prefHeight="30.0" prefWidth="800.0" BorderPane.alignment="TOP_CENTER">
            <children>
                <MenuBar>
                    <menus>
                        <Menu text="File">
                            <items>
                                <MenuItem text="New Game" onAction="#fillBoardEvent"/>
                                <MenuItem text="Save as GIF"/>
                                <MenuItem text="Export statistics"/>
                                <MenuItem text="Exit" onAction="#exitEvent"/>
                            </items>
                        </Menu>
                        <Menu text="Help">
                            <items>
                                <MenuItem text="Game of Life"/>
                                <MenuItem text="How to play"/>
                                <MenuItem text="Open Javadoc"/>
                            </items>
                        </Menu>
                        <Menu text="About">
                            <MenuItem text="Game of Life"/>
                            <MenuItem text="How to play"/>
                            <MenuItem text="Open Javadoc"/>
                        </Menu>
                    </menus>
                </MenuBar>
            </children>
        </VBox>
    </top>

    <bottom>
        <VBox prefHeight="70.0" prefWidth="800.0" BorderPane.alignment="BOTTOM_CENTER">
            <children>
                <ProgressBar prefHeight="70.0" prefWidth="800.0" progress="50.0" />
            </children>
        </VBox>
    </bottom>
</BorderPane>

This is the problem; When I try to resize the window, my GridPane and VBox stays in the same place. It should go from edge to edge when I resize the window. This is how it looks: Board


Solution

  • The problem is not that the grid pane is not resizing: the problem is that when the grid pane resizes, the columns and rows do not resize. Hence the grid pane has lots of blank space after the horizontal space taken up by the columns and after the vertical space taken up by the rows. (This also explains why it's not appearing centered: the grid pane itself is centered in the border pane, but the rows and columns appear in the top left of the grid pane itself.)

    You can fix this easily enough by setting the column constraints and row constraints on the grid pane:

    public void fillBoardEvent(ActionEvent event) {
    
        int numRows = 50 ;
        int numColumns = 50 ;
    
        for (int i = 0 ; i < numRows; i++) {
            RowConstraints row = new RowConstraints();
            row.setVgrow(Priority.ALWAYS);
            gridBoard.getRowConstraints().add(row);
        }
    
        for (int j = 0 ; j < numColumns; j++) {
            ColumnConstraints col = new ColumnConstraints();
            col.setHgrow(Priority.ALWAYS);
            gridBoard.getColumnConstraints().add(col);            
        }
    
        for(int i = 0; i < numRows; i++) {
            for(int j = 0; j < numColumns; j++) {
                InitiateCells n;
    
                // code as before...
            }
        }
    }
    

    Now the columns and rows will grow, but their content will not. So you end up with space between the cells like this:

    enter image description here

    You can't really fix this easily using Rectangles, because Rectangles are not resizable, and so whatever settings you put on the grid pane, it won't be able to resize the rectangles for you. (You could introduce bindings between the grid pane's size and the rectangles width and height, but that will get very ugly very fast.) I would use Regions instead of Rectangles, as Regions are resizable. Note that the values you assign as the width and height of the cells are really preferred width and height, since you want them to resize with the window.

    A Region does not have fill, stroke etc, but it can by styled via CSS, so I would do

    public class InitiateCells extends Region {
        private String name;
        private double width;
        private double height;
    
    
        public InitiateCells(String name, double width, double height, int rowIndex, int columnIndex) {
            this.name = name;
            this.width = width;
            this.height = height;
    
            setPrefSize(width, height);
            getStyleClass().add("board-cell");
    
            gridBoard.add(this, columnIndex, rowIndex);
    
        }
    
    
        public InitiateCells(String name, double width, double height, int rowIndex, int columnIndex, int value) {
            this(name, width, height, rowIndex, columnIndex);
            pseudoClassStateUpdated(PseudoClass.getPseudoClass("on"), true);
        }
    
    }
    

    Now in your main class, add an external stylesheet:

    scene.getStylesheets().add(getClass().getResource("board-style.css").toExternalForm());
    

    and add a board-style.css file with

    .board-cell {
        -fx-background-color: #3b3b3b, #282828 ;
        -fx-background-insets: 0, 2 ;
    }
    
    .board-cell:on {
        -fx-background-color: #3b3b3b, white ;
        -fx-background-insets: 0, 2 ;   
    }