-1

I am stuck at this for quite some time, we are working with a FXML file. I have seen and implemented this code already from here: JavaFX: Get Node by row and column

public Node getNodeByRowColumnIndex(final int row,final int column,GridPane gridPane) {
        Node result = null;
        ObservableList<Node> childrens = gridPane.getChildren();
        for(Node node : childrens) {
            if(gridPane.getRowIndex(node) == row && gridPane.getColumnIndex(node) == column) {
                result = node;
                break;
            }
        }
        return result;
    }

This works for all the Nodes we have added in the FXML files at the start, but for the nodes we manually add later during the run-time of our program (a game) it does not work. It then gets a null pointer exception. If we print out the nodes we get this for the node added at run-time: Group@181fe7 thus a memory address. For the nodes added pre-runtime in the fxml file we do get a nice string with information about the node, so somehow adding a node to the grid differs from the way it is done in fxml. We manually add nodes by the following code:

private void addSheep(Point point) {
    Circle sheep = new Circle(25);
    sheep.setFill(Color.BLUE);
    sheep.setOnMouseClicked(this::sheepSelected);
    pane.add(sheep, point.x, point.y);
    GridPane.setHalignment(animal, HPos.CENTER);
}

Why can it be so hard? I just want to find what is on a specific cell in the gridpane and remove one of the objects located there. I am spending huge amounts of time for something seeming so trivial.

Community
  • 1
  • 1
Joop
  • 3,706
  • 34
  • 55
  • 1
    It's not clear why you would get a null pointer exception. What is `null` that is causing that? And can't you just arrange for your code to "know" what is in each cell in the grid? For example, just maintain a `Map`. – James_D Mar 17 '15 at 17:59
  • We don't keep track of where the node objects are located. We thought that the PaneGrid would know that, which now seems like a stupid idea. The null pointer exception comes from here: for(Node node : paneChildren){ System.out.println(node); int nodeRow = pane.getRowIndex(node); The system.out.println(node), prints out Group@181fe7 and getRowIndex(node) gets a null pointer exception. So basically the question remains now is what is this Group and why do you get this only when adding nodes during runtime but not if you add it directly to the fxml. – Joop Mar 17 '15 at 18:07

1 Answers1

1

Somewhere you are adding some node(s) to the GridPane without specifying a columnIndex and rowIndex. (This can also, under some circumstances, happen even if you don't explicitly add them yourself. E.g. you have gridLinesVisible set to true on the GridPane.) It looks like at least one of those nodes is a Group; the output you describe is the default implementation of toString() coming from a Group instance.

The way the GridPane manages grid locations internally is to set specific values via each node's property map; the values in this map for the column index and row index are Integer properties. The getColumnIndex() and getRowIndex() retrieve those Integer properties and effectively call intValue() on them, so if they are not set, you get a NullPointerException.

So a quick fix may be to do something like

public Node getNodeByRowColumnIndex(final int row,final int column,GridPane gridPane) {
    Node result = null;
    ObservableList<Node> childrens = gridPane.getChildren();
    for(Node node : childrens) {
        if(node instanceof Circle && gridPane.getRowIndex(node) == row && gridPane.getColumnIndex(node) == column) {
            result = node;
            break;
        }
    }
    return result;
}

This will work if all the nodes you add with column indexes and row indexes properly set (and that you are interested in) are Circles. Obviously you can add some other logic to work around other scenarios.

The reason you are running into difficulties here is that you are effectively trying to retrieve data (what is at a location) from a view component (the GridPane). This is an anti-pattern: views should not be storing application data, they should merely passively observe it (at most). If you still have an extensive amount of work to do on the project, you should probably think about restructuring it so it is data-centric.

Here are some code snippets that might help you think about what I mean here:

public class Sheep {

    // data for intrinsic state of sheep omitted..

    private final Circle view ;

    public Sheep() {
        this.view = new Circle(...);
        // configure view...
    }

    public Node getView() {
        return view ;
    }

    // logic etc... manipulates intrinsic state of sheep....
}

Make sure you implement Point so it can be used properly in a collection:

public class Point {
    final int x, y ;
    @Override
    public boolean equals(Object other) {
        if (other.getClass() != Point.class) {
            return false ;
        }
        Point o = (Point)other ;
        return o.x == x && o.y == y ;
    }
    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

game code:

ObservableMap<Point, Sheep> allSheep = FXCollections.observableHashMap();
GridPane gameView = new GridPane();
allSheep.addListener((Change<? extends Point, ? extends Sheep> c) -> {
    if (c.wasAdded()) {
        Point location = c.getKey();
        Sheep addedSheep = c.getValue();
        gameView.add(addedSheep.getView(), location.x, location.y);
    } else if (c.wasRemoved()) {
        Sheep removedSheep = c.getValue();
        gameView.getChildren().remove(removedSheep.getView());
    }
}

Now the view is effectively set up to take care of itself, and you just work with the data, just by manipulating the map of locations to sheep:

public void addSheep(int x, int y) {
    allSheep.put(new Point(x,y), new Sheep());
}

public void removeSheep(int x, int y) {
    allSheep.remove(new Point(x, y));
}

Note the map contains all the data pertinent to the game, so you can examine it whenever you need and make changes appropriately. You should be able to separate that logic from the "view" of the game, and just have controller/presenter type classes encapsulating the link between them (for example, the map listener defined above, which updates the grid when the entries in the map change).

James_D
  • 201,275
  • 16
  • 291
  • 322