为什么 TableView 的更改侦听器会为 ObjectProperty<T> 与 JavaFX8 中的 TProperty 列提供不同的结果?
Why does a TableView's change listener give different results for ObjectProperty<T> vs TProperty columns in JavaFX8?
亲戚Java菜鸟问题
我有一个带提取器的 TableView 和一个添加到基础 ObservableList 的 ListChangeListener
。
如果我在数据模型中有一个 StringProperty
列,如果我双击该单元格然后在不进行任何更改的情况下按 ENTER 键,则更改侦听器不会检测到更改。不错。
但是,如果我将列定义为 ObjectProperty<String>
并双击然后按 ENTER,更改侦听器始终会检测到更改,即使 none 已经完成.
为什么会这样?从更改侦听器的角度来看,ObjectProperty<String>
和 StringProperty
之间有什么区别?
我已阅读 and 并认为我理解其中的差异。但我不明白为什么更改侦听器会针对 TProperty
/SimpleTProperty
和 ObjectProperty<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);
}
}
通过查看 StringPropertyBase
和 ObjectPropertyBase
的源代码可以看出区别——特别是它们的 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
class 使用 Object.equals
检查,而 ObjectPropertyBase
class 使用引用相等性 (==
/!=
)。
我无法肯定地回答为什么存在这种差异,但我可以大胆猜测:一个ObjectProperty
可以容纳任何东西 因此 Object.equals
有可能变得昂贵;例如当使用 List
或 Set
时。在编码 StringPropertyBase
时,我猜他们认为潜力不存在,String
平等的语义更重要,或两者兼而有之。他们为什么这样做可能有 more/better 原因,但由于我没有参与开发,所以我不知道它们。
有趣的是,如果你看看他们如何处理听众
(com.sun.javafx.binding.ExpressionHelper
) 您会看到他们使用 Object.equals
检查是否相等。只有在当前 ChangeListener
已注册时才会进行此相等性检查——可能是为了在没有 ChangeListener
时支持惰性求值。
如果新旧值是 equals
,则不会通知 ChangeListener
s。但是,这不会阻止 InvalidationListener
s 收到通知。因此,您的 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!
亲戚Java菜鸟问题
我有一个带提取器的 TableView 和一个添加到基础 ObservableList 的 ListChangeListener
。
如果我在数据模型中有一个 StringProperty
列,如果我双击该单元格然后在不进行任何更改的情况下按 ENTER 键,则更改侦听器不会检测到更改。不错。
但是,如果我将列定义为 ObjectProperty<String>
并双击然后按 ENTER,更改侦听器始终会检测到更改,即使 none 已经完成.
为什么会这样?从更改侦听器的角度来看,ObjectProperty<String>
和 StringProperty
之间有什么区别?
我已阅读 TProperty
/SimpleTProperty
和 ObjectProperty<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);
}
}
通过查看 StringPropertyBase
和 ObjectPropertyBase
的源代码可以看出区别——特别是它们的 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
class 使用 Object.equals
检查,而 ObjectPropertyBase
class 使用引用相等性 (==
/!=
)。
我无法肯定地回答为什么存在这种差异,但我可以大胆猜测:一个ObjectProperty
可以容纳任何东西 因此 Object.equals
有可能变得昂贵;例如当使用 List
或 Set
时。在编码 StringPropertyBase
时,我猜他们认为潜力不存在,String
平等的语义更重要,或两者兼而有之。他们为什么这样做可能有 more/better 原因,但由于我没有参与开发,所以我不知道它们。
有趣的是,如果你看看他们如何处理听众
(com.sun.javafx.binding.ExpressionHelper
) 您会看到他们使用 Object.equals
检查是否相等。只有在当前 ChangeListener
已注册时才会进行此相等性检查——可能是为了在没有 ChangeListener
时支持惰性求值。
如果新旧值是 equals
,则不会通知 ChangeListener
s。但是,这不会阻止 InvalidationListener
s 收到通知。因此,您的 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!