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