【问题标题】:JavaFX: Custom Equals Method Causes No Refresh in TableViewJavaFX:自定义 Equals 方法导致 TableView 中没有刷新
【发布时间】:2017-02-23 06:04:53
【问题描述】:

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

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

这适用于:

  • 初始列表人口
  • 重复集条目(未触发侦听器,这是预期行为)

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

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

知道发生了什么吗?为什么 TableView 上的监听器没有被后续的adds 触发?数据正在被放入 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 类:

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);
}
}

【问题讨论】:

  • 你能发布MarkStructureItem类吗?设置更改侦听器的onChanged 方法是否被调用以添加到集合中? (除此之外:您对泛型的使用存在严重缺陷,尽管这与实际问题完全不同。)
  • 将其添加到原始问题中。每次添加都会调用自定义 onChanged。刷新触发器似乎没有从 onChanged 中调用。我对正确使用泛型(PM 或诸如此类)的方向感兴趣......这是继承的代码,我们可以使它更好的任何方式都值得赞赏!谢谢!
  • return thisObj.getId() == this.getId(); 可能会导致问题,因为 getId() 返回一个对象。由于您在内部使用 IntegerProperty,因此 id 属性的类型实际上应该是 int,而不是 Integer,这将是解决问题的最佳方法。
  • 此外,最好遵循JavaFX Properties pattern,即将idProperty()marksProperty() 方法添加到MarkStructureItem 类中。话虽如此,我逐字测试了您的代码,它完全按预期工作......所以也许还有其他地方不正确。你能扩展成minimal reproducible example吗?您的处理程序方法也很奇怪,为什么要打开按钮的 id?无论如何,您应该(几乎总是)将该方法作为处理程序的一个控件。
  • 对于泛型,您应该拥有public class MarksUpdater implements SetChangeListener&lt;MarkStructureItem&gt;public void onChanged(Change&lt;? extends MarkStructureItem&gt; change) { ... }。然后只需将其实例化为new MarksUpdater(),即可移除change.getElementAdded() 上的演员表。

标签: java javafx actionlistener fxml


【解决方案1】:

问题 1:

您的 equals 实现不是 null-safe 并且您使用引用相等 (==) 而不是 equals 比较 Integers。只要您在缓存值范围内并使用自动装箱,这可能会起作用,但在该范围之外停止工作(此范围仅保证为 -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;
    }

【讨论】:

  • 所以,我终于想通了:这并不是人们建议的任何内容,但是通过您的所有反馈,我设法减少了问题。
  • (呃,过早的“Enter”键。)好吧,无论如何,问题在于用于测试集合中对象的唯一性的自定义“equals”方法。它表面上被实现是因为 ID 是唯一的,但 Marks 不是。这按预期工作:具有重复 ID 的项目在自定义“等于”方法之外没有任何业务逻辑的情况下被拒绝。但是,这也导致了问题,因为 JavaFX 也使用“等于”方法:更改标记而不更改 ID,JavaFX 没有看到该行是“不同的”(即使它是),所以它没有触发刷新.
猜你喜欢
  • 1970-01-01
  • 2017-12-13
  • 1970-01-01
  • 1970-01-01
  • 2020-01-21
  • 2019-08-12
  • 2017-05-24
  • 1970-01-01
  • 2018-02-14
相关资源
最近更新 更多