javalayoutjavafxgridpane

Square GridPane of square cells


My goal is to create a GridView that is resizable, always square and contains an equal number of rows and columns, making their cells also square, similar to a Reversi or Chess board.

Here's a small illustration, the grid is centered horizontally on the content pane.

enter image description here

I've tried a multitude of different binding variants and layouts, but I can't quite get it right. Here's my controller (so far):

public class Controller {

    public HBox contentPane;

    public void initialize() {
        final int sideLength = 10;

        final GridPane gridPane = new GridPane();

        gridPane.setStyle("-fx-border-color: red; -fx-border-insets: 2");

        HBox.setHgrow(gridPane, Priority.ALWAYS);

        for (int i = 0; i < sideLength; i++) {
            final ColumnConstraints columnConstraints = new ColumnConstraints(Control.USE_PREF_SIZE, 10, Double.MAX_VALUE);
            columnConstraints.setHgrow(Priority.ALWAYS);
            gridPane.getColumnConstraints()
                    .add(columnConstraints);

            final RowConstraints rowConstraints = new RowConstraints(Control.USE_PREF_SIZE, 10, Double.MAX_VALUE);
            rowConstraints.setVgrow(Priority.ALWAYS);

            gridPane.getRowConstraints()
                    .add(rowConstraints);
        }

        contentPane.getChildren().add(gridPane);

        for (int i = 0; i < sideLength; i++) {
            for (int j = 0; j < sideLength; j++) {
                final GameCell child = new GameCell();
                GridPane.setRowIndex(child, i);
                GridPane.setColumnIndex(child, j);
                gridPane.getChildren().add(child);
            }
        }
    }
}

And the cells, which are supposed to contain Shapes lates, but I've but Circles for now just to test it:

public class GameCell extends VBox {

    private final Circle circle;

    public GameCell() {
        circle = new Circle();

        setMinSize(0, 0);
        setPrefSize(Control.USE_COMPUTED_SIZE, Control.USE_COMPUTED_SIZE);
        getChildren().add(circle);

        final ChangeListener<Number> listener = (observable, oldValue, newValue) ->
                circle.setRadius((int) (Math.min(this.getWidth(), this.getHeight()) / 2));

        widthProperty().addListener(listener);
        heightProperty().addListener(listener);
    }
}

This is how it looks currently:

enter image description here


Solution

  • Solved it with a lot of tinkering, here's my solution for future reference:

    GameCell:

    public class GameCell extends Pane {
        public GameCell() {
            final Circle circle = new Circle(10);
            circle.radiusProperty().bind(Bindings.divide(widthProperty(), 4));
    
            circle.centerXProperty().bind(widthProperty().divide(2));
            circle.centerYProperty().bind(widthProperty().divide(2));
    
            getChildren().add(circle);
        }
    }
    

    GamePane:

    public class GamePane extends HBox {
        public GamePane() {
            final VBox vBox = new VBox();
    
            vBox.alignmentProperty().set(Pos.CENTER);
            alignmentProperty().set(Pos.CENTER);
    
            final GridPane gridPane = new GridPane();
    
            final NumberBinding binding = Bindings.min(widthProperty(), heightProperty());
    
            gridPane.setMinSize(200, 200);
            vBox.prefWidthProperty().bind(binding);
            vBox.prefHeightProperty().bind(binding);
            vBox.setMaxSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
    
            vBox.setFillWidth(true);
            VBox.setVgrow(gridPane, Priority.ALWAYS);
    
            final int sideLength = 8;
            for (int i = 0; i < sideLength; i++) {
                final ColumnConstraints columnConstraints = new ColumnConstraints(Control.USE_PREF_SIZE, Control.USE_COMPUTED_SIZE, Double.MAX_VALUE);
                columnConstraints.setHgrow(Priority.SOMETIMES);
                gridPane.getColumnConstraints().add(columnConstraints);
    
                final RowConstraints rowConstraints = new RowConstraints(Control.USE_PREF_SIZE, Control.USE_COMPUTED_SIZE, Double.MAX_VALUE);
                rowConstraints.setVgrow(Priority.SOMETIMES);
                gridPane.getRowConstraints().add(rowConstraints);
            }
    
            vBox.getChildren().add(gridPane);
    
            getChildren().add(vBox);
    
            HBox.setHgrow(this, Priority.ALWAYS);
    
            for (int i = 0; i < sideLength; i++) {
                for (int j = 0; j < sideLength; j++) {
                    final Pane child = new GameCell();
    
                    GridPane.setRowIndex(child, i);
                    GridPane.setColumnIndex(child, j);
                    gridPane.getChildren().add(child);
                }
            }
        }
    }