Reputation: 147
I have a Main
, Cell
and Displayer
classes. Main creates a Cell[][]
grid, passes it to Displayer
that constructs Pane
s 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
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 ChangeListener
s 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