如何在 JavaFX Cell 中绑定数据
How to bind data in a JavaFX Cell
由于 Cells 在 JavaFX 中被虚拟化,我不确定将数据绑定到哪里。
根据文档,电池可以随时回收(重新使用)。
目前我的代码看起来像这样 (1):
@Override
protected void updateItem(Object value, boolean empty) {
super.updateItem(value, empty);
if (empty || value == null) {
setGraphic(null);
return;
}
if (!isBound) {
myLabel.textProperty().bind(value.myProperty());
isBound = true;
}
//no unbinding?
}
但是我不知道什么时候解绑属性。
所以我认为在构造函数 (2):
中执行此操作可能更好
itemProperty().addListener((list, oldValue, newValue) -> {
if(newValue != null) {
myLabel.textProperty().bind(newValue.myProperty());
}
if(oldValue != null) {
myLabel.textProperty.unbind();
}
});
但我注意到 itemProperty() 的 ChangeListener 被调用的次数比真正需要的次数多,因此它会消耗更多的性能。
如果我是对的,我有时会用解决方案 (1) 得到错误的绑定项目。
你会如何处理?
编辑 - 想法:(3)
private Object boundObj;
@Override
protected void updateItem(Object value, boolean empty) {
super.updateItem(value, empty);
if (empty) {
setGraphic(null);
return;
}
if (boundObj != null && value != boundObj) {
myLabel.textProperty().unbind();
boundObj = null;
}
else {
myLabel.textProperty().bind(value.topicProperty());
boundObj = value;
}
setGraphic(gridPane);
}
解决方案 2(更新与侦听器的绑定)通常是执行此操作的正确方法。该项目可能会经常更改(尤其是当用户滚动时),并且您需要在发生这种情况时更改绑定。
如果您担心性能(我认为您不必担心),ChangeListener
仅在项目实际更改时才 调用。 updateItem(...)
方法会在项目更改时调用,但可能会更频繁地调用。因此,观察 itemProperty
在最坏情况下的效率不低于 updateItem(...)
方法。
无论如何,您不太可能在此处看到性能问题。您所要做的就是更新标签的文本(无论您如何接近它,当项目发生变化时您都需要这样做),然后绑定将注册一个侦听器,这是一个轻量级操作。
在此特定示例中,您可以通过无条件调用 unbind()
来简化它。如果 属性 未绑定,则此 has no effect。所以你可以这样做:
@Override
protected void updateItem(Object value, boolean empty) {
super.updateItem(value, empty);
myLabel.textProperty().unbind();
if (empty || value == null) {
setGraphic(null);
} else {
setGraphic(myLabel);
myLabel.textProperty().bind(value.myProperty());
}
}
但这并不能推广到其他场景,例如,如果您不是绑定标签,而是双向绑定 TextField
的 textProperty
。
您通常可以在 updateItem(...)
方法中执行此操作,注意如果您在调用 super.updateItem(...)
之前调用它,getItem()
将 return 旧项目:
@Override
protected void updateItem(Object value, boolean empty) {
Object oldItem = getItem();
if (oldItem != null) {
myLabel.textProperty().unbind();
}
super.updateItem(value, empty);
if (empty || value == null) {
setGraphic(null);
} else {
setGraphic(myLabel);
myLabel.textProperty().bind(value.myProperty());
}
}
我不太喜欢这个解决方案,因为它有点依赖于库实现(getItem()
return在此上下文中使用旧项目没有记录行为)。此外,它的效率可能较低,因为更改侦听器 仅在项目更改时调用 ; updateItem(...)
方法没有这样的保证。
请注意,如果您使用推荐的方法并观察 itemProperty
,您通常可以完全不用子class单元格 class 就可以逃脱:
myListView.setCellFactory(listView -> {
ListCell<SomeType> cell = new ListCell<>(); // default implementation
Label myLabel = new Label();
cell.itemProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue != null) {
myLabel.textProperty().unbind();
}
if (newValue == null) {
setGraphic(null);
} else {
myLabel.bind(newValue.myProperty());
setGraphic(myLabel);
}
});
return cell ;
});
(这很符合我自己的编程风格,我尽量避免不必要的继承。显然这只是个人品味的问题。)
如果单元格从非空(即它有一个非空项目,标签的文本绑定到该项目)变为空,您的第三个选项将失败,因为在这种情况下您无法取消绑定。您也许可以重新调整逻辑来解决这个问题,但看起来您只是让它变得过于复杂。使用更改侦听器是执行此操作的方法。
我刚遇到类似的问题,这里的答案给了我一些指导,但我认为解决方案应该有所不同。
来自 ListView javadoc:
The elements of the ListView are contained within the items ObservableList. This ObservableList is automatically observed by the ListView, such that any changes that occur inside the ObservableList will be automatically shown in the ListView itself.
因此您不需要将单元格元素绑定到基础项,JavaFX 会为您完成。这样的事情会做:
@Override
protected void updateItem(MyData value, boolean empty) {
super.updateItem(value, empty);
if (value == null || empty) {
setGraphic(null);
} else {
setGraphic(myGraphic);
myLabel.textProperty().set(value.getTextProperty().get());
}
}
通过这种方式,您在某种程度上修复了从模型传播到图形的绑定。如果您的 MyItem 中的基本 属性 发生更改,您仍然需要确保通知列表。为此,您使用带有 extractor 的可观察列表。
如果您希望在视图中所做的某些更改反映在模型中,您只需设置您选择的侦听器即可。您可以在可调用的构造函数或单元工厂中执行此操作。
MyCell extends ListCell<MyData> {
TextField myText = new TextField();
public MyCell(){
myText.textProperty().addListener((observable,oldValue,newValue) -> {
if (newValue != null) getItem().setName(newValue);
});
}
}
这样您就可以在您的域旁边表达您的业务逻辑,并且让单元格只处理模型和视图之间的映射以及用户操作和程序逻辑之间的映射。
我已尝试按照 James_D 的建议进行绑定,但使用双向绑定。结果很糟糕。当对 ObservableList 进行更改时,获取项目属性的单元格随机更改为 null。简单的绑定工作,但在我看来是多余的。您已经注册了一个侦听器,为什么要绑定到您已经在侦听的数据?只需在侦听器回调中进行更改即可。
由于 Cells 在 JavaFX 中被虚拟化,我不确定将数据绑定到哪里。
根据文档,电池可以随时回收(重新使用)。
目前我的代码看起来像这样 (1):
@Override
protected void updateItem(Object value, boolean empty) {
super.updateItem(value, empty);
if (empty || value == null) {
setGraphic(null);
return;
}
if (!isBound) {
myLabel.textProperty().bind(value.myProperty());
isBound = true;
}
//no unbinding?
}
但是我不知道什么时候解绑属性。
所以我认为在构造函数 (2):
中执行此操作可能更好itemProperty().addListener((list, oldValue, newValue) -> {
if(newValue != null) {
myLabel.textProperty().bind(newValue.myProperty());
}
if(oldValue != null) {
myLabel.textProperty.unbind();
}
});
但我注意到 itemProperty() 的 ChangeListener 被调用的次数比真正需要的次数多,因此它会消耗更多的性能。 如果我是对的,我有时会用解决方案 (1) 得到错误的绑定项目。 你会如何处理?
编辑 - 想法:(3)
private Object boundObj;
@Override
protected void updateItem(Object value, boolean empty) {
super.updateItem(value, empty);
if (empty) {
setGraphic(null);
return;
}
if (boundObj != null && value != boundObj) {
myLabel.textProperty().unbind();
boundObj = null;
}
else {
myLabel.textProperty().bind(value.topicProperty());
boundObj = value;
}
setGraphic(gridPane);
}
解决方案 2(更新与侦听器的绑定)通常是执行此操作的正确方法。该项目可能会经常更改(尤其是当用户滚动时),并且您需要在发生这种情况时更改绑定。
如果您担心性能(我认为您不必担心),ChangeListener
仅在项目实际更改时才 调用。 updateItem(...)
方法会在项目更改时调用,但可能会更频繁地调用。因此,观察 itemProperty
在最坏情况下的效率不低于 updateItem(...)
方法。
无论如何,您不太可能在此处看到性能问题。您所要做的就是更新标签的文本(无论您如何接近它,当项目发生变化时您都需要这样做),然后绑定将注册一个侦听器,这是一个轻量级操作。
在此特定示例中,您可以通过无条件调用 unbind()
来简化它。如果 属性 未绑定,则此 has no effect。所以你可以这样做:
@Override
protected void updateItem(Object value, boolean empty) {
super.updateItem(value, empty);
myLabel.textProperty().unbind();
if (empty || value == null) {
setGraphic(null);
} else {
setGraphic(myLabel);
myLabel.textProperty().bind(value.myProperty());
}
}
但这并不能推广到其他场景,例如,如果您不是绑定标签,而是双向绑定 TextField
的 textProperty
。
您通常可以在 updateItem(...)
方法中执行此操作,注意如果您在调用 super.updateItem(...)
之前调用它,getItem()
将 return 旧项目:
@Override
protected void updateItem(Object value, boolean empty) {
Object oldItem = getItem();
if (oldItem != null) {
myLabel.textProperty().unbind();
}
super.updateItem(value, empty);
if (empty || value == null) {
setGraphic(null);
} else {
setGraphic(myLabel);
myLabel.textProperty().bind(value.myProperty());
}
}
我不太喜欢这个解决方案,因为它有点依赖于库实现(getItem()
return在此上下文中使用旧项目没有记录行为)。此外,它的效率可能较低,因为更改侦听器 仅在项目更改时调用 ; updateItem(...)
方法没有这样的保证。
请注意,如果您使用推荐的方法并观察 itemProperty
,您通常可以完全不用子class单元格 class 就可以逃脱:
myListView.setCellFactory(listView -> {
ListCell<SomeType> cell = new ListCell<>(); // default implementation
Label myLabel = new Label();
cell.itemProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue != null) {
myLabel.textProperty().unbind();
}
if (newValue == null) {
setGraphic(null);
} else {
myLabel.bind(newValue.myProperty());
setGraphic(myLabel);
}
});
return cell ;
});
(这很符合我自己的编程风格,我尽量避免不必要的继承。显然这只是个人品味的问题。)
如果单元格从非空(即它有一个非空项目,标签的文本绑定到该项目)变为空,您的第三个选项将失败,因为在这种情况下您无法取消绑定。您也许可以重新调整逻辑来解决这个问题,但看起来您只是让它变得过于复杂。使用更改侦听器是执行此操作的方法。
我刚遇到类似的问题,这里的答案给了我一些指导,但我认为解决方案应该有所不同。
来自 ListView javadoc:
The elements of the ListView are contained within the items ObservableList. This ObservableList is automatically observed by the ListView, such that any changes that occur inside the ObservableList will be automatically shown in the ListView itself.
因此您不需要将单元格元素绑定到基础项,JavaFX 会为您完成。这样的事情会做:
@Override
protected void updateItem(MyData value, boolean empty) {
super.updateItem(value, empty);
if (value == null || empty) {
setGraphic(null);
} else {
setGraphic(myGraphic);
myLabel.textProperty().set(value.getTextProperty().get());
}
}
通过这种方式,您在某种程度上修复了从模型传播到图形的绑定。如果您的 MyItem 中的基本 属性 发生更改,您仍然需要确保通知列表。为此,您使用带有 extractor 的可观察列表。
如果您希望在视图中所做的某些更改反映在模型中,您只需设置您选择的侦听器即可。您可以在可调用的构造函数或单元工厂中执行此操作。
MyCell extends ListCell<MyData> {
TextField myText = new TextField();
public MyCell(){
myText.textProperty().addListener((observable,oldValue,newValue) -> {
if (newValue != null) getItem().setName(newValue);
});
}
}
这样您就可以在您的域旁边表达您的业务逻辑,并且让单元格只处理模型和视图之间的映射以及用户操作和程序逻辑之间的映射。
我已尝试按照 James_D 的建议进行绑定,但使用双向绑定。结果很糟糕。当对 ObservableList 进行更改时,获取项目属性的单元格随机更改为 null。简单的绑定工作,但在我看来是多余的。您已经注册了一个侦听器,为什么要绑定到您已经在侦听的数据?只需在侦听器回调中进行更改即可。