woodStone
woodStone

Reputation: 147

JavaFX: game scene doesn't get updated after button click

I have a Main, Cell and Displayer classes. Main creates a Cell[][] grid, passes it to Displayer that constructs Panes and returns a game Scene.

I have a deleteCells button that calls deleteCells method inside Main class. However, after I press it, the Scene doesn't get updated, even though System.out.println output proves the method was executed:

Cell:

public class Cell {
    private boolean status;

    public Cell()  {
        System.out.println("DEAD CELL CREATED");

        status = false;
    }

    public boolean getStatus()  {
            return status;
    }

    public void setStatus(boolean status)  {
        this.status = status;
    }

}

Main:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.util.Random;

public class Main extends Application {
    private static int  gameWidth= 800;
    private static int gameHeight=600;

    private static int gridSize = 20;
    private static Cell[][] grid = new Cell[gridSize][gridSize];

    private static Scene scene;
    private static Displayer gameDisplayer;    

    @Override
    public void start(Stage primaryStage) throws Exception{
        primaryStage.setTitle("Advanced Game of Life");

        createGrid();

        gameDisplayer = new Displayer(gameWidth, gameHeight, grid);

        scene = gameDisplayer.getGameScene();
        primaryStage.setScene(scene);
        primaryStage.show();
    }


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

    private static void createGrid()  {
        for (int i=0; i<gridSize; i++)  {
            for (int j=0; j<gridSize; j++)
                grid[i][j] = new Cell();
        }
    }

    public static void deleteCells()  {
        for (int i=0; i<gridSize; i++)  {
            for (int j=0; j<gridSize; j++) {
                grid[i][j].setStatus(false);
            }
        }

        //scene = gameDisplayer.getGameScene();  //this doesn't work
    }

    public static void createCell()  {
        Random rand = new Random();
    }
}

Displayer:

import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;

public class Displayer  implements EventHandler<ActionEvent> {
    private static Color ALIVE_COLOR=Color.GREEN;
    private static Color DEAD_COLOR=Color.SILVER;;

    private static BorderPane gamePane;
    private static Pane cellsPane;

    private HBox buttonsPane;
    private Pane statsPane;
    private Pane setupsPane;
    private HBox bottomPane;

    Button createCellButton;
    Button deleteCellsButton;

    Label cellsCountLabel;
    Label setupsLabel;

    private int  gameWidth;
    private int gameHeight;

    static int gridSize = 20;

    static int cellId = 1;

    private Cell[][] gameGrid;
    private Scene gameScene;

    public Displayer(int width, int height, Cell[][] grid)  {
        gameWidth = width;
        gameHeight = height;

        gameGrid=grid;

        createPanes();
        createButtons();
        createLabels();

        setPaneStyles();
        setButtonsStyles();
        setLabelsStyles();


        gameScene = new Scene(gamePane, gameWidth, gameHeight);
    }

    public Scene getGameScene()  {
            return gameScene;
    }

    private void createPanes()  {
        gamePane = new BorderPane();

        buttonsPane = new HBox(5);
        statsPane = new Pane();
        cellsPane = makeGridPane(gameGrid);
        setupsPane = new Pane();
        bottomPane = new HBox(5);
    }

    private void createButtons()  {
        createCellButton = new Button();
        deleteCellsButton = new Button();
    }

    private void createLabels()  {
        cellsCountLabel = new Label("Cells Count: " + (cellId + 1));
        setupsLabel = new Label("Setups Label");
    }



    private void setPaneStyles()  {...}

    private void setButtonsStyles()  {...}

    private void setLabelsStyles()  {...}

    public void handle(ActionEvent event)  {
        if (event.getSource()==createCellButton)  {
           //Main.createCell();
        }
        else if (event.getSource() == deleteCellsButton) {
            Main.deleteCells();
            cellsCountLabel.setText("Cells Count: " + (cellId + 1));
            System.out.println("Cells deleted");
        }
        else  {
            System.out.println("Unknown button");
        }
    }

