Chessboard with automatic resizing


Question

So, I am trying to display a chessboard in javaFX. I will have to perform different operations and draw on some of the tiles so I chose to use a Canvas for each tile and a GridPane to arrange them for me in a grid fashion.
Unfortunately I am having some problems with the resizing of the grid tiles; I want my whole chessboard to automatically adapt its size to the Scene. Therefore, I have added a ChangeListener to both the height and width properties of the GridPane which takes care of resizing the tiles. This only works when the window gets bigger, when the window is reduced to a smaller size everything still gets bigger!
Here's the shortest SSCCE I came up with which reproduces my problem:

package chessboardtest;

import javafx.application.Application;
import javafx.beans.value.*;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.stage.Stage;

public class ChessboardTest extends Application {

    final int size = 10;

    @Override
    public void start(Stage primaryStage) {
        VBox root = new VBox();

        final GridPane chessboard = new GridPane();
        fillChessboard(chessboard, size);

        ChangeListener<Number> resizeListener = new ChangeListener<Number>() {

            @Override
            public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
                double newWidth = chessboard.getWidth() / size;
                double newHeight = chessboard.getHeight() / size;

                for(Node n: chessboard.getChildren()) {
                    Canvas canvas = (Canvas)n;
                    canvas.setWidth(newWidth);
                    canvas.setHeight(newHeight);
                }
            }
        };

        chessboard.widthProperty().addListener(resizeListener);
        chessboard.heightProperty().addListener(resizeListener);

        root.getChildren().add(chessboard);
        root.setPadding(new Insets(10));
        VBox.setVgrow(chessboard, Priority.ALWAYS);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("chessboard");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    void fillChessboard(GridPane pane, int size) {
        class RedrawListener implements ChangeListener<Number> {
            Color color;
            Canvas canvas;
            public RedrawListener(Canvas c, int i) {
                if(i % 2 == 0) {
                    color = Color.BLACK;
                }
                else {
                    color = Color.WHITE;
                }
                canvas = c;
            }

            @Override
            public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
                canvas.getGraphicsContext2D().setFill(color);
                canvas.getGraphicsContext2D().fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
            }
        }

        for(int row = 0; row < size; row++) {
            for(int col = 0, i = row; col < size; col++, i++) {
                Canvas c = new Canvas();
                RedrawListener rl = new RedrawListener(c, i);
                c.widthProperty().addListener(rl);
                c.heightProperty().addListener(rl);

                pane.add(c, row, col);
            }
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
1
2
6/6/2014 12:38:35 PM

Accepted Answer

If you don't need a canvas (and you probably don't), just use StackPanes for the squares and make them fill the width and the height. You can always add a canvas (or anything else) to the StackPanes to display their content.

import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Chessboard extends Application {

    @Override
    public void start(Stage primaryStage) {
        GridPane root = new GridPane();
        final int size = 8 ;
        for (int row = 0; row < size; row++) {
            for (int col = 0; col < size; col ++) {
                StackPane square = new StackPane();
                String color ;
                if ((row + col) % 2 == 0) {
                    color = "white";
                } else {
                    color = "black";
                }
                square.setStyle("-fx-background-color: "+color+";");
                root.add(square, col, row);
            }
        }
        for (int i = 0; i < size; i++) {
            root.getColumnConstraints().add(new ColumnConstraints(5, Control.USE_COMPUTED_SIZE, Double.POSITIVE_INFINITY, Priority.ALWAYS, HPos.CENTER, true));
            root.getRowConstraints().add(new RowConstraints(5, Control.USE_COMPUTED_SIZE, Double.POSITIVE_INFINITY, Priority.ALWAYS, VPos.CENTER, true));
        }
        primaryStage.setScene(new Scene(root, 400, 400));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
5
6/7/2014 3:07:00 AM

This is a nice solution, but resizing is so much easier with data binding in Java FX. You can hide all listener business this way. Here is a solution much like James D's, but using Rectangles insread of Canvases for the squares:

public class ResizeChessboard extends Application {
    GridPane root = new GridPane();
    final int size = 8;

    public void start(Stage primaryStage) {
        for (int row = 0; row < size; row++) {
            for (int col = 0; col < size; col++) {
                Rectangle square = new Rectangle();
                Color color;
                if ((row + col) % 2 == 0) color = Color.WHITE;
                else color = Color.BLACK;
                square.setFill(color);
                root.add(square, col, row);
                square.widthProperty().bind(root.widthProperty().divide(size));
                square.heightProperty().bind(root.heightProperty().divide(size));
            }
        }
        primaryStage.setScene(new Scene(root, 400, 400));
        primaryStage.show();
    }

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

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon