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;
}
情况如下:我有一个具有侦听器的 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;
}