JavaFX 2.2:如何强制重新绘制/更新ListView



我在JavaFX 2模态对话框窗口中有一个ListView控件。

这个ListView显示DXAlias实例,listcell是由单元格工厂制造的。工厂对象所做的主要工作是检查ListView的UserData属性数据,并将其与ListCell对应的项进行比较。如果它们相同,则ListCell的内容以红色呈现,否则为黑色。我这样做是为了指示ListView中的哪个项当前被选为"默认"。这是我的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()"的按钮处理程序,它通过选取所选的DXAlias实例并将其存储到ListView: lsvAlias中,使ListView中选定的项目成为新的默认值。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));
        }
    }

因为响应点击设置默认按钮所做的更改是改变ListView的UserData(),而不改变支持的ObservableList,所以列表不能正确指示新的默认值。

是否有办法强制ListView重新渲染它的ListCells?Android用户对这个问题提出了千万亿的问题,但似乎没有人对JavaFX感到满意。我可能不得不对后备数组做一个"无意义的改变"来强制重绘。

我看到这是JavaFX 2.1的要求:JavaFX ListView刷新

对于本页上的任何其他主体:

正确的做法是为你的可观察列表提供一个"extractor" Callback。这将通知列表(和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)

不确定这在JavaFX 2.2中是否有效,但它在JavaFX 8中确实有效,我花了一段时间才弄清楚。您需要创建自己的ListViewSkin,并添加一个refresh方法,如

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

这将调用updateItem而不必替换整个Observable集合。

此外,要使用新的皮肤,您需要实例化它并将其设置在控制器的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    

我在调试Accordion的行为后发现了这个问题。这个控件在每次展开ListView时刷新它所包含的ListView。

我不确定我是否错过了什么,但起初我尝试了scott的解决方案,但后来我发现有一个已经实现的refresh()方法,它为我做了这个技巧。

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

现在,我能够让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);
    }
}

helper方法只是从ListView交换出ObservableList,然后交换回来,大概迫使ListView更新它的ListCells:

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

似乎不需要使用updateItem方法。您需要的是将当前默认单元格的指示符(现在在用户数据中)存储到obectproperty中。并在每个单元格的此属性上添加一个侦听器。当值改变时,重新分配样式。

但是我认为,这样的绑定会调用另一个问题-在滚动时,将创建新的单元格,但旧的单元格不会离开绑定,这可能导致内存泄漏。所以你需要在移除场景的cell上添加监听器。我认为,这可以通过在parentProperty上添加一个侦听器来完成,当它变为null时-删除绑定。

UI不应该被强制更新。当现有节点的属性/呈现发生变化时,它会自动更新。因此,您只需要更新现有节点(单元)的外观/属性。不要忘记,单元格可以在滚动/重新渲染等过程中大量创建。

最新更新