Dynamically add elements to a fixed-size GridPane in JavaFX

Koln picture Koln · Apr 24, 2014 · Viewed 25.2k times · Source

I would like to display a grid containing a various number of rectangles in JavaFX. It is important that this grid cannot be resized.

I chose the GridPane layout. I dynamically add javafx.scene.shape.Rectangle to it. Here's how my grid looks like with 2 rows and 4 columns.

Grid 4x2

Upon resizing, I would like it to keep the same overall shape, that is to say each Rectangle having the same size and keeping an horizontal and vertical gaps between my Rectangles.

However, here's what I get with a 4x4 grid: enter image description here

The problems being:

  • The last row and last column do not have the same size as the rest of the Rectangles.
  • The gaps have disappeared.

Here is my code responsible for refreshing the display:

public void refreshConstraints() {
    getRowConstraints().clear();
    getColumnConstraints().clear();

    for (int i = 0; i < nbRow; i++) {
        RowConstraints rConstraint = new RowConstraints();
        // ((nbRow - 1) * 10 / nbRow) = takes gap into account (10% of height)
        rConstraint.setPercentHeight(100 / nbRow - ((nbRow - 1) * 10 / nbRow));
        getRowConstraints().add(rConstraint);
    }

    for (int i = 0; i < nbColumn; i++) {
        ColumnConstraints cConstraint = new ColumnConstraints();
        cConstraint.setPercentWidth(100 / nbColumn - ((nbColumn - 1) * 10 / nbColumn));
        getColumnConstraints().add(cConstraint);
    }

}

Using the setFillWidth and setHgrow yields no result either, the gap is kept between my Rectangles, but the Rectangles aren't resized and they overlap the rest of my GUI elements.


EDIT: MCVE code:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.RowConstraints;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class DynamicGrid extends Application {

    //Class containing grid (see below)
    private GridDisplay gridDisplay;

    @Override
    public void start(Stage primaryStage) {

        //Represents the grid with Rectangles
        gridDisplay = new GridDisplay(400, 200);

        //Fields to specify number of rows/columns
        TextField rowField = new TextField();
        TextField columnField = new TextField();
        //Function to set an action when text field loses focus
        buildTextFieldActions(rowField, columnField);
        HBox fields = new HBox();
        fields.getChildren().add(rowField);
        fields.getChildren().add(new Label("x"));
        fields.getChildren().add(columnField);

        BorderPane mainPanel = new BorderPane();
        mainPanel.setLeft(gridDisplay.getDisplay());
        mainPanel.setBottom(fields);

        Scene scene = new Scene(mainPanel);
        primaryStage.setTitle("Test grid display");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

    private void buildTextFieldActions(final TextField rowField, final TextField columnField) {
        rowField.focusedProperty().addListener(new ChangeListener<Boolean>() {

            @Override
            public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
                if (!t1) {
                    if (!rowField.getText().equals("")) {
                        try {
                            int nbRow = Integer.parseInt(rowField.getText());
                            gridDisplay.setRows(nbRow);
                            gridDisplay.updateDisplay();
                        } catch (NumberFormatException nfe) {
                            System.out.println("Please enter a valid number.");
                        }
                    }
                }
            }
        });

        columnField.focusedProperty().addListener(new ChangeListener<Boolean>() {

            @Override
            public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
                if (!t1) {
                    if (!columnField.getText().equals("")) {
                        try {
                            int nbColumn = Integer.parseInt(columnField.getText());
                            gridDisplay.setColumns(nbColumn);
                            gridDisplay.updateDisplay();
                        } catch (NumberFormatException nfe) {
                            System.out.println("Please enter a valid number.");
                        }
                    }
                }
            }
        });
    }

    //Class responsible for displaying the grid containing the Rectangles
    public class GridDisplay {

        private GridPane gridPane;
        private int nbRow;
        private int nbColumn;
        private int width;
        private int height;
        private double hGap;
        private double vGap;

        public GridDisplay(int width, int height) {
            this.gridPane = new GridPane();
            this.width = width;
            this.height = height;
            build();
        }

        private void build() {
            this.hGap = 0.1 * width;
            this.vGap = 0.1 * height;
            gridPane.setVgap(vGap);
            gridPane.setHgap(hGap);
            gridPane.setPrefSize(width, height);
            initializeDisplay(width, height);
        }

        //Builds the first display (correctly) : adds a Rectangle for the number
        //of rows and columns
        private void initializeDisplay(int width, int height) {
            nbRow = height / 100;
            nbColumn = width / 100;

            for (int i = 0; i < nbColumn; i++) {
                for (int j = 0; j < nbRow; j++) {
                    Rectangle rectangle = new Rectangle(100, 100);
                    rectangle.setStroke(Paint.valueOf("orange"));
                    rectangle.setFill(Paint.valueOf("steelblue"));
                    gridPane.add(rectangle, i, j);

                }
            }
        }

        //Function detailed in post
        //Called in updateDisplay()
        public void refreshConstraints() {
            gridPane.getRowConstraints().clear();
            gridPane.getColumnConstraints().clear();
            for (int i = 0; i < nbRow; i++) {
                RowConstraints rConstraint = new RowConstraints();
                rConstraint.setPercentHeight(100 / nbRow - ((nbRow - 1) * 10 / nbRow));
                gridPane.getRowConstraints().add(rConstraint);
            }

            for (int i = 0; i < nbColumn; i++) {
                ColumnConstraints cConstraint = new ColumnConstraints();
                cConstraint.setPercentWidth(100 / nbColumn - ((nbColumn - 1) * 10 / nbColumn));
                gridPane.getColumnConstraints().add(cConstraint);
            }

        }

        public void setColumns(int newColumns) {
            nbColumn = newColumns;
        }

        public void setRows(int newRows) {
            nbRow = newRows;
        }

        public GridPane getDisplay() {
            return gridPane;
        }

        //Function called when refreshing the display
        public void updateDisplay() {
            Platform.runLater(new Runnable() {

                @Override
                public void run() {
                    //The gridpane is cleared of the previous children
                    gridPane.getChildren().clear();

                    //A new rectangle is added for row*column
                    for (int i = 0; i < nbColumn; i++) {
                        for (int j = 0; j < nbRow; j++) {
                            Rectangle rectangle = new Rectangle(100, 100);
                            rectangle.setStroke(Paint.valueOf("orange"));
                            rectangle.setFill(Paint.valueOf("steelblue"));
                            gridPane.add(rectangle, i, j);
                        }
                    }
                    //Call to this function to update the grid's constraints
                    refreshConstraints();
                }
            });

        }

    }

}

