NaturalBottle
NaturalBottle

Reputation: 21

JavaFX: Update ImageView on GridPane click

I'm working on a Minesweeper game using JavaFX:

(On game start)

My GridPane is made of ImageViews, and I'm trying to find a way to figure out the column and row indexes of the image that was clicked on the GridPane. I have this code currently:

gameGrid.setOnMouseClicked(event -> //gameGrid is my GridPane
{
    Node source = (Node)event.getSource();
    int columnIndex = GridPane.getColumnIndex(source);
    System.out.println(columnIndex);
    int rowIndex = GridPane.getRowIndex(source);
    if(event.getButton()== MouseButton.PRIMARY)
    game.newRevealCell(columnIndex,rowIndex);
    else if(event.getButton()==MouseButton.SECONDARY)

    game.getGrid()[columnIndex][rowIndex].setFlagged(true);
    //game.getGrid is a 2D array containing the game cells

});

However, the methods getColumnIndex and getRowIndex are returning null. What am I doing wrong?

Upvotes: 2

Views: 1404

Answers (2)

trashgod
trashgod

Reputation: 205785

Starting from this example, the variation below fills a GridPane with N x N instances of Button, adding each to a List<Button>. The method getGridButton() shows how to obtain a button reference efficiently based on its grid coordinates, and the action listener shows that the clicked and found buttons are identical.

How does each grid button know its own location on the grid? Each one gets its own instance of an anonymous class—the button's event handler—that has access to the row and column parameters passed to createGridButton(). These parameters are effectively final; in contrast, the original example using an earlier version of Java had to make the parameters explicitly final.

While this approach obtains the row and column of any Node in the grid, a Button or ToggleButton may be more convenient in this context. In particular, you can use setGraphic​() with ContentDisplay.GRAPHIC_ONLY to get the desired appearance.

As an aside, the GridPane methods getColumnIndex and getRowIndex return null as they refer to the child's index constraints, only if set, as illustrated here.

row major order

import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

/** @see https://stackoverflow.com/a/69429741/230513 */
public class GridButtonTest extends Application {
    
    private static final int N = 5;
    private final List<Button> list = new ArrayList<>();
    
    private Button getGridButton(int r, int c) {
        int index = r * N + c;
        return list.get(index);
    }
    
    private Button createGridButton(int row, int col) {
        Button button = new Button("r" + row + ",c" + col);
        button.setOnAction((ActionEvent event) -> {
            System.out.println(event.getSource() == getGridButton(row, col));
        });
        return button;
    }
    
    @Override
    public void start(Stage stage) {
        stage.setTitle("GridButtonTest");
        GridPane root = new GridPane();
        for (int i = 0; i < N * N; i++) {
            int row = i / N;
            int col = i % N;
            Button gb = createGridButton(row, col);
            list.add(gb);
            root.add(gb, col, row);
            GridPane.setMargin(gb, new Insets(N));
        }
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

For models using a two dimensional array, also consider a List<List<Button>>, illustrated below:

import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

/** @see https://stackoverflow.com/a/69429741/230513 */
public class GridButtonTest extends Application {

    private static final int N = 5;
    private final List<List<Button>> list = new ArrayList<>();

    private Button getGridButton(int r, int c) {
        return list.get(r).get(c);
    }

    private Button createGridButton(int row, int col) {
        Button button = new Button("r" + row + ",c" + col);
        button.setOnAction((ActionEvent event) -> {
            System.out.println(event.getSource() == getGridButton(row, col));
        });
        return button;
    }

    @Override
    public void start(Stage stage) {
        stage.setTitle("GridButtonTest");
        GridPane root = new GridPane();
        for (int row = 0; row < N; row++) {
            list.add(new ArrayList<>());
            for (int col = 0; col < N; col++) {
                Button gb = createGridButton(row, col);
                list.get(row).add(gb);
                root.add(gb, col, row);
                GridPane.setMargin(gb, new Insets(N));
            }
        }
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

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

Upvotes: 3

Sai Dandem
Sai Dandem

Reputation: 9959

+1 for all the comments and the answer provided by @trashgod.

But trying to address your actual issue, I think using event.getSource is the root cause for your issue. The source returned from the event will be always the GridPane.

event.getTarget() method will give you the target node you clicked. So I think changing it to getTarget() will fix your issue.

Below is the working demo of what I mean. This works even if I dont set the index contraint explicitly.

Update: I was mistaken that explicit constraint settings is done only through GridPane.add method. But using add/addRow/addColumn methods sets the index constraints as well.

Note: This will work if the target node you clicked is a direct child of GridPane (in your case). But if your target node is not a direct child of GridPane, like if the StackPane has a Label and if you clicked on that Label, a null exception will be thrown.

import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class GridPaneIndexingDemo extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        StackPane root = new StackPane();
        Scene scene = new Scene(root, 450, 450);
        stage.setScene(scene);
        stage.setTitle("GridPane");
        stage.show();

        GridPane grid = new GridPane();
        grid.setGridLinesVisible(true);
        grid.addRow(0, getPane(1), getPane(2), getPane(3));
        grid.addRow(1, getPane(4), getPane(5), getPane(6));
        grid.addRow(2, getPane(7), getPane(8), getPane(9));
        grid.setOnMouseClicked(event -> {
            Node source = (Node) event.getTarget();
            int columnIndex = GridPane.getColumnIndex(source);
            int rowIndex = GridPane.getRowIndex(source);
            System.out.println("Row : " + rowIndex + ", Col : " + columnIndex);
        });
        root.getChildren().add(grid);
    }

    private StackPane getPane(int i) {
        String[] colors = {"grey", "yellow", "blue", "pink", "brown", "white", "silver", "orange", "lightblue", "grey"};
        StackPane pane = new StackPane();
        pane.setPrefSize(150,150);
        pane.setStyle("-fx-background-color:" + colors[i] + ";-fx-border-width:2px;");
        return pane;
    }

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

Upvotes: 3

Related Questions