如何按网格中的位置从 GridPane 中删除节点(Circle 和 Button 对象)?

How to remove nodes (Circle and Button objects) from GridPane by location in the grid?

我在这个问题上停留了很长一段时间,我们正在处理一个 FXML 文件。我已经从这里看到并实现了这段代码: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;
    }

这适用于我们在开始时在 FXML 文件中添加的所有节点,但对于我们稍后在程序(游戏)的 运行 期间手动添加的节点,它不起作用.然后它得到一个空指针异常。如果我们打印出节点,我们会为在 运行 时间添加的节点得到这个:Group@181fe7 因此是一个内存地址。对于在 运行 之前在 fxml 文件中添加的节点,我们确实得到了一个包含节点信息的漂亮字符串,因此以某种方式向网格添加节点与在 fxml 中完成的方式不同。我们通过以下代码手动添加节点:

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);
}

为什么会这么难?我只想找到网格窗格中特定单元格上的内容,然后删除位于那里的一个对象。我为一些看似微不足道的事情花费了大量时间。

您在某处向 GridPane 添加了一些节点,但未指定 columnIndexrowIndex。 (在某些情况下,即使您没有自己明确添加它们,也会发生这种情况。例如,您在 GridPane 上将 gridLinesVisible 设置为 true。)看起来至少其中一个节点是 Group;您描述的输出是来自 Group 实例的 toString() 的默认实现。

GridPane内部管理网格位置的方式是通过每个节点的property map设置特定值;此映射中列索引和行索引的值是 Integer 属性。 getColumnIndex()getRowIndex() 检索那些 Integer 属性并有效地调用它们的 intValue(),所以如果它们没有设置,你会得到一个 NullPointerException

所以一个快速的解决办法可能是做一些像

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;
}

如果您添加的列索引和行索引设置正确(并且您感兴趣)的所有节点都是 Circle,这将起作用。显然,您可以添加一些其他逻辑来解决其他情况。

您在此处 运行 遇到困难的原因是您正在有效地尝试从视图组件 (GridPane) 检索数据(某个位置的内容)。这是一个反模式:视图不应该存储应用程序数据,它们应该只是被动地观察它(至多)。如果您对该项目仍有大量工作要做,您可能应该考虑对其进行重组,使其以数据为中心。

以下是一些代码片段,可以帮助您理解我的意思:

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....
}

确保实现 Point 以便它可以在集合中正确使用:

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);
    }
}

游戏代码:

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());
    }
}

现在视图已有效地设置为自行管理,您只需处理数据,只需操作绵羊的位置图即可:

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));
}

请注意地图包含与游戏相关的所有数据,因此您可以随时检查它并进行适当的更改。您应该能够将该逻辑与游戏的 "view" 分开,并且只需 controller/presenter 类型 类 封装它们之间的 link (例如,定义的地图侦听器上面,它会在地图中的条目更改时更新网格)。