gpt4 book ai didi

javafx - 为什么 TableView 的更改监听器对 JavaFX8 中的 ObjectProperty 与 TProperty 列给出不同的结果?

转载 作者:行者123 更新时间:2023-12-02 02:29:33 24 4
gpt4 key购买 nike

一个相对的 Java 新手问题。

我有一个带有提取器的 TableView 和一个 ListChangeListener添加到底层 ObservableList。

如果我有一个 StringProperty在数据模型中的列中,如果我双击该单元格然后按 Enter 键而不进行任何更改,则更改监听器不会检测到更改。这很好。

但是,如果我将该列定义为 ObjectProperty<String>双击然后按 ENTER 键,更改监听器始终会检测到更改,即使没有进行任何更改

为什么会发生这种情况? ObjectProperty<String> 和有什么不一样?和StringProperty从变更监听者的角度来看?

我已阅读 Difference between SimpleStringProperty and StringPropertyJavaFX SimpleObjectProperty<T> vs SimpleTProperty我想我理解其中的差异。但我不明白为什么更改监听器对 TProperty 给出不同的结果/SimpleTPropertyObjectProperty<T>

如果有帮助的话,这是一个针对我有点荒谬的案例的 MVCE。我实际上正在尝试让一个更改监听器为 BigDecimal 工作。和LocalDate列并已卡在其上 5 天。如果我能理解为什么更改监听器给出不同的结果,我也许能够让我的代码正常工作。

我正在使用 JavaFX8 (JDK1.8.0_181)、NetBeans 8.2 和 Scene Builder 8.3。

package test17;

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.converter.DefaultStringConverter;

public class Test17 extends Application {

private Parent createContent() {

ObservableList<TestModel> olTestModel = FXCollections.observableArrayList(testmodel -> new Observable[] {
testmodel.strProperty(),
testmodel.strObjectProperty()
});

olTestModel.add(new TestModel("A", "a"));
olTestModel.add(new TestModel("B", "b"));

olTestModel.addListener((ListChangeListener.Change<? extends TestModel > c) -> {
while (c.next()) {
if (c.wasUpdated()) {
System.out.println("===> wasUpdated() triggered");
}
}
});

TableView<TestModel> table = new TableView<>();

TableColumn<TestModel, String> strCol = new TableColumn<>("strCol");
strCol.setCellValueFactory(cellData -> cellData.getValue().strProperty());
strCol.setCellFactory(TextFieldTableCell.forTableColumn(new DefaultStringConverter()));
strCol.setEditable(true);
strCol.setPrefWidth(100);
strCol.setOnEditCommit((CellEditEvent<TestModel, String> t) -> {
((TestModel) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setStr(t.getNewValue());
});

TableColumn<TestModel, String> strObjectCol = new TableColumn<>("strObjectCol");
strObjectCol.setCellValueFactory(cellData -> cellData.getValue().strObjectProperty());
strObjectCol.setCellFactory(TextFieldTableCell.forTableColumn(new DefaultStringConverter()));
strObjectCol.setEditable(true);
strObjectCol.setPrefWidth(100);
strObjectCol.setOnEditCommit((CellEditEvent<TestModel, String> t) -> {
((TestModel) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setStrObject(t.getNewValue());
});

table.getColumns().addAll(strCol, strObjectCol);
table.setItems(olTestModel);
table.getSelectionModel().setCellSelectionEnabled(true);
table.setEditable(true);

BorderPane content = new BorderPane(table);
return content;
}

public class TestModel {

private StringProperty str;
private ObjectProperty<String> strObject;

public TestModel(
String str,
String strObject
) {
this.str = new SimpleStringProperty(str);
this.strObject = new SimpleObjectProperty(strObject);
}

public String getStr() {
return this.str.get();
}

public void setStr(String str) {
this.str.set(str);
}

public StringProperty strProperty() {
return this.str;
}

public String getStrObject() {
return this.strObject.get();
}

public void setStrObject(String strObject) {
this.strObject.set(strObject);
}

public ObjectProperty<String> strObjectProperty() {
return this.strObject;
}

}

@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle("Test");
stage.setWidth(350);
stage.show();
}

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

}

最佳答案

通过查看 StringPropertyBaseObjectPropertyBase 的源代码(具体来说,它们的 set 方法)可以看出差异。

StringPropertyBase

@Override
public void set(String newValue) {
if (isBound()) {
throw new java.lang.RuntimeException((getBean() != null && getName() != null ?
getBean().getClass().getSimpleName() + "." + getName() + " : ": "") + "A bound value cannot be set.");
}
if ((value == null)? newValue != null : !value.equals(newValue)) {
value = newValue;
markInvalid();
}
}

ObjectPropertyBase

@Override
public void set(T newValue) {
if (isBound()) {
throw new java.lang.RuntimeException((getBean() != null && getName() != null ?
getBean().getClass().getSimpleName() + "." + getName() + " : ": "") + "A bound value cannot be set.");
}
if (value != newValue) {
value = newValue;
markInvalid();
}
}

注意到他们检查新值是否等于旧值的方式不同吗? StringPropertyBase 类使用 Object.equals 进行检查,而 ObjectPropertyBase 类使用引用相等性 (==/!=)。

我无法回答为什么存在这种差异,但我可以大胆猜测:ObjectProperty 可以容纳任何东西,因此Object.equals 可能会很昂贵;例如使用 ListSet 时。当编码 StringPropertyBase 时,我猜他们认为潜力不存在,String 相等的语义更重要,或者两者兼而有之。他们这样做可能有更多/更好的原因,但由于我没有参与开发,所以我不知道它们。

<小时/>

有趣的是,如果你看看他们如何处理听众(com.sun.javafx.binding.ExpressionHelper) 您将看到它们使用 Object.equals 检查相等性。仅当当前注册了 ChangeListener 时,才会发生此相等性检查 - 可能是为了在没有 ChangeListener 时支持延迟计算。

如果新值和旧值等于,则不会通知ChangeListener。不过,这并不能阻止 InvalidationListener 收到通知。因此,您的 ObservableList 将触发更新更改,因为该机制基于 InvalidationListener 而不是 ChangeListener。

相关源代码如下:

ExpressionHelper$Generic.fireValueChangedEvent

@Override
protected void fireValueChangedEvent() {
final InvalidationListener[] curInvalidationList = invalidationListeners;
final int curInvalidationSize = invalidationSize;
final ChangeListener<? super T>[] curChangeList = changeListeners;
final int curChangeSize = changeSize;

try {
locked = true;
for (int i = 0; i < curInvalidationSize; i++) {
try {
curInvalidationList[i].invalidated(observable);
} catch (Exception e) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}
}
if (curChangeSize > 0) {
final T oldValue = currentValue;
currentValue = observable.getValue();
final boolean changed = (currentValue == null)? (oldValue != null) : !currentValue.equals(oldValue);
if (changed) {
for (int i = 0; i < curChangeSize; i++) {
try {
curChangeList[i].changed(observable, oldValue, currentValue);
} catch (Exception e) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}
}
}
}
} finally {
locked = false;
}
}

您可以在以下代码中看到此行为:

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;

public class Main {

public static void main(String[] args) {
ObjectProperty<String> property = new SimpleObjectProperty<>("Hello, World!");
property.addListener(obs -> System.out.printf("Property invalidated: %s%n", property.get()));
property.addListener((obs, ov, nv) -> System.out.printf("Property changed: %s -> %s%n", ov, nv));
property.get(); // ensure valid

property.set(new String("Hello, World!")); // must not use interned String
property.set("Goodbye, World!");
}

}

输出:

Property invalidated: Hello, World!
Property invalidated: Goodbye, World!
Property changed: Hello, World! -> Goodbye, World!

关于javafx - 为什么 TableView 的更改监听器对 JavaFX8 中的 ObjectProperty<T> 与 TProperty 列给出不同的结果?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52401862/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com