    public Pane makeGridPane(Cell[][] grid) {
        Pane gridPane = new Pane();

        for(int i=0; i<gridSize; i++){
            for(int j=0; j<gridSize; j++){
                Rectangle rect = new Rectangle();

                System.out.println("grid[" + i + "][" + j +"]");
                if (grid[i][j].getStatus())  {
                        rect.setFill(ALIVE_COLOR);
                }
                else {
                    rect.setFill(DEAD_COLOR);
                }

                rect.setStroke(Color.BLACK);

                rect.setX(i * gridSize);
                rect.setY(j * gridSize);
                rect.setWidth(gridSize);
                rect.setHeight(gridSize);

                rect.setOnMouseClicked(new EventHandler<MouseEvent>(){
                    @Override
                    public void handle(MouseEvent me){
                            rect.setFill(Color.RED);
                    }
                });

                gridPane.getChildren().add(rect);


            }
        }

        return gridPane;
    }
}

Is there way to make the Scene update itself, even though it is constructed inside Displayer and the buttons call methods inside Main class? I tried adding scene = gameDisplayer.getGameScene(); inside deleteCells() method, but it didn't change the situation. How should I handle user's input, in accordance with MVC, so that the Scene responded to changes, given all GUI elements are located in a separate class Displayer?

EDIT:

Added a editGrid() method to Main:

public static void editGrid(int x, int y, boolean status)  {
        grid[x][y].setStatus(status);
}

Updated setOnMouseClicked inside Displayer:

rect.setOnMouseClicked(new EventHandler<MouseEvent>(){
   @Override
   public void handle(MouseEvent me){
       Main.editGrid ((int) rect.getX()/gridSize, (int) rect.getY()/gridSize, true);
   }
});

Upvotes: 0

Views: 330

Answers (1)

James_D
James_D

Reputation: 209330

In traditional MVC, the model is "observable", in the sense that observers can register to receive notifications of changes to the data. The view (or controller, depending on the variant of the MVC pattern you are using) observes the data in the model and updates the UI components accordingly when the data changes.

JavaFX makes it pretty easy to do this by defining a properties and binding API. These properties classes are directly observable, firing events to ChangeListeners when they change, and the binding API allows you to express dependencies between variables. The UI components themselves are written using these properties.

So you can implement your model as

public class Cell {
    private final BooleanProperty status;

    public Cell()  {
        System.out.println("DEAD CELL CREATED");

        status = new SimpleBooleanProperty(false);
    }

    public BooleanProperty statusProperty() {
        return status ;
    }

    public final boolean getStatus()  {
            return statusProperty().get();
    }

    public final void setStatus(boolean status)  {
        statusProperty().set(status);
    }

}

Then all you need to do is update the color of your rectangles when the corresponding cell's status changes:

public Pane makeGridPane(Cell[][] grid) {
    Pane gridPane = new Pane();

    for(int i=0; i<gridSize; i++){
        for(int j=0; j<gridSize; j++){
            Rectangle rect = new Rectangle();

            System.out.println("grid[" + i + "][" + j +"]");

            if (grid[i][j].getStatus()) {
                rect.setFill(ALIVE_COLOR);
            } else {
                rect.setFill(DEAD_COLOR);
            }

            grid[i][j].statusProperty().addListener((obs, oldStatus, newStatus) -> {
                if (newStatus) {
                    rect.setFill(ALIVE_COLOR);
                } else {
                    rect.setFill(DEAD_COLOR);
                }
            });

            rect.setStroke(Color.BLACK);

            rect.setX(i * gridSize);
            rect.setY(j * gridSize);
            rect.setWidth(gridSize);
            rect.setHeight(gridSize);


            rect.setOnMouseClicked(new EventHandler<MouseEvent>(){
                @Override
                public void handle(MouseEvent me){
                        rect.setFill(Color.RED);
                }
            });

            gridPane.getChildren().add(rect);


        }
    }

    return gridPane;
}

Upvotes: 1

Related Questions