How can I draw over a GridPane of Rectangles with an Image? (JavaFX)

Viewed 398

So I am trying to display a chessboard in Java. So far, I can correctly draw and color a bunch of rectangles and have the window and rectangles resize properly. However, now i want to add an image of a chess piece on top of those rectangles, and I am not sure how to proceed.

I create an imageview of a png for a chess piece, but when I try adding it to the gridpane like i did the rectangles, it gives me a duplicate child error.

Here is the code that works so far

package test3;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.TilePane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.scene.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.image.*;
/**
 *

 */
public class Test3 extends Application {
    GridPane root = new GridPane();
    final int size = 8;
    ImageView pawn = new ImageView("file:pawn.png");
    @Override
    public void start(Stage primaryStage) throws Exception {
        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.CHOCOLATE;
                }
                else {
                    color = Color.ANTIQUEWHITE;
                }
                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);
    }

}

What would be the best way to create an imageview of a chess piece and draw it on top of the rectangles, like how chess pieces are on a board? I already have a file, say pawn.png, i can make an imageview of it, but any time i try to add the image view to the gridpane where i drew rectangles, it gives errors. I am not sure what to do. I tried having a second gridpane where i only add the images, but that also gives duplicate child errors.

1 Answers

(Some of my points might have already addressed in comments)

The first thing you need to fix is to create separate ImageView nodes for each square, as you cannot duplicate the nodes in scenegraph.

Irrespective of Rectangle or StackPane as your square, you can fix the problem.

Using Rectangle:

If you prefer to use Rectangle, you can just include the ImageView node in the same grid location of Rectangle and adjust the width/height of the ImageView node w.r.t the Rectangle.

ImageView pawn = new ImageView(getClass().getResource("pawn.png").toExternalForm());
root.add(pawn, col, row);
pawn.fitWidthProperty().bind(square.widthProperty().subtract(2));
pawn.fitHeightProperty().bind(square.heightProperty().subtract(2));

Using StackPane:

The approach is pretty much same as using Rectangle, but you will place the ImageView node in the StackPane instead of GridPane. The added advantage of using StackPane is that the ImageView will be automatically centered to your square. You can also include an optional code to resize the image to fit to the square.

Background dark = new Background(new BackgroundFill(Color.CHOCOLATE, CornerRadii.EMPTY, Insets.EMPTY));
Background light = new Background(new BackgroundFill(Color.ANTIQUEWHITE, CornerRadii.EMPTY, Insets.EMPTY));
for (int row = 0; row < size; row++) {
    for (int col = 0; col < size; col++) {
        ImageView pawn = new ImageView(getClass().getResource("pawn.png").toExternalForm());
        StackPane square = new StackPane(pawn);
        square.setBackground((row + col) % 2 == 0?dark:light);
        root.add(square, col, row);
        square.prefWidthProperty().bind(root.widthProperty().divide(size));
        square.prefHeightProperty().bind(root.heightProperty().divide(size));

        // Comment the below two lines if you don't want the images to resize.
        pawn.fitWidthProperty().bind(square.widthProperty().subtract(2));
        pawn.fitHeightProperty().bind(square.heightProperty().subtract(2));
    }
}

Either of the above two approaches produces the same result as below gif. The working demo is below:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        yourApproach();
        // usingPane(); 
        primaryStage.setScene(new Scene(root, 400, 400));
        primaryStage.setTitle("ChessBoard");
        primaryStage.show();
    }

    private void usingPane() {
        Background dark = new Background(new BackgroundFill(Color.CHOCOLATE, CornerRadii.EMPTY, Insets.EMPTY));
        Background light = new Background(new BackgroundFill(Color.ANTIQUEWHITE, CornerRadii.EMPTY, Insets.EMPTY));
        for (int row = 0; row < size; row++) {
            for (int col = 0; col < size; col++) {
                ImageView pawn = new ImageView(getClass().getResource("pawn.png").toExternalForm());
                StackPane square = new StackPane(pawn);
                square.setBackground((row + col) % 2 == 0 ? dark : light);
                root.add(square, col, row);
                square.prefWidthProperty().bind(root.widthProperty().divide(size));
                square.prefHeightProperty().bind(root.heightProperty().divide(size));

                // Comment the below two lines if you don't want the images to resize.
                pawn.fitWidthProperty().bind(square.widthProperty().subtract(2));
                pawn.fitHeightProperty().bind(square.heightProperty().subtract(2));
            }
        }
    }

    private void yourApproach() {
        for (int row = 0; row < size; row++) {
            for (int col = 0; col < size; col++) {
                Rectangle square = new Rectangle();
                square.setFill((row + col) % 2 == 0 ? Color.CHOCOLATE : Color.ANTIQUEWHITE);

                root.add(square, col, row);
                square.widthProperty().bind(root.widthProperty().divide(size));
                square.heightProperty().bind(root.heightProperty().divide(size));

                ImageView pawn = new ImageView(getClass().getResource("pawn.png").toExternalForm());
                root.add(pawn, col, row);
                pawn.fitWidthProperty().bind(square.widthProperty().subtract(2));
                pawn.fitHeightProperty().bind(square.heightProperty().subtract(2));
            }
        }
    }

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

enter image description here

Related