9

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

此 ListView 显示 DXAlias 实例,其 ListCells 由单元工厂制造。工厂对象所做的主要工作是检查 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 中,使 ListView 中的选定项成为新的默认值:lsvAlias.setUserData(selected 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 refresh

4

5 回答 5

15

对于在此页面上结束的任何其他正文:

这样做的正确方法是为您的可观察列表提供“提取器”回调。这将通知任何属性更改的列表(和 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)

于 2015-08-27T07:21:09.367 回答
13

不确定这是否适用于 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    

我在调试手风琴的行为后发现了这一点。每次展开它时,此控件都会刷新它包含的 ListView。

于 2014-09-21T17:46:11.687 回答
9

我不确定我是否遗漏了一些东西,但起初我尝试了 scottb 的解决方案,但后来我发现有一个已经实现的 refresh() 方法,它对我有用。

ListView<T> list;
list.refresh();
于 2018-06-17T22:03:31.263 回答
5

现在,我可以通过在我的按钮处理程序中使用名为 forceListRefreshOn() 的以下方法来重绘 ListView 并正确指示选定的默认值:

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

辅助方法只是从 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);
}
于 2013-06-02T16:39:13.257 回答
0

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

但我认为,这样的绑定会引发另一个问题——滚动时会创建新的单元格,但旧的单元格不会离开绑定,这会导致内存泄漏。因此,您需要在从场景中移除的单元格上添加侦听器。我认为,可以通过在 parentProperty 上添加一个侦听器来完成,当它变为空时 - 删除绑定。

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

于 2013-06-02T11:09:21.137 回答