Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX 2.2: How to force a redraw/update of a ListView

I have a ListView control in a JavaFX 2 modal dialog window.

This ListView displays DXAlias instances, the ListCells for which are manufactured by a cell factory. The main thing the factory objects do is examine the UserData property data of the ListView and compare it to the item corresponding to the ListCell. If they are the same, the contents of the ListCell are rendered in red, otherwise black. I do this to indicate which of the items in the ListView is currently selected as the "default". Here is my ListCell factory class so you can see what I mean:

private class AliasListCellFactory implements 
    Callback<ListView<DXSynonym>, ListCell<DXSynonym>> {

@Override
public ListCell<DXSynonym> call(ListView<DXSynonym> p) {
    return new ListCell<DXSynonym>() {

    @Override
    protected void updateItem(DXSynonym item, boolean empty) {
        super.updateItem(item, empty);

        if (item != null) {
            DXSynonym dx = (DXSynonym) lsvAlias.getUserData();

            if (dx != null && dx == item) {
                this.setStyle("-fx-text-fill: crimson;");                    
            } else { this.setStyle("-fx-text-fill: black;"); }

            this.setText(item.getDxName());

        } else { this.setText(Census.FORMAT_TEXT_NULL); }
    }};
}

I have a button handler called "handleAliasDefault()" which makes the selected item in the ListView the new default by taking the selected DXAlias instance and storing it into the ListView: lsvAlias.setUserData( selected DXAlias ). Here is the handler code:

// Handler for Button[fx:id="btnAliasDefault"] onAction
    @FXML
    void handleAliasDefault(ActionEvent event) {

        int sel = lsvAlias.getSelectionModel().getSelectedIndex();
        if (sel >= 0 && sel < lsvAlias.getItems().size()) {
            lsvAlias.setUserData(lsvAlias.getItems().get(sel));
        }
    }

Because the change that is made in response to clicking on the Set Default button is to change the ListView's UserData() without any change to the backing ObservableList, the list does not correctly indicate the new default.

Is there a way to force a ListView to re-render its ListCells? There are a quadrillion questions from Android users on this subject, but there appears to be no happiness for JavaFX. I may have to make a "meaningless change" to the backing array to force a redraw.

I see that this was asked for JavaFX 2.1: Javafx ListView refreshing

like image 951
scottb Avatar asked Jun 02 '13 05:06

scottb


1 Answers

For any body else that ends up on this page:

The correct way of doing this is to supply your observable list with an "extractor" Callback. This will signal the list (and ListView) of any property changes.

Custom class to display:

public class Custom {
    StringProperty name = new SimpleStringProperty();
    IntegerProperty id = new SimpleIntegerProperty();

    public static Callback<Custom, Observable[]> extractor() {
        return new Callback<Custom, Observable[]>() {
            @Override
            public Observable[] call(Custom param) {
                return new Observable[]{param.id, param.name};
            }
        };
    }

    @Override
    public String toString() {
        return String.format("%s: %s", name.get(), id.get());
    }
}

And your main code body:

ListView<Custom> myListView;
//...init the ListView appropriately
ObservableList<Custom> items = FXCollections.observableArrayList(Custom.extractor());
myListView.setItems(items);
Custom item = new Custom();
items.add(item);
item.name.set("Mickey Mouse");
// ^ Should update your ListView!!!

See (and similar methods): https://docs.oracle.com/javafx/2/api/javafx/collections/FXCollections.html#observableArrayList(javafx.util.Callback)

like image 166
Andrey Avatar answered Nov 05 '22 11:11

Andrey