javafxcontrolsinner-classes

Changing colored cell in GridPane in JavaFX


I want to write code, that can do following:

  1. Create 9*9 GridPane board that filled with 81 StackPanes.

  2. When I click on cell (StackPane) I want to change color of background StackPane to blue if it is white. If I have alreday had blue cell on board, than old cell becomes white and new one becomes blue. So, In anytime I can only have 0 or 1 colored cell.

  3. I tried to use Array[9][9] of StackPanes but I failed because even if I declare this array before "public void start(Stage stage)" I fail to use each StackPane in lambda expressions or innerclasses.

  4. I tried to use variable that tells if I have 1 colored cell or not and coordinates of colored cell in 2 variables. When I click on cells I see that coordinates works, but I can't change color of Panes.


package com.example.testingcolorswitching;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {


    boolean isAnyPaneSelected = true; // true if 1 of cells is selected
    int xColored = 1; // holds X coordinate of Selected cell;
    int yColored = 1; // holds Y coordinate of Selected cell;
    int columnSize = 50;
    int rowSize = 50;

    @Override
    public void start(Stage stage) throws IOException {

        GridPane grid = new GridPane();

        for (int x = 0; x < 9; x++)
            for (int y = 0; y < 9; y++){
                StackPane cell = new StackPane();
                cell.setMinSize(columnSize, rowSize);
                
                // I would like that this was checked every time I click on GridPane 
                if (isAnyPaneSelected && x == xColored && y == yColored)
                    cell.setStyle("-fx-background-color:BLUE");

                final int thisX = x;
                final int thisY = y;

                cell.setOnMouseClicked((event) -> {
                    if(isAnyPaneSelected) {
                        if (thisX == xColored & thisY == yColored)
                            isAnyPaneSelected = false;
                        else {
                            xColored = thisX;
                            yColored = thisY;
                        }
                    } else {
                        isAnyPaneSelected = true;
                        xColored = thisX;
                        yColored = thisY;
                    }

                    System.out.println(isAnyPaneSelected + " " + xColored + " " + yColored);
                });

                grid.add(cell, x, y);
            }


        // code below is not important. This code adds lines on screen.
        for (int i = 0; i <= 9; i++){
            Line line = new Line(0.0, rowSize * i, rowSize * 9, rowSize * i);
            line.setManaged(false);
            if (i % 3 == 0)
                line.setStrokeWidth(3);

            else
                line.setStrokeWidth(1);
            grid.getChildren().add(line);

            line = new Line(columnSize * i, 0.0, columnSize * i, columnSize * 9);
            line.setManaged(false);
            if (i % 3 == 0)
                line.setStrokeWidth(3);

            else
                line.setStrokeWidth(1);
            grid.getChildren().add(line);
        }

        Scene scene = new Scene(grid, 600, 600);
        stage.setScene(scene);
        stage.show();

    }

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


Solution

  • One way to fix this is by including the cell to an ObjectProperty and then you can add listener to add/remove the style of the cell.

    Something like:

    // Create an object property to hold the selected cell.
    private ObjectProperty<StackPane> selectedCell = new SimpleObjectProperty<>();
    
    // Add listener to the object property to toggle the style
    selectedCell.addListener((obs, old, val) -> {
        if (old != null) {
            old.setStyle(null);
        }
        if (val != null) {
            val.setStyle("-fx-background-color:BLUE");
        }
    });
    
    // On click, set the cell to the objectproperty.
    cell.setOnMouseClicked((event) -> {
        // If already selected, then remove it.
        if (cell.equals(selectedCell.get())) {
            selectedCell.set(null);
        } else {
            selectedCell.set(cell);
        }
    });
    

    Having said that, I also suggest to alter the logic of showing the lines. It really doesnt seem right adding unmanaged lines and positioning them in GridPane. I included a logic to add borders to each cell to get the final result (below image).

    enter image description here

    Below is a complete demo of both the changes:

    enter image description here

    import javafx.application.Application;
    import javafx.beans.property.ObjectProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.scene.Group;
    import javafx.scene.Scene;
    import javafx.scene.layout.GridPane;
    import javafx.scene.layout.StackPane;
    import javafx.stage.Stage;
    
    import java.io.IOException;
    
    public class CellColouringDemo extends Application {
    
        public static final String CSS = "data:text/css," + // language=CSS
                """
                            .outer-box{
                                -fx-border-style:solid;
                                -fx-border-color:black;
                                -fx-border-width:3px 0px 0px 3px;
                            }
                            
                            .cell{
                                -fx-border-style:solid;
                                -fx-border-color:black;
                                -fx-border-width:0px 1px 1px 0px;
                            }
                            
                            .cell-right{
                                -fx-border-width:0px 3px 1px 0px;
                            }
                            .cell-bottom{
                                -fx-border-width:0px 1px 3px 0px;
                            }
                            .cell-corner{
                                -fx-border-width:0px 3px 3px 0px;
                            }
                            
                            .selected-cell{
                                -fx-background-color: blue;
                            }
                        """;
        private int columnSize = 50;
        private int rowSize = 50;
    
        private ObjectProperty<StackPane> selectedCell = new SimpleObjectProperty<>();
    
        record Pos(int col, int row){}
    
        @Override
        public void start(Stage stage) throws IOException {
            selectedCell.addListener((obs, old, val) -> {
                if (old != null) {
                    old.getStyleClass().remove("selected-cell");
                    Pos pos = (Pos) old.getUserData();
                    // Do further logic with previous cell position
                }
                if (val != null) {
                    val.getStyleClass().add("selected-cell");
                    Pos pos = (Pos) val.getUserData();
                    // Do further logic with current cell position
                }
            });
            GridPane grid = new GridPane();
            grid.getStyleClass().add("outer-box");
            for (int row = 0; row < 9; row++) {
                for (int col = 0; col < 9; col++) {
                    StackPane cell = new StackPane();
                    cell.setUserData(new Pos(col, row));
                    cell.getStyleClass().add("cell");
                    setCellStyle(cell, row, col);
                    cell.setMinSize(columnSize, rowSize);
                    cell.setOnMouseClicked((event) -> {
                        if (cell.equals(selectedCell.get())) {
                            selectedCell.set(null);
                        } else {
                            selectedCell.set(cell);
                        }
                    });
                    grid.add(cell, col, row);
                }
            }
            StackPane root = new StackPane(new Group(grid));
            Scene scene = new Scene(root, 600, 600);
            scene.getStylesheets().add(CSS);
            stage.setScene(scene);
            stage.show();
        }
    
        private void setCellStyle(StackPane cell, int row, int col) {
            boolean isBottom = (row + 1) % 3 == 0;
            boolean isRight = (col + 1) % 3 == 0;
            if (isBottom && isRight) {
                cell.getStyleClass().add("cell-corner");
            } else if (isBottom) {
                cell.getStyleClass().add("cell-bottom");
            } else if (isRight) {
                cell.getStyleClass().add("cell-right");
            }
        }
    
        public static void main(String[] args) {
            launch();
        }
    }