0
votes

I have a list of objects which containing lists of a complex data type and I need to bind these properties to my TableView. My entities are looking like this:

public class Customer
{
   StringProperty property1;
   StringProperty property2;
   ....
   List<Contract> contracts;
}


public class Contract
{
    StringProperty property1;
    StringProperty property2;
    ....
}

My observableArrayList contains a list of Customer which contains a list of Contract. Getting the Customer properties is easy:

sampleColumn1.setCellValueFactory(celldata -> celldata.getValue().sampleProperty1());
sampleColumn2.setCellValueFactory(celldata -> celldata.getValue().sampleProperty2());
...

But I can't get the properties of the Contract list.. Currently I do the following workaround:

PropertyValueFactory<Customer, List<Contract>> contractFactory = new PropertyValueFactory<>("contracts");
contractIdColumn.setCellValueFactory(contractFactory);
contractIdColumn.setCellFactory(celldata -> new TableCell<Customer>, List<Contract>>(){
@Override
public void updateItem(List<Contract> contracts, boolean empty){
    super.updateItem(contracts, empty);
    if(empty)
    setText("");            
    else
    setText(contracts.stream().map(Contract::getContractId)
   .collect(Collectors.joining("\n")));
}
});

But this code is dirty, does somebody know an approach to do this with cellValueFactory and lambda expressions? Any suggestions?

2

2 Answers

0
votes

A cell value factory is simply a Callback taking a CellDataFeatures and returning an appropriate observable value, so you should be able to do something like this:

contractIdColumn.setCellValueFactory(cellDataFeatures -> new SimpleStringProperty(cellDataFeatures.getValue().contracts.stream()
    .map(Contract::getContractId)
    .collect(Collectors.joining("\n"))));  

Note that if you were to use an ObservableList instead of a list you could use Bindings.createStringBinding so that the value would update if the list updates but the Customer element does not change.

0
votes

I don't think the code you have is too bad. The important thing here is to separate the data displayed in the cell (provided by the cellValueFactory) from how the data is presented (provided by the cellFactory), and you have that. The data is clearly the list of contracts, so you certainly want

TableColumn<Customer, List<Contract>> contractIdColumn ;

and you need the cell value to be an ObservableValue<List<Contract>>, which you can do either as you have it:

contractIdColumn.setCellValueFactory(new PropertyValueFactory<>("contracts"));

or, in a more type-safe way, with

contractIdColumn.setCellValueFactory(cellData -> 
    new SimpleObjectProperty<>(cellData.getValue().getContracts()));

Then your current cell factory will display the list of contracts as text, with each contract ID on a new line. You could readily modify the cell factory (without changing the cell value factory) to use, e.g. a ListView<Contract>:

contractIdColumn.setCellFactory(celldata -> new TableCell<Customer>, List<Contract>>(){

    private final ListView<Contract> listView = new ListView<>();

    @Override
    public void updateItem(List<Contract> contracts, boolean empty){
        super.updateItem(contracts, empty);
        if(empty)
            setGraphic(null);            
        else {
            listView.getItems().setAll(contracts);
            setGraphic(listView);
        }
    }

});

(just a rough example; you may need to style the cells to fit the list view properly, and this assumes Contract has an appropriate toString(); you may need to set a cell factory on the ListViews too, etc).