如何绑定JavaFX Cell中的数据

时间:2015-02-16 19:40:02

标签: java javafx javafx-8

由于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 个答案:

答案 0 :(得分:4)

解决方案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());
    }
}

但是,这并没有概括为其他方案,例如,如果您不是绑定标签,而是双向绑定textProperty的{​​{1}}。

您可以在TextField方法中执行此操作,注意updateItem(...)如果在调用getItem()之前调用旧项目将会返回旧项目:

super.updateItem(...)

我更喜欢这个解决方案,因为它依赖于库实现(@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()方法做出此类保证。

请注意,如果您使用推荐的方法并观察updateItem(...),您可以经常离开,而无需对单元格类进行子类化:

itemProperty

(这非常适合我自己的编程风格,我试图避免不必要的继承。显然这只是个人品味的问题。)

如果单元格从非空(即它有一个非空项目,标签的文本被绑定)变为空,则第三个选项失败,因为在这种情况下你无法取消绑定。你可能能够重新设计逻辑以解决这个问题,但似乎你只是让它变得过于复杂。使用更改侦听器是实现此目的的方法。

答案 1 :(得分:1)

我刚遇到类似的问题,这里的答案给了我一些指导,但我认为解决方案应该有所不同。

来自ListView javadoc:

  

ListView的元素包含在ObservableList项中。 ListView会自动观察此ObservableList,这样ObservableList中发生的任何更改都将自动显示在ListView本身中。

因此,您不需要将单元格元素绑定到基础项目,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。简单的绑定工作,但在我看来是多余的。您已经注册了一个监听器,为什么要绑定到您已经收听的数据?只需在侦听器回调中进行更改。