6

I'm using a GridPane to stock informations about cities (in a game), but sometimes I want to delete some lines. This is the Class I use for my GridPane :

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.franckyi.kingsim.city.City;

import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;

public class CityGrid extends GridPane {

    private List<City> cities;

    public CityGrid() {
        super();
        cities = new ArrayList<City>();
    }

    public List<City> getCities() {
        return cities;
    }

    public void deleteCity(int row) {
        cities.remove(row - 1);
        removeNodes(getNodesFromRow(row));
        int i = row;
        while (!getNodesFromRow(i + 1).isEmpty()) {
            moveNodes(i + 1, getNodesFromRow(i + 1));
            removeNodes(getNodesFromRow(i + 1));
            i++;
        }
    }

    public void addCity(City city) {
        cities.add(city);
        int col = 0;
        List<Node> rowNodes = new ArrayList<Node>();
        Button mod = new Button("Modify");
        mod.setOnAction(event -> {

        });
        Button del = new Button("Delete");
        del.setOnAction(event -> {
            deleteCity(getRowIndex(del));
        });
        rowNodes.addAll(Arrays.asList(new CheckBox(), new Text(city.getName()), new Text(city.getStatus().getText()),
                new Text(city.getTrade() + ""), new Text(city.getCost() + ""), new Text(city.getTroops() + ""), mod,
                del));
        for (Node n : rowNodes) {
            n.setId(cities.size() + "");
            this.add(n, col, cities.size());
            col++;
        }
    }

    private List<Node> getNodesFromRow(int i) {
        List<Node> list = new ArrayList<Node>();
        for (Node n : getChildren()) {
            if (getRowIndex(n).equals(i)) {
                list.add(n);
            }
        }
        System.out.println(list.size());
        return list;
    }

    private void removeNodes(List<Node> list) {
        for (Node node : list) {
            this.getChildren().remove(getIndex(getColumnIndex(node), getRowIndex(node)));
        }
    }

    private void moveNodes(int row, List<Node> nodes) {
        int i = 0;
        for (Node node : getNodesFromRow(row)) {
            this.getChildren().set(getIndex(getColumnIndex(node), getRowIndex(node)),
                    nodes.get(i));
            i++;
        }
    }

    private int getIndex(int col, int row) {
        int i = 0;
        for (Node node : getChildren()) {
            if (getColumnIndex(node) == col && getRowIndex(node) == row)
                return i;
            i++;
        }
        return 0;
    }

}

The addCity function works perfectly. The deleteCity function also works when I delete the last city added. But when I delete a city, it automatically deletes ALL the cities added after the one I delete, and I don't want that.

You should also notice that everytime the getNodesFromRow(int i) method is executed, it prints the number of Nodes in the selected row. When I add two cities and I delete the first one, this is what I get in the console : 8, 0, 8, 8, 8, 8, 8, 0.

Can someone help me ? (Tell me if you want all the code needed to reproduce it at home)

GOXR3PLUS
  • 6,877
  • 9
  • 44
  • 93
