Search code examples
javafxgridpane

JavaFX continuous form dynamically add new row with content in gridpane


I need some help creating a continuous form with a gridpane like in ms access. Initially the gridpane has 1 row and 3 columns

| Choicebox | Delete Button | Add Button |

>> screenshot initial gridpane

public class myGridpane {

    @FXML
    private GridPane gp_form;
    private List<Car> myCars = new ArrayList();

    public void initialize() {
        myCars = loadCars();
        initGridpane(myCars);
    }

    private initGridpane(List<Car> myCars) {

        int rowIndex = 0;

        for (Car myCar : myCars) {

          Button b_newCar = new Button("+");
          Button b_deleteCar = new Button("-");

          ChoiceBox<Car> cb_car = new ChoiceBox<>();
          cb_car.setItems(Car.getAllCarKeys());
          cb_car.setValue(myCar.getModel());


          b_deleteCar.setOnAction(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent e) {
              // remove row
              // remove car from List myCars
            }
          });

          b_newCar.setOnAction(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent e) {
              // add new row
            }
          });

          cb_car.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>() {

                @Override
                public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
                    // update myList
                }
            });

          gp_form.add(cb_car, 0, rowIndex);
          gp_form.add(b_deleteCar, 1, rowIndex);
          gp_form.add(b_newCar, 2, rowIndex);

          rowIndex++;
        }
    }
}

The result should look like this:

result gridpane.

How do I remove the row and the value of the choicebox from my list? And how do I update my list if a choicebox is changed?


Solution

  • I suggest to use ListView with custom ListCell instead of GridPane because your cars list may contain for example 1k values. In that case you will create 3k nodes in GridPane and it will reduce performance. ListView will create only visible cells and reuse them when needed.

    Try this code:

    private ObservableList<Car> cars = FXCollections.observableArrayList();
    
    @Override
    public void start(Stage primaryStage) {
        cars.addAll(new Car(CAR_TYPE.CAR1), new Car(CAR_TYPE.CAR2), new Car(CAR_TYPE.CAR3));
    
        ListView<Car> carsListView = new ListView<>();
        carsListView.setCellFactory(c -> new CarListCell());
        carsListView.setItems(cars);
    
        StackPane root = new StackPane();
        root.getChildren().add(carsListView);
    
        Scene scene = new Scene(root, 300, 250);
    
        primaryStage.setTitle("Cars list view");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    private class CarListCell extends ListCell<Car> {
    
        private HBox content = new HBox();
        private ChoiceBox<CAR_TYPE> cb = new ChoiceBox<>();
        private Button add = new Button("+");
        private Button sub = new Button("-");
    
        public CarListCell() {
            cb.setItems(FXCollections.observableArrayList(CAR_TYPE.values()));
            cb.setMaxWidth(Double.MAX_VALUE);
            HBox.setHgrow(cb, Priority.ALWAYS);
            content.getChildren().addAll(cb, add, sub);
            content.setSpacing(10);
            setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            setGraphic(content);
        }
    
        @Override
        protected void updateItem(Car item, boolean empty) {
            super.updateItem(item, empty);
            if (item == null || empty) {
                setText(null);
                setGraphic(null);
            } else {
                setGraphic(content);
                cb.setValue(item.getType());
                add.setOnAction(e -> {
                    Car newCar = new Car(cb.getValue());
                    cars.add(newCar);
                });
                sub.setOnAction(e -> {
                    cars.remove(item);
                });
            }
        }
    
    }
    
    private enum CAR_TYPE {
        CAR1, CAR2, CAR3;
    }
    
    private class Car {
    
        private CAR_TYPE type;
    
        public Car(CAR_TYPE type) {
            this.type = type;
        }
    
        public CAR_TYPE getType() {
            return type;
        }
    
        public void setType(CAR_TYPE type) {
            this.type = type;
        }
    }