pror21 pror21 - 1 month ago 6
Java Question

How to populate table column cells with ComboBox and observe?

My table view has 2 columns. The first one has the name of my object and the second i want to have a ComboBox with values from my object attribute list.

Any change to the ComboBox will change its selected attribute with the list element that is chosen by the user.

For example:

| Name | Choices |
|---------------|-------------------|
| Object 1 Name | Object 1 ComboBox |
| Object 2 Name | Object 2 ComboBox |


So far i've managed to populate the names but i'm stuck with the choices column.

MyObject Model:

public class MyObject {

// Objects name is the filename
private File file;
// Selection from the ComboBox that is populated with infoList elements
private SomeInfo selectedInfo;

private List<SomeInfo > infoList;

... getter/setter


Controller:

@FXML
private TableView<MyObject> myObjectTableView;
@FXML
private TableColumn<MyObject, String> myObjectNameTableColumn, myObjectChoicesTableColumn;

private void populateTableView(List<MyObject> myObjectList) {
ObservableList<MyObject> myObjectObservableList = FXCollections.observableList(myObjectList);
myObjectTableView.setItems(myObjectObservableList);
myObjectNameTableColumn.setCellValueFactory(c -> new SimpleStringProperty(c.getValue().getFile().getName()));
}

Answer

The type of your myObjectChoicesTableColumn is wrong. You need

@FXML
private TableColumn<MyObject, List<SomeInfo>> myObjectChoicesTableColumn ;

Then your cell value factory can be

myObjectChoicesTableColumn.setCellValueFactory(cellData -> 
    new SimpleObjectProperty<>(cellData.getValue().getInfoList()));

and to display a combo box you need a cell factory:

myObjectChoicesTableColumn.setCellFactory(col -> {

    ComboBox<SomeInfo> combo ;

    TableCell<MyObject, List<SomeInfo>> cell = new TableCell<MyObject, List<SomeInfo>>() {
        @Override
        protected void updateItem(List<SomeInfo> info, boolean empty) {
            super.updateItem(info, empty);
            if (empty) {
                setGraphic(null);
            } else {
                MyObject rowItem = myObjectTableView.getItems().get(getIndex());
                combo.getItems().setAll(info);
                combo.setValue(rowItem.getSelectedInfo());
                setGraphic(combo);
            }
        }
    };

    combo.valueProperty().addListener((obs, oldValue, newValue) -> 
        myObjectTableView.getItems().get(cell.getIndex()).setSelectedInfo(newValue));

    return cell ;
}

You might find it cleaner to refactor the model, so that you wrap the list and the selected item in a new class:

public class SelectableInfo {
    private final ObservableList<SomeInfo> infoList = FXCollections.observableArrayList() ;

    private SomeInfo selectedInfo ;

    public ObservableList<SomeInfo> getInfoList() {
        return infoList ;
    }

    public SomeInfo getSelectedInfo() {
        return selectedInfo ;
    }

    public void setSelectedInfo(SomeInfo info) {
        selectedInfo = info ;
    }
}

and

public class MyObject {
    private File file ;
    private SelectableInfo selectableInfo ;

    // getters and setters...
}

Then you would have the simpler:

@FXML
private TableColumn<MyObject, SelectableInfo> myObjectChoicesTableColumn ;

myObjectChoicesTableColumn.setCellValueFactory(cellData -> new SimpleObjectProperty<>(cellData.getValue().getSelectableInfo());

myObjectChoicesTableColumn.setCellFactory(col -> {
    ComboBox<SomeInfo> combo = new ComboBox<>();
    TableCell<MyObject, SelectableInfo> cell = new TableCell<MyObject, SelectableInfo>() {
        @Override
        protected void updateItem(SelectableInfo info, boolean empty) {
            super.updateItem(info, empty) ;
            if (empty) {
                setGraphic(null);
            } else {
                combo.setItems(info.getInfoList());
                combo.setValue(info.getSelectedInfo());
                setGraphic(combo);
            }
        }
    };
    combo.valueProperty().addListener((obs, oldValue, newValue) -> 
        cell.getItem().setSelectedInfo(newValue));
    return cell ;
});
Comments