Franckyi
  • 243
  • 3
  • 12
  • 2
    Please post the code in the question ([properly formatted](http://meta.stackexchange.com/questions/22186/how-do-i-format-my-code-blocks)), instead of providing a link to it. – James_D Nov 09 '16 at 21:53

2 Answers2

14
private void moveNodes(int row, List<Node> nodes) {
    int i = 0;
    for (Node node : getNodesFromRow(row)) {
        this.getChildren().set(getIndex(getColumnIndex(node), getRowIndex(node)),
                nodes.get(i));
        i++;
    }
}

This does not work. The index in the child list has no meaning in a GridPaneother then the order in which they are drawn. The row/column index is saved to the properties map of each child. To modify these, you need to use the static GridPane.setRowIndex method.

Example

@Override
public void start(Stage primaryStage) {
    Button btn = new Button("Delete");
    TextField tf = new TextField();

    TextFormatter<Integer> formatter = new TextFormatter<>(new IntegerStringConverter());
    formatter.setValue(0);
    tf.setTextFormatter(formatter);
    btn.disableProperty().bind(IntegerExpression.integerExpression(formatter.valueProperty()).lessThan(0));

    GridPane grid = new GridPane();
    grid.setHgap(5);
    grid.setVgap(5);

    btn.setOnAction((ActionEvent event) -> {
        deleteRow(grid, formatter.getValue());
    });

    for (int r = 0; r < 5; r++) {
        for (int c = 0; c < 3; c++) {
            grid.add(new Text(r+"_"+c), c, r);
        }
    }

    Scene scene = new Scene(new VBox(new HBox(tf, btn), grid));

    primaryStage.setScene(scene);
    primaryStage.show();
}

static void deleteRow(GridPane grid, final int row) {
    Set<Node> deleteNodes = new HashSet<>();
    for (Node child : grid.getChildren()) {
        // get index from child
        Integer rowIndex = GridPane.getRowIndex(child);

        // handle null values for index=0
        int r = rowIndex == null ? 0 : rowIndex;

        if (r > row) {
            // decrement rows for rows after the deleted row
            GridPane.setRowIndex(child, r-1);
        } else if (r == row) {
            // collect matching rows for deletion
            deleteNodes.add(child);
        }
    }

    // remove nodes from row
    grid.getChildren().removeAll(deleteNodes);
}
fabian
  • 80,457
  • 12
  • 86
  • 114
  • Thanks for the quick answer. I'll try this code when I'll be at home. I forgot to say that I already have a line in the GridPane (that is not a city, it's just a header), does it impact your code ? – Franckyi Nov 10 '16 at 06:22
  • As long as there are no row spans used, there should be no problem. You just need to use the correct row index, i.e. if the header uses a single row and the second row (index=1) contains the first city, you need to pass `1` to `deleteRow`. – fabian Nov 10 '16 at 11:23
  • Oh, and I have an other problem. The deleteRow works well but I don't know how to modify a row... – Franckyi Nov 10 '16 at 16:55
  • @Franckyi depending on what you mean by "modify a row" you could get the column index of a child the same way you get the row index (but using `getColumnIndex` instead of `getRowIndex`) which allows you to find the nodes. If you want to add more elements to a row, there is also a `addRow(int index, Node... nodes)` method in `GridPane`. – fabian Nov 10 '16 at 18:52
  • Ok, I made a working method for modifying the line (by modifying a line, I mean changing some values, not adding values), but thanks anyway :) – Franckyi Nov 10 '16 at 19:27
1

I had the same task today with the added requirement to not only remove (hide) a single row but multiple rows and also to readd (unhide) them later. (My use case is a form that has to show varying subsets of a set of fields, depending on choices made by the user.)

Put on the right track by fabian, whom I'd like to thank, I designed the GridPaneRowHider class shown below. To use it with a given GridPane,

GridPane myPane = ...

allocate an instance

GridPaneRowHider h = new GridPaneRowHider(); 

and call

h.hide(myPane,1,2,6);

to hide the specified rows and

h.unhide(myPane);

to reveal them again.

So here's the code:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.IntStream;

import nnn.util.collection.Pair;
import javafx.scene.Node;
import javafx.scene.layout.GridPane;

/**
 * An object that can hide and unhide rows in a GridPane.
 * 
 * @author Tillmann
 * @since 1.1.9
 */
public class GridPaneRowHider {
/**
 * A container of the currently hidden Nodes, along with information about
 * their positions; associates rows with Nodes in row; sorted by rows,
 * ascending
 */
private Map<Integer,List<Pair<Node,Integer>>> hidden_ = new TreeMap<Integer,List<Pair<Node,Integer>>>(
        (a, b) -> a.compareTo(b));

/**
 * From the specified GridPane hides the specified rows.
 * 
 * @param gp the GridPane
 * @param rows the rows to hide
 * @see #unhide(GridPane)
 */
public void hide(GridPane gp, int... rows) {
    // to suit the algorithm, sort the rows in descending fashion (higher row
    // numbers before lower, i.e. visually south to north)
    Integer[] sRows = IntStream.of(rows).boxed().toArray(Integer[]::new);
    Arrays.sort(sRows,(a, b) -> b.compareTo(a));
    // row by row
    for (int row : sRows) {
        // the list of Nodes in this row
        List<Pair<Node,Integer>> li = new ArrayList<Pair<Node,Integer>>();
        hidden_.put(Integer.valueOf(row),li);
        // look at all the Nodes
        for (Iterator<Node> it = gp.getChildren().iterator(); it.hasNext();) {
            Node n = it.next();
            int r = GridPane.getRowIndex(n);
            // if it's a Node in the row to hide
            if (r == row) {
                // save it in the list
                li.add(new Pair<>(n,GridPane.getColumnIndex(n)));
                // and remove it from the GridPane
                it.remove();
            }
            // if it's a Node further down, move it a row upwards (to the North)
            // to fill the visual gap
            else if (r > row)
                GridPane.setRowIndex(n,r - 1);
        }
    }
}

/**
 * Shows the rows again that have been hidden by a previous call to
 * <code>hide()</code>.
 * <p>
 * 
 * If no call to hide() took place before (or unhide() has been called
 * already), nothing happens. If hide() has been called several times, the
 * result is undefined.
 * 
 * @param gp the GridPane
 * @see #hide(GridPane, int...)
 */
public void unhide(GridPane gp) {
    // walk along the rows from north to south
    for (Map.Entry<Integer,List<Pair<Node,Integer>>> me : hidden_.entrySet()) {
        // look at the Nodes in the GridPane
        for (Node n : gp.getChildren()) {
            int r = GridPane.getRowIndex(n);
            // if the Node is in the row to unhide or further down, make room
            // by moving it a row further down (to the south)
            if (r >= me.getKey())
                GridPane.setRowIndex(n,r + 1);
        }
        // readd the hidden Nodes to the GridPane at their saved locations
        for (Pair<Node,Integer> p : me.getValue())
            gp.add(p.t_,p.u_,me.getKey());
    }
    // and mark we're done
    hidden_.clear();
}
}
Tillmann
  • 149
  • 7