Adding borders to GridPane JavaFX
Asked Answered
S

4

9

I am creating a board game in JavaFX using GridPane.

There are 7 different animations which could be placed in each grid (cell) of the grid.

Initially the grid looks like this

empty gridpane

I tested adding a simple circle to it before programming my animation insertions. And it looks like this

gridpane with subscenes

enter image description here

The nodes added are SubScenes which include TimeLine animation. Each cell size is 40x40 and the SubScene size is also 40x40.

The subscenes when added, get on top of the gridpane border lines and it doesn't look good.

What can I do so that the nodes are added below the grid lines? i.e. the gridlines are on top of the nodes.

If it is not possible with GridPane, is there anything else I can use?

class which i execute for the game

class Game {
    static GridPane grid;
    public void start(final Stage stage) throws Exception {
        int rows = 5;
        int columns = 5;

        stage.setTitle("Enjoy your game");
        grid = new GridPane();
        for(int i = 0; i < columns; i++) {
            ColumnConstraints column = new ColumnConstraints(40);
            grid.getColumnConstraints().add(column);
        }

        for(int i = 0; i < rows; i++) {
            RowConstraints row = new RowConstraints(40);
            grid.getRowConstraints().add(row);
        }

        grid.setOnMouseReleased(new EventHandler<MouseEvent> () {
            public void handle(MouseEvent me) {
                grid.add(Anims.getAnim(1), (int)((me.getSceneX() - (me.getSceneX() % 40)) / 40), (int)((me.getSceneY() - (me.getSceneY() % 40)) / 40)); //here the getAnim argument could be between 1-7
            }
        });

        grid.setStyle("-fx-background-color: white; -fx-grid-lines-visible: true");
        Scene scene = new Scene(grid, (columns * 40) + 100, (rows * 40) + 100, Color.WHITE);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(final String[] arguments) {
        Application.launch(arguments);
    }
}

class which contains animations, here I am just creating a circle

public class Anims {

