JavaFX:自定义等于方法导致 TableView 不刷新

JavaFX: Custom Equals Method Causes No Refresh in TableView

情况如下:我有一个具有侦听器的 ObservableSet(因为我的数据的 ID 字段必须是唯一的)。该侦听器更新 ObservableList。 ObservableList 反过来又被 TableView 监听。 (根据评论,这都是必需的,因为 ObservableSet 不能用于支持 JavaFX 中的 TableView。)

但是,我们发现对 Set 执行多个 add 操作不会触发 TableView 的刷新。

这适用于:

但是,编辑值时,TableView 只会在只有一个 add 语句时触发(在下面的示例中,注释掉 markStructure.add(new MarkStructureItem(2, 15)); 可以正常工作,但是你只有一个单值)。如果有多个,TableView 不会刷新。

这可以通过在侦听器 class (tblTable.refresh()) 末尾添加手动刷新来解决。这实际上是它到目前为止的运作方式,但现在的开发需要删除它。

知道发生了什么事吗?为什么 TableView 上的侦听器没有被后续的 add 触发?数据被放入 Set 和 List 中,这已经确定了;它只是没有触发刷新。

代码示例(不是生产,而是我放在一起测试并获得答案的 POC):

public class TestController implements Initializable {
ObservableSet<MarkStructureItem> markStructure = FXCollections.observableSet();
ObservableList<MarkStructureItem> listMarkStructure = FXCollections.observableArrayList();

@FXML
private Pane root;

@FXML
private TableView<MarkStructureItem> tblTable;

@FXML
private TableColumn<MarkStructureItem, Integer> col1;

@FXML
private TableColumn<MarkStructureItem, Integer> col2;

@FXML
private Button btnButton;

private void resetAll() {
    markStructure.clear();
    markStructure.add(new MarkStructureItem(1, 25));
    markStructure.add(new MarkStructureItem(2, 15));
}

@FXML
private void handleButtonAction(ActionEvent event) throws IOException {
    Object source = event.getSource();
    Button btnSource = (Button) source;
    Stage stage = (Stage) root.getScene().getWindow();

    switch (btnSource.getId()) {
        case "btnButton": {
            resetAll();
            break;
        }
    }
}

class MarksUpdater<MarkStructureItem extends configurationeditor.MarkStructureItem> implements SetChangeListener {
    @Override
    public void onChanged(Change change) {
        if (change.wasRemoved()) {
            listMarkStructure.remove(change.getElementRemoved());
        } else if (change.wasAdded()) {
            MarkStructureItem newMarks = (MarkStructureItem) change.getElementAdded();
            listMarkStructure.add(newMarks);
        }
    }
}

@Override
public void initialize(URL url, ResourceBundle rb) {
    markStructure.addListener(new MarksUpdater<MarkStructureItem>());

    col1.setCellValueFactory(
            new PropertyValueFactory<MarkStructureItem, Integer>("id")
    );
    col2.setCellValueFactory(
            new PropertyValueFactory<MarkStructureItem, Integer>("marks")
    );
    col2.setCellFactory(TextFieldTableCell.forTableColumn(new IntegerStringConverter()));
    col2.setOnEditCommit(
            new EventHandler<CellEditEvent<MarkStructureItem, Integer>>() {
                @Override
                public void handle(CellEditEvent<MarkStructureItem, Integer> t) {
                    ((MarkStructureItem) t.getTableView().getItems().get(
                            t.getTablePosition().getRow())
                    ).setMarks(t.getNewValue());
                }
            }
    );
    tblTable.setItems(listMarkStructure);

    resetAll();
}
}

markStructure class:

public class MarkStructureItem {
final SimpleIntegerProperty marks;
final SimpleIntegerProperty id;

@Override
public int hashCode() {
    return this.getId().hashCode();
}

@Override
public boolean equals(Object obj) {
    if (obj.getClass() == this.getClass()) {
        MarkStructureItem thisObj = (MarkStructureItem) obj;
        return thisObj.getId() == this.getId();
    }
    return false;
}

public MarkStructureItem(Integer finishPosition, Integer marks) {
    this.marks = new SimpleIntegerProperty(marks);
    this.id = new SimpleIntegerProperty(finishPosition);
}

public Integer getId() {
    return id.get();
}

public void setMarks(Integer value) {
    marks.set(value);
}

public Integer getMarks() {
    return marks.get();
}

public void setId(Integer value) {
    id.set(value);
}
}

问题 1:

您的 equals 实现不是 null 安全的,并且您使用引用相等性 (==) 而不是 equals 来比较 Integer .只要您在缓存值范围内并使用自动装箱,这可能会起作用,但在该范围外停止工作(该范围仅保证为 -128 到 127,请参阅 Integer.valueOf(int))。
由于您在 Set 中使用 Object,我建议无论如何都使 id 不可修改。否则,您需要先从集合中删除该值,然后再对其进行修改并将其添加回 Set(哈希码发生变化),这会弄乱列表中的顺序,除非您暂时阻止 SetChangeListener来自更新列表。
同样使用原始类型将防止 ==.

出现问题

问题 2:

要使 marks 属性 中的任何更改在 TableView 中可见,您需要提供返回 属性 本身的 marksProperty() 方法.这对于 TableView 监听 属性.

的变化是必要的
public class MarkStructureItem {

    final SimpleIntegerProperty marks;
    final int id;

    @Override
    public int hashCode() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        return obj != null
                  && obj.getClass() == this.getClass()
                  && this.id == ((MarkStructureItem) obj).id; // primitive type can be compared using ==
                  // && this.getId().equals(((MarkStructureItem) obj).getId()); // alternative for non-primitive types
    }

    public MarkStructureItem(int finishPosition, Integer marks) {
        this.marks = new SimpleIntegerProperty(marks);
        this.id = finishPosition;
    }

    public int getId() {
        return id;
    }

    public IntegerProperty marksProperty() {
        return marks;
    }