JavaFX 2.2: How to force redraw / update ListView

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

This ListView displays the DXAlias, ListCells instances for which the factory cell is being produced. The main thing that factory objects do is to look at these UserData ListView properties and compare them with the element corresponding to the ListCell. If they match, the contents of the ListCell are displayed in red; otherwise, in black. I am doing this to indicate which of the items in the ListView is currently selected as "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 handle "handleAliasDefault ()" that makes the selected item in the ListView new by default, taking the selected DXAlias ​​instance and saving it in the ListView: lsvAlias.setUserData (selected by 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)); } } 

Since the change made in response to clicking the Set Default button is to change the ListView UserData () without making any changes to the ObservableList database, the list incorrectly indicates the new default.

Is there a way to get ListView to re-render its ListCells? There are four quadrillion Android related issues on this subject, but there seems to be no happiness for JavaFX. I may have to make a “pointless change” to the support array to force a redraw.

I see it was suggested for JavaFX 2.1: Updating the Javafx ListView

+6
source share
4 answers

Currently, I can get ListView to redraw and correctly specify the default one, using the following method, called forceListRefreshOn (), in my button handler:

 @FXML void handleAliasDefault(ActionEvent event) { int sel = lsvAlias.getSelectionModel().getSelectedIndex(); if (sel >= 0 && sel < lsvAlias.getItems().size()) { lsvAlias.setUserData(lsvAlias.getItems().get(sel)); this.<DXSynonym>forceListRefreshOn(lsvAlias); } } 

The helper method simply collapses the ObservableList from the ListView and then swaps it back, supposedly causing the ListView to update the ListCells:

 private <T> void forceListRefreshOn(ListView<T> lsv) { ObservableList<T> items = lsv.<T>getItems(); lsv.<T>setItems(null); lsv.<T>setItems(items); } 
+5
source

Not sure if this works in JavaFX 2.2, but it happens in JavaFX 8 and it took a while to figure it out. You need to create your own ListViewSkin and add a refresh method, for example:

 public void refresh() { super.flow.recreateCells(); } 

This will call updateItem without replacing the entire Observable collection.

In addition, to use the new skin, you need to instantiate it and set it in the controller’s initialize method if you are using FXML:

 MySkin<Subscription> skin = new MySkin<>(this.listView); // Injected by FXML this.listView.setSkin(skin); ... ((MySkin) listView.getSkin()).refresh(); // This is how you use it 

I found this after debugging the behavior of the Accordion. This control updates the ListView that it contains every time you extend it.

+12
source

For any other object that ends on this page:

The right way to do this is to provide your observable list with a “callback” extractor. 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)

+10
source

You don't seem to need to use the updateItem method. What you need to do is save the current cell indicator by default (which is now in the user data), in OblectProperty. And add a listener to this property from each cell. When the value changes, reassign the style.

But I think that such a binding will cause another problem - when scrolling, new cells will be created, but the old ones will not leave bindings, which may cause a memory leak. Therefore, you need to add a listener to remove the cell from the scene. I think this can be done by adding a listener to parentProperty when it becomes null - remove the binding.

The user interface should not be updated. It updates automatically when the properties / rendering of existing nodes changes. Therefore, you just need to update the appearance / property of existing nodes (cells). And so as not to forget that cells can be created in bulk, during scrolling / rendering, etc.

0
source

Source: https://habr.com/ru/post/946334/


All Articles