Answer

jewelsea picture jewelsea · Apr 27, 2014

Seems like a TilePane is a better fit for this use case than a GridPane.

2by4 3by2

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.TilePane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

// java 8 code
public class DynamicTiles extends Application {

    //Class containing grid (see below)
    private GridDisplay gridDisplay;

    //Class responsible for displaying the grid containing the Rectangles
    public class GridDisplay {

        private static final double ELEMENT_SIZE = 100;
        private static final double GAP = ELEMENT_SIZE / 10;

        private TilePane tilePane = new TilePane();
        private Group display = new Group(tilePane);
        private int nRows;
        private int nCols;

        public GridDisplay(int nRows, int nCols) {
            tilePane.setStyle("-fx-background-color: rgba(255, 215, 0, 0.1);");
            tilePane.setHgap(GAP);
            tilePane.setVgap(GAP);
            setColumns(nCols);
            setRows(nRows);
        }

        public void setColumns(int newColumns) {
            nCols = newColumns;
            tilePane.setPrefColumns(nCols);
            createElements();
        }

        public void setRows(int newRows) {
            nRows = newRows;
            tilePane.setPrefRows(nRows);
            createElements();
        }

        public Group getDisplay() {
            return display;
        }

        private void createElements() {
            tilePane.getChildren().clear();
            for (int i = 0; i < nCols; i++) {
                for (int j = 0; j < nRows; j++) {
                    tilePane.getChildren().add(createElement());
                }
            }
        }

        private Rectangle createElement() {
            Rectangle rectangle = new Rectangle(ELEMENT_SIZE, ELEMENT_SIZE);
            rectangle.setStroke(Color.ORANGE);
            rectangle.setFill(Color.STEELBLUE);

            return rectangle;
        }

    }

    @Override
    public void start(Stage primaryStage) {

        //Represents the grid with Rectangles
        gridDisplay = new GridDisplay(2, 4);

        //Fields to specify number of rows/columns
        TextField rowField = new TextField("2");
        TextField columnField = new TextField("4");

        //Function to set an action when text field loses focus
        buildTextFieldActions(rowField, columnField);

        HBox fields = new HBox(10);
        fields.getChildren().add(rowField);
        fields.getChildren().add(new Label("x"));
        fields.getChildren().add(columnField);

        BorderPane mainPanel = new BorderPane();
        mainPanel.setCenter(gridDisplay.getDisplay());
        mainPanel.setTop(fields);

        Scene scene = new Scene(mainPanel, 1000, 800);
        primaryStage.setTitle("Test grid display");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

    private void buildTextFieldActions(final TextField rowField, final TextField columnField) {
        rowField.focusedProperty().addListener((ov, t, t1) -> {
            if (!t1) {
                if (!rowField.getText().equals("")) {
                    try {
                        int nbRow = Integer.parseInt(rowField.getText());
                        gridDisplay.setRows(nbRow);
                    } catch (NumberFormatException nfe) {
                        System.out.println("Please enter a valid number.");
                    }
                }
            }
        });

        columnField.focusedProperty().addListener((ov, t, t1) -> {
            if (!t1) {
                if (!columnField.getText().equals("")) {
                    try {
                        int nbColumn = Integer.parseInt(columnField.getText());
                        gridDisplay.setColumns(nbColumn);
                    } catch (NumberFormatException nfe) {
                        System.out.println("Please enter a valid number.");
                    }
                }
            }
        });
    }
}