JavaFX 2.2: как заставить перерисовку / обновление ListView

у меня есть элемент управления ListView в модальном диалоговом окне JavaFX 2.

этот ListView отображает экземпляры DXAlias, ListCells для которых производятся фабрикой ячеек. Главное, что делают заводские объекты, - это исследуют данные свойства UserData ListView и сравнивают их с элементом, соответствующим ListCell. Если они одинаковы, содержимое ListCell отображается красным цветом, в противном случае-черным. Я делаю это, чтобы указать, какой из элементов в ListView в настоящее время выбрано как "default". Вот мой класс фабрики ListCell, чтобы вы могли видеть, что я имею в виду:

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); }
    }};
}

у меня есть обработчик кнопок под названием "handleAliasDefault ()", который делает выбранный элемент в ListView новым по умолчанию, беря выбранный экземпляр DXAlias и сохраняя его в ListView: lsvAlias.setUserData (выбранные DXAlias). Вот код обработчика:

// 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));
        }
    }

потому что изменение, которое было сделано в ответ на нажатие на кнопку Установить по умолчанию чтобы изменить UserData() ListView без каких-либо изменений в резервном ObservableList, список неправильно указывает новое значение по умолчанию.

есть ли способ заставить ListView повторно отображать его ячейки списка? Есть квадриллион вопросов от пользователей Android по этому вопросу, но, похоже, нет счастья для JavaFX. Возможно, мне придется сделать "бессмысленное изменение" в массиве поддержки, чтобы заставить перерисовку.

Я вижу, что это было предложено для JavaFX 2.1: JavaFX с ListView с освежающим

5 ответов


не уверен, что это работает в JavaFX 2.2, но это делает в JavaFX 8, и мне потребовалось некоторое время, чтобы понять. Вам нужно создать свой собственный ListViewSkin и добавить refresh методика:

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

этой updateItem без необходимости замены всей наблюдаемой коллекции.

кроме того, чтобы использовать новый скин, вам нужно создать его экземпляр и установить его на initialize метод контроллера в случае, если вы используете 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    

Я нашел это после отладки поведение аккордеона. Этот элемент управления обновляет ListView, который он содержит при каждом его развертывании.


для любого тела, которое заканчивается на этой странице:

правильный способ сделать это-предоставить вашему наблюдаемому списку обратный вызов "extractor". Это будет сигнализировать о списке (и ListView) любых изменений свойств.

пользовательский класс для отображения:

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());
    }
}

и ваш основной код:

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!!!

посмотреть (и подобные методы): https://docs.oracle.com/javafx/2/api/javafx/collections/FXCollections.html#observableArrayList(javafx.util.Callback)


на данный момент я могу заставить ListView перерисовать и правильно указать выбранное значение по умолчанию, используя следующий метод, называемый forceListRefreshOn (), в моем обработчике кнопок:

@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);
    }
}

вспомогательный метод просто заменяет ObservableList из ListView, а затем меняет его обратно, предположительно заставляя ListView обновлять свои ListCells:

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

Я не уверен, что мне чего-то не хватает, но сначала я попробовал решение Скотта, но потом я узнал, что есть уже реализованный метод refresh (), который сделал трюк для меня.

ListView<T> list;
list.refresh();

кажется, вам не нужно использовать метод updateItem. Что вам нужно-это сохранить индикатор текущей ячейки по умолчанию (которая сейчас находится в пользовательских данных), в OblectProperty. И добавьте прослушиватель в это свойство из каждой ячейки. При изменении значения переназначьте стиль.

но я думаю, такая привязка вызовет еще одну проблему-при прокрутке будут созданы новые ячейки, но старые не выйдут из привязки, что может вызвать утечку памяти. Поэтому вам нужно добавить прослушиватель при удалении ячейки с места происшествия. Я думаю, это можно сделать, добавив прослушиватель в parentProperty, когда он становится null - remove binding.

UI не должен быть вынужден обновляться. Он обновляется автоматически при изменении свойств / визуализации существующих узлов. Поэтому вам просто нужно обновить внешний вид / свойство существующих узлов (ячеек). И не забывать, что ячейки могут быть созданы массово, во время прокрутки/переориентации и т. д.