JavaFXListView自定义单元格工厂为可观察列表中的第一个条目创建两个对象

JavaFXListView自定义单元格工厂为可观察列表中的第一个条目创建两个对象,listview,javafx,Listview,Javafx,我正在学习列表视图的自定义单元格工厂,我有一个问题。问题是,对于items的第一个条目,单元格工厂创建了两个单元格,这只是第一个条目,我不知道为什么,可能是我做错了什么。如果你指出我做错了什么,我将不胜感激 这是我的主要课程: public class TestMain3 extends Application { private ObjectProperty<String> field; public TestMain3() { } @Override public void

我正在学习列表视图的自定义单元格工厂,我有一个问题。问题是,对于items的第一个条目,单元格工厂创建了两个单元格,这只是第一个条目,我不知道为什么,可能是我做错了什么。如果你指出我做错了什么,我将不胜感激

这是我的主要课程:

public class TestMain3 extends Application {
private ObjectProperty<String> field;

public TestMain3() {

}

@Override
public void start(Stage stage) throws Exception {
    ObservableList<TestMain3> observableList = FXCollections.observableArrayList();
    observableList.addAll(new TestMain3("w"), new TestMain3("y"), new TestMain3("u"), new TestMain3("a"), new TestMain3("a"), new TestMain3("a"));

    System.out.println(observableList.toString() + " observable list");
    ObservableList<TestCell> testCells = FXCollections.observableArrayList();
    ListView<TestMain3> listView = new ListView<>();
    listView.setCellFactory(new Callback<ListView<TestMain3>, ListCell<TestMain3>>() {
        @Override
        public ListCell<TestMain3> call(ListView<TestMain3> param) {
            TestCell testCell = new TestCell();
            testCells.add(testCell);
            return testCell;
        }
    });
    listView.setItems(observableList);

    VBox vBox = new VBox();
    vBox.getChildren().add(listView);
    Button button = new Button("ADD");
    button.setOnMouseClicked(new EventHandler<MouseEvent>() {
        @Override
        public void handle(MouseEvent event) {
        }
    });
    button.setAlignment(Pos.CENTER_RIGHT);
    vBox.getChildren().add(button);
    Scene scene = new Scene(vBox, 600, 600);
    stage.setScene(scene);
    stage.show();
    System.out.println(testCells.toString());
}

public TestMain3(String t) {
    field = new SimpleObjectProperty<>();
    field.set(t);
}

public static void main(String[] args) {
    launch(args);
}

public String getField() {
    return field.get();
}

public ObjectProperty fieldProperty() {
    return field;
}

public void setField(String field) {
    this.field.set(field);
}

@Override
public boolean equals(Object testMain3) {
    if (testMain3 == null) {
        return false;
    }
    TestMain3 testMain31 = (TestMain3) testMain3;
    return getField().equalsIgnoreCase(testMain31.getField());
}

@Override
public String toString() {
    return field.getValue().toString();
}
}
正如你们在我的可观察列表中看到的,我只有一个W,然而listview创建了两个W,即使它没有显示在listview中,它也会产生一些问题

更新

当列表被重用时,我确实会删除侦听器

@Override
protected void updateItem(Contact contact, boolean empty) {
    super.updateItem(contact, empty);
    if (empty || contact == null) {
        setText(null);
        setGraphic(null);
        setContextMenu(null);
    } else {
         registerListeners(contact);
        .....
     }
}
registerListeners方法

private void registerListeners(Contact contact) {
   if (person != null) {    
     person.getChat().newMessagesCountProperty().
            removeListener(newMessagesChangeListener);
     person.getChat().getMessages().
            removeListener(chatMessagesListChangeListener);
     onlineIconView.visibleProperty().unbind();
   }
   person = contact;
   onlineIconView.visibleProperty().bind(contact.onlineProperty());               
   person.getChat().newMessagesCountProperty().
           addListener(newMessagesChangeListener);
   person.getChat().getMessages().
           addListener(chatMessagesListChangeListener);
}
我正在创建一个聊天应用程序,问题是当listview中的第一个人收到新消息时,其单元格应该更新,显示新消息的图标和新消息的数量,但是其侦听器会被调用两次,因此看起来用户收到了两条新消息,而实际上他只收到了一条。 在第一次接触之后,接触没有问题

更新2
现在,在
updateItem()
中,如果它是第一个非空单元格,我只返回nothing,因此它解决了我的问题。我认为这是一个bug。

这里没有什么问题

单元格工厂及其创建的
ListCell
的唯一职责是创建足够的单元格来填充
ListView
,并在单元格显示的项目发生更改时调用
updateItem(…)
。无法保证实际创建了多少个单元格,以及对每个单元格调用了多少次
updateItem(…)

由于您观察到只有一个单元格显示,且其文本为
“w”
,因此单元格工厂和单元格工作正常

您看到使用字段为
“w”
的参数调用
updateItem(…)
两次,这只是您正在使用的JavaFX版本中实现单元工厂机制方式的一个副作用。您可能会在其他版本中观察到不同的行为

关于这一点,您真正需要知道的只有:

  • 不要假设特定单元格上调用了多少次
    updateItem(…)
    。特别是,列表视图中的项目与显示的单元格之间没有一对一的对应关系,因此将多次调用
    updateItem(…)
    以重用单元格(例如在滚动期间)
  • 很少创建单元格(通常,仅当第一次显示
    ListView
    时,也可能是当
    ListView
    高度增加时)
  • updateItem(…)
    可能会被频繁调用(例如,当用户在
    列表视图中滚动时)。因此,不要在
    updateItem(…)

我知道cell factory创建了它所需要的任意多个单元,并且这些单元被虚拟化和重用(通过在每个单元上调用
updateItem(…)
),但是从输出中可以看到,它创建了两个不同的单元,并使用相同的参数(TestMain3(“w”)调用了这两个单元
updateItem()
)。所以我有7个单元格,它们的值不是空的,而在可观察列表中我只有6个是的,我看到了。也许这很奇怪,但它并没有违反任何声明的功能。实际问题是什么?在
updateItem(…)
的开始,我将一些监听器绑定到我的对象(当然,如果它不为null),基于这些监听器,它应该更改单元格外观(ui),在我的情况下,我认为前两个单元格重叠或类似的内容,并且我没有得到想要的ui更改。如果你不删除监听器,你将有一个bug,因为在重复使用单元后,单元将监听多个项目。发布一些代码。你可以使用带有。这样,当您指定的任何属性发生更改时,都会调用
updateItem(…)
方法,您根本不需要管理侦听器。
@Override
protected void updateItem(Contact contact, boolean empty) {
    super.updateItem(contact, empty);
    if (empty || contact == null) {
        setText(null);
        setGraphic(null);
        setContextMenu(null);
    } else {
         registerListeners(contact);
        .....
     }
}
private void registerListeners(Contact contact) {
   if (person != null) {    
     person.getChat().newMessagesCountProperty().
            removeListener(newMessagesChangeListener);
     person.getChat().getMessages().
            removeListener(chatMessagesListChangeListener);
     onlineIconView.visibleProperty().unbind();
   }
   person = contact;
   onlineIconView.visibleProperty().bind(contact.onlineProperty());               
   person.getChat().newMessagesCountProperty().
           addListener(newMessagesChangeListener);
   person.getChat().getMessages().
           addListener(chatMessagesListChangeListener);
}