JJ Roman JJ Roman - 2 months ago 22
Java Question

How to define which property ListView should use to render

I have a java bean object list which I would like to display in ListView control. By default ListView uses toString method.

How can I define which property to use for rendering in ListView?

I want to achieve same functionality as in TableView can be achieved by PropertyValueFactory in this code:

@FXML
private TableView<Person> mainTableView;
//...
TableColumn<Person,String> personColumn = new TableColumn<>("Name");List
personColumn.setCellValueFactory(new PropertyValueFactory("name"));
mainTableView.getColumns().add(personColumn);


Edit

It looks like there is no easy(out of the box) solution. Based on code from James_D I created generic class to deal with the problem. It wraps PropertyValueFactory - note that PropertyValueFactory firstly looks for method [NAME]Property() trying to get observable, only when it is not found it tries to access standard bean properties.

public class PropertyValueFactoryWrapperCellFactory<T> implements Callback<ListView<T>, ListCell<T>> {

private final PropertyValueFactory<T, String> pvf;

public PropertyValueFactoryWrapperCellFactory(String propertyName) {
super();
pvf = new PropertyValueFactory(propertyName);
}

@Override
public ListCell<T> call(ListView<T> param) {
return new ListCell<T>() {
@Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
textProperty().unbind();
if (item == null) {
return;
}
TableColumn.CellDataFeatures<T, String> cdf = new TableColumn.CellDataFeatures<>(null, null, item);
textProperty().bind(pvf.call(cdf));
}
};
}

}


Usage:

@FXML
private ListView<Person> mainListView;
//...
mainListView.setCellFactory(new PropertyValueFactoryWrapperCellFactory("name"));

Answer

Use a cell factory.

If the property is immutable, it's pretty straightforward:

ListView<MyDataType> listView = new ListView<>();
listView.setCellFactory(new Callback<ListView<MyDataType>, ListCell<MyDataType>>() {
    @Override
    public ListCell<MyDataType> call(ListView<MyDataType> lv) {
        return new ListCell<MyDataType>() {
            @Override
            public void updateItem(MyDataType item, boolean empty) {
                super.updateItem(item, empty);
                if (item == null) {
                    setText(null);
                } else {
                    // assume MyDataType.getSomeProperty() returns a string
                    setText(item.getSomeProperty());
                }
            }
        };
    }
});

If the property can change its value and the list view needs to update dynamically in response to these changes, you need to bind the textProperty of the list cell:

listView.setCellFactory(new Callback<ListView<MyDataType>, ListCell<MyDataType>>() {
    @Override
    public ListCell<MyDataType> call(ListView<MyDataType> lv) {
        return new ListCell<MyDataType>() {
            @Override
            public void updateItem(MyDataType item, boolean empty) {
                super.updateItem(item, empty);
                textProperty().unbind();
                if (item != null) {
                    // assumes MyDataType.someProperty() returns a StringProperty:
                    textProperty.bind(item.someProperty());
                }
            }
        };
    }
});