-2

I've searched for a while now explanations on internet about filling cells of a TableView with ComboBox, but each one of them is using lines such as :

    TableView<SomeObject> tableView = new TableView<>();
    TableColumn<SomeObject, String> column = new TableColumn<>("property");
    ObservableList<String> list = FXCollections.observableArrayList("option1", "option2", "option3");
    column.setCellValueFactory(new PropertyValueFactory<>("property"));
    column.setCellFactory(ComboBoxTableCell.forTableColumn(list);
    tableView.getColumns().add(column);

... with SomeObject defined in another class.

The point is, I'm building a GUI where I cannot define Object to fill any TableView (because I can't anticipate the number of columns my table will contain). All I know is my TableView will be full of String, and the first two columns will always have the same title ("N°" and "Truth"). Nothing else. So I cannot use the upper code because I don't have any "property" to fill.

Here is how I fill my TableView, without ComboBox, (and it works correctly), where my data is called rawData and it's an Partionner (code below) :

ObservableList<ObservableList<String>> data = FXCollections.observableArrayList();
for(int i = 1; i < rawData.size(); i++){
    // Begin at i = 1 because the first raw (i = 0) is the names of the columns.
    data.add(FXCollections.observableArrayList(rawData.get(i)));
}
tableView.setItems(data);

for(int i = 0; rawData.get(0).size(); i++){
    final int cuurentColumn = i;
    TableColumn<ObservableList<String>, String> column = new TableColumn<>((String) rawData.get(0).get(i)); // Here are the columns titles.
    column.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().get(currentColumn)));
    column.setStyle("-fx-alignment: CENTER");
    tableView.getColumns().add(column);
}

where a Partionner is :

public final class Partionner<T> extends AbstractList<List<T>>{
    private final List<T> list;
    private final int chunkSize;

    public Partionner(List<T> list, int chunkSize){
        this.list = list;
        this.chunkSize = chunkSize;
    }

    public static <T> Partitionner<T> ofSize(List<T> list, int chunkSize){
        return new Partionner<>(list, chunkSize);
    }

    @Override
    public List<T> get(int index){
        int start = index * chunkSize;
        int end = Math.min(start + chunkSize, list.size());
        if(start > end){
            throw new IndexOutOfBoundsException("OOB Partitionner");
        }
        return new ArrayList<>(list.subList(start, end));
    }

    public int size(){
        return (int) Math.ceil((double) list.size() / (double) chunkSize);
    }
// You can create a Partionner with :
Partitionner partitionner = Partitionner.ofSize(arrayList, length);
// Where arraylist is an ArrayList<String> and length it's size
}

From all this data, which come from a CSV actually, I want to use a comboBox for each cell of the 2nd column "Truth". I thought about something like :

ObservableList<ObservableList<String>> data = FXCollections.observableArrayList();
for(int i = 1; i < rawData.size(); i++){
    // Begin at i = 1 because the first raw (i = 0) is the names of the columns.
    data.add(FXCollections.observableArrayList(rawData.get(i)));
}
tableView.setItems(data);

for(int i = 0; i < rawData.get(0).size(); i++){
    final int currentColumn = i;
    if(i == 1){
        // So it's here I'm completly disabled to fill this code :/
        // Begin of bullshit
        TableColumn<ObservableList<String>, String> column = new TableColumn<>((String) rawData.get(0).get(i));
        ObservableList<String> possibleTruth = FXCollections.obervableArrayList("1", "2", "3");
        final ComboBox<String> comboBox = new ComboBox<>(possibleTruth);
        column.setCellValueFactory(......);
        column.setCellFactory(......);
        tableView.getColumns.add(col);
        // End of bullshit

    } else {
        TableColumn<ObservableList<String>, String> column = new TableColumn<>((String) rawData.get(0).get(i));
        column.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().get(currentColumn)));
        column.setStyle("-fx-alignment: CENTER");
        tableView.getColumns().add(column);
    }
}

Any ideas to fill my code ? Feel free to leave your paypal if your solution works haha (jk) :)

----------- Here is a minimal reproductible example -----------

Class main :

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Hello World");
        primaryStage.setResizable(false);
        Scene scene = new Scene(root, 1280, 720);
        primaryStage.setScene(scene);
        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }
}

