JavaFX continuous form dynamically add new row with content in gridpane

1.5k views Asked by At

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?

2

There are 2 answers

1
MBec On BEST ANSWER

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;
    }
}
1
tonyh On

i have added a second dropdown, a save button and changed the start with an empty initial list. this works fine, but how do i get all selected Cars with their color to an ArrayList when i click on the save button?

And if i select the first car with its color without clicking the "+" button the ObservableList is empty.

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

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

    private ObservableList<Car> cars = FXCollections.observableArrayList();

    @Override
    public void start(Stage primaryStage) {

        Car initialCar = new Car(null,null);

        cars.add(initialCar);

        ListView<Car> carsListView = new ListView<>();
        carsListView.setCellFactory(c -> new CarListCell());
        carsListView.setItems(cars);

        StackPane root = new StackPane();
        root.getChildren().addAll(carsListView, new Button("Save"));

        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_car = new ChoiceBox<>();
        private ChoiceBox<CAR_COLOR> cb_color = new ChoiceBox<>();
        private Button add = new Button("+");
        private Button sub = new Button("-");

        public CarListCell() {

            cb_car.setItems(FXCollections.observableArrayList(CAR_TYPE.values()));
            cb_color.setItems(FXCollections.observableArrayList(CAR_COLOR.values()));

            HBox.setHgrow(cb_car, Priority.ALWAYS);
            HBox.setHgrow(cb_color, Priority.ALWAYS);

            content.getChildren().addAll(cb_car, cb_color, 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_car.setValue(item.getType());
                cb_color.setValue(item.getColor());
                add.setOnAction(e -> {
                    Car newCar = new Car(cb_car.getValue(), cb_color.getValue());
                    cars.add(newCar);
                });
                sub.setOnAction(e -> {
                    cars.remove(item);
                });
            }
        }

    }

    private enum CAR_TYPE {
        CAR1, CAR2, CAR3;
    }

    private enum CAR_COLOR {
        BLUE, RED, GREEN;
    }

    private class Car {

        private CAR_TYPE type;
        private CAR_COLOR color;

        public Car(CAR_TYPE type, CAR_COLOR color) {
            this.type = type;
            this.color = color;
        }

        public CAR_TYPE getType() {
            return type;
        }

        public void setType(CAR_TYPE type) {
            this.type = type;
        }

        public CAR_COLOR getColor() {
            return color;
        }

        public void setColor(CAR_COLOR color) {
            this.color = color;
        }
    }
}