    public static SubScene getAnim(final int number) throws Exception {
        Circle circle = new Circle(20, 20f, 7);
        circle.setFill(Color.RED);
        Group group = new Group();
        group.getChildren().add(circle);
        SubScene scene = new SubScene(group, 40, 40);
        scene.setFill(Color.WHITE);
        return scene;
    }
}
Saidee answered 1/10, 2015 at 16:38 Comment(6)
Your question is impossible to answer without code: how are we supposed to know what you are doing that creates that effect without seeing how you have implemented it? Create a minimal reproducible example that shows the same behavior and edit your question to include it.Rasure
@Rasure hope this helpsSaidee
It would be easier if you posted a minimal reproducible example, but there's enough there to see what's going wrong.Rasure
This question is similar (maybe a duplicate?) to How to create a background grid.Krissy
Using sub scenes is a very handed solution for this, usually sub scenes are for mixing 2D/3D scenes with different camera perspectives and lighting (which doesn't seem to be what you are doing here), you can probably get rid of the SubScenes from your implementation and just use regular container nodes such as Groups of Pane subclasses.Krissy
@jewelsea, replacing SubScenes with Panes worked fine, thanksSaidee
R
17

Don't use setGridLinesVisible(true): the documentation explicitly states this is for debug only.

Instead, place a pane in all the grid cells (even the empty ones), and style the pane so you see the borders. (This gives you the opportunity to control the borders very carefully, so you can avoid double borders, etc.) Then add the content to each pane. You can also register the mouse listeners with the pane, which means you don't have to do the ugly math to figure out which cell was clicked.

The recommended way to apply a border to any region is to use CSS and a "nested background" approach. In this approach, you draw two (or more) background fills on the region, with different insets, giving the appearance of a border. So for example:

-fx-background-fill: black, white ;
-fx-background-insets: 0, 1 ;

will first draw a black background with no insets, and then over that will draw a white background with insets of 1 pixel on all sides, giving the appearance of a black border of width 1 pixel. While this may seem counter-intuitive, the performance of this is (allegedly) better than specifying border directly. You can also specify a sequence of four values for the insets for each fill, which are interpreted as the insets on the top, right, bottom, and left, respectively. So

-fx-background-fill: black, white ;
-fx-background-insets: 0, 0 1 1 0 ;

has the effect of a black border on the right and bottom, etc.

I'm also not sure SubScene is what you really want, unless you are intending attaching different cameras to each cell. If you really need a subscene, make the fill transparent to avoid drawing over the edges of the cell. You could just add the Group directly to each cell (you could probably just add the circle, depending on exactly what you need...).

Something like:

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.RowConstraints;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class Game2 extends Application{
    @Override
    public void start(final Stage stage) throws Exception {
        int rows = 5;
        int columns = 5;

        stage.setTitle("Enjoy your game");

        GridPane grid = new GridPane();
        grid.getStyleClass().add("game-grid");

        for(int i = 0; i < columns; i++) {
            ColumnConstraints column = new ColumnConstraints(40);
            grid.getColumnConstraints().add(column);
        }

        for(int i = 0; i < rows; i++) {
            RowConstraints row = new RowConstraints(40);
            grid.getRowConstraints().add(row);
        }

        for (int i = 0; i < columns; i++) {
            for (int j = 0; j < rows; j++) {
                Pane pane = new Pane();
                pane.setOnMouseReleased(e -> {
                    pane.getChildren().add(Anims.getAtoms(1));
                });
                pane.getStyleClass().add("game-grid-cell");
                if (i == 0) {
                    pane.getStyleClass().add("first-column");
                }
                if (j == 0) {
                    pane.getStyleClass().add("first-row");
                }
                grid.add(pane, i, j);
            }
        }


        Scene scene = new Scene(grid, (columns * 40) + 100, (rows * 40) + 100, Color.WHITE);
        scene.getStylesheets().add("game.css");
        stage.setScene(scene);
        stage.show();
    }

    public static class Anims {

        public static Node getAtoms(final int number) {
            Circle circle = new Circle(20, 20f, 7);
            circle.setFill(Color.RED);
            Group group = new Group();
            group.getChildren().add(circle);
//            SubScene scene = new SubScene(group, 40, 40);
//            scene.setFill(Color.TRANSPARENT);
            return group;
        }
    }

    public static void main(final String[] arguments) {
        Application.launch(arguments);
    }
}

and the css:

.game-grid {
    -fx-background-color: white ;
    -fx-padding: 10 ;
}
.game-grid-cell {
    -fx-background-color: black, white ;
    -fx-background-insets: 0, 0 1 1 0 ;
}
.game-grid-cell.first-row {
    -fx-background-insets: 0, 1 1 1 0 ;
}
.game-grid-cell.first-column {
    -fx-background-insets: 0, 0 1 1 1 ;
}
.game-grid-cell.first-row.first-column {
    -fx-background-insets: 0, 1 ;
}
Rasure answered 1/10, 2015 at 17:25 Comment(6)
I am trying to make this compatible with my code, as there are a lot of things going on when a cell is clicked apart from getting an animation. I don't know much about css, so could you please explain in brief what those numbers are in the .css file?Saidee
@vicky the numbers in the provided CSS are background insets for layered backgrounds, read up on them (and other CSS related topics) in the CSS reference guide.Krissy
it works great, replaced SubScenes with Panes as you and jewelsea suggested, thanksSaidee
Added an explanation for the "nested background" technique.Rasure
Nice, but wouldn't it be easier to add h and v gaps of 1 pixel and let the grid pane's background color "shine" through?Bathe
wouldn't performance be better if gridPane draw lines instead of creating n*n panes and set border to each one?Delitescent
B
7

Simply add an H and V gap of one pixel width and let the grid pane's background color "shine" through:

.my-grid-pane {
    -fx-background-color: lightgray;
    -fx-vgap: 1;
    -fx-hgap: 1;
    -fx-padding: 1;
}

If the grid pane's background color spreads from outside more than one pixel (will happen if its parent is larger than itself), just wrap the grid in a Group!

Bathe answered 29/6, 2017 at 1:28 Comment(0)
F
0

I apologize for the response instead of the comment, not enough reputation.

Strangely, but @James_D 's response didn't help me; when the window was resized, the cell borders randomly changed their width, overlapping each other.

This answer helped solve the problem, so by slightly changing the code given by @James_D (only the .css file), we get:

.classes-grid {
    -fx-background-color: white ;
    -fx-padding: 10 ;
}
.classes-grid-cell {
    -fx-border-color: dimgray;
    -fx-border-width: 0 1 1 0;
    -fx-background-color: transparent;
}
.classes-grid-cell.first-row {
    -fx-border-width: 1 1 1 0 ;
}
.classes-grid-cell.first-column {
    -fx-border-width: 0 1 1 1 ;
}
.classes-grid-cell.first-row.first-column {
    -fx-border-width: 1 ;
}
Foreyard answered 11/6, 2020 at 0:40 Comment(0)
P
0

Same idea with Mordechai's answer. But if you want to set these things by JavaFX code, not CSS stylesheet. Then you can do sth like this:
Set up the Hgap and Vgap: gridpane.setHgap(1) and gridpane.setVgap(1)
Set up the background color: gridpane.setBackground(new Background(new BackgroundFill(Color.rgb(0,0,0), new CornerRadii(2.5), new Insets(-1.0))))
(CornerRadii and Insets value depends on your choice, background color determined by rgb value)

Plowboy answered 26/4, 2022 at 20:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.