Class Controller :

public class Controller {
    @FXML public TableView tableView;
    public ArrayList<String> inputData;
    public Partitionner rawData;

    public void initialize(){
        inputData = new ArrayList<>();
        inputData.add("N°");
        inputData.add("Truth");
        inputData.add("Col A");
        inputData.add("Col B");
        inputData.add("1");
        inputData.add("t1");
        inputData.add("valA1");
        inputData.add("valB1");
        inputData.add("2");
        inputData.add("t2");
        inputData.add("valA2");
        inputData.add("valB2");
        inputData.add("3");
        inputData.add("t3");
        inputData.add("valA3");
        inputData.add("valB3");
        rawData = Partitionner.ofSize(inputData, 4);
        System.out.println(rawData);
    }

    public void populateTableView (){
        ObservableList<ObservableList<String>> data = FXCollections.observableArrayList();
        for(int i = 1; i < rawData.size(); i++){
            // Begin at i = 1 because the first raw (i = 0) is the names of the columns.
            data.add(FXCollections.observableArrayList(rawData.get(i)));
        }
        tableView.setItems(data);

        for(int i = 0; i < rawData.get(0).size(); i++){
            final int currentColumn = i;
            if(i == 1){
                // Trying to fill the "Truth" column with a combobox in each cell containing t1, t2 and t3.
                TableColumn<ObservableList<String>, String> column = new TableColumn<>((String) rawData.get(0).get(i));
                ObservableList<String> possibleTruth = FXCollections.observableArrayList("t1", "t2", "t3");
              column.setCellFactory(ComboBoxTableCell.forTableColumn(possibleTruth));
                tableView.getColumns().add(column);

            } else {
                TableColumn<ObservableList<String>, String> column = new TableColumn<>((String) rawData.get(0).get(i));
                column.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().get(currentColumn)));
                column.setStyle("-fx-alignment: CENTER");
                tableView.getColumns().add(column);
            }
        }
    }
}

sample.fxml file :

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="752.0" prefWidth="1162.0" xmlns="http://javafx.com/javafx/15.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
   <children>
      <Button layoutX="140.0" layoutY="683.0" mnemonicParsing="false" onAction="#populateTableView" prefHeight="25.0" prefWidth="161.0" text="Fill Data" />
      <TableView fx:id="tableView" layoutX="43.0" layoutY="39.0" prefHeight="585.0" prefWidth="1073.0" />
   </children>
</AnchorPane>

And don't forget to add the Partitionner class, which is just above.

  • you forgot to set the cellValueFactory for the combo column, see James answer and apply it _to the letter_ BTW: this is not minimal (hard-coded data in a simple list would do, no need for any specialized impl, like Partitionner) – kleopatra Jul 29 '21 at 09:48
  • Hey, thanks for your answer. Adding column.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().get(currentColumn))); only display "t1" in the first cell, "t2" in the second, and "t3" in the third, instead of adding a ComboBox. Any idea why ? – Pierre-Olivier Jul 29 '21 at 11:46

1 Answers1

0

Just combine the pieces you already have. Doesn't the following do what you want?

for(int i = 0; rawData.get(0).size(); i++){
    final int currentColumn = i;
    TableColumn<ObservableList<String>, String> column = new TableColumn<>((String) rawData.get(0).get(i);
    column.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().get(currentColumn)));
    column.setStyle("-fx-alignment: CENTER");
    tableView.getColumns().add(column);

    if(i == 1){
        ObservableList<String> possibleTruth = FXCollections.obervableArrayList("1", "2", "3");
        column.setCellFactory(ComboBoxTableCell.forTableColumn(possibleTruth));
    } 
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Hi, thanks for your answer. If I do as you say, i'll have int the 2nd column (Truth) the string in rawData in the same column, and not a ComboBox with with options 1, 2, 3. That's exactly what I don't understand :/ – Pierre-Olivier Jul 28 '21 at 14:00
  • 3
    @Pierre-Olivier I don't understand your comment. You probably need to create a [mre] and post it in your question to demonstrate the problem. – James_D Jul 28 '21 at 14:04
  • Here it is. Hope it will help you answering me. As you will see with this code, the "Truth" colulmn is empty. – Pierre-Olivier Jul 29 '21 at 07:19