为什么TableView';s change listener为ObjectProperty提供不同的结果<;T>;JavaFX8中的VSTProperty列?
一个相对的Java新手问题 我有一个带有提取器的TableView和一个添加到基础ObservableList的为什么TableView';s change listener为ObjectProperty提供不同的结果<;T>;JavaFX8中的VSTProperty列?,javafx,tableview,javafx-8,propertychangelistener,Javafx,Tableview,Javafx 8,Propertychangelistener,一个相对的Java新手问题 我有一个带有提取器的TableView和一个添加到基础ObservableList的ListChangeListener 如果我在数据模型中有一个StringProperty列,那么如果我双击单元格,然后按ENTER键而不做任何更改,则更改侦听器不会检测到更改。那很好 但是,如果我将列定义为ObjectProperty,然后双击并单击ENTER,则即使未进行任何更改,更改侦听器也会始终检测到更改 为什么会这样?从更改侦听器的角度来看,ObjectProperty和St
ListChangeListener
如果我在数据模型中有一个StringProperty
列,那么如果我双击单元格,然后按ENTER键而不做任何更改,则更改侦听器不会检测到更改。那很好
但是,如果我将列定义为ObjectProperty
,然后双击并单击ENTER,则即使未进行任何更改,更改侦听器也会始终检测到更改
为什么会这样?从更改侦听器的角度来看,ObjectProperty
和StringProperty
之间有什么区别
我读过并认为我理解这些差异。但是我不明白为什么更改侦听器对TProperty
/SimpleTProperty
和ObjectProperty
给出不同的结果
如果有帮助的话,这里有一个MVCE来解释我的一些荒谬的案例。实际上,我正试图让一个更改侦听器为BigDecimal
和LocalDate
列工作,并且已经在上面停留了5天。如果我能理解为什么变更监听器会给出不同的结果,我就可以让我的代码正常工作
我正在使用JavaFX8(JDK1.8.0µ)、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);
}
}
包测试17;
导入javafx.application.application;
导入静态javafx.application.application.launch;
导入javafx.beans.Observable;
导入javafx.beans.property.ObjectProperty;
导入javafx.beans.property.SimpleObject属性;
导入javafx.beans.property.SimpleStringProperty;
导入javafx.beans.property.StringProperty;
导入javafx.collections.FXCollections;
导入javafx.collections.ListChangeListener;
导入javafx.collections.ObservableList;
导入javafx.scene.Parent;
导入javafx.scene.scene;
导入javafx.scene.control.TableColumn;
导入javafx.scene.control.TableView;
导入javafx.scene.control.cell.TextFieldTableCell;
导入javafx.scene.layout.BorderPane;
导入javafx.stage.stage;
导入javafx.util.converter.DefaultStringConverter;
公共类Test17扩展了应用程序{
私有父createContent(){
ObservableList olTestModel=FXCollections.observableArrayList(testmodel->new Observable[]{
testmodel.strProperty(),
testmodel.strObjectProperty()
});
添加(新的测试模型(“A”,“A”));
添加(新的测试模型(“B”、“B”);
olTestModel.addListener((ListChangeListener.Change通过查看StringPropertyBase
和ObjectPropertyBase
的源代码可以看出差异,特别是它们的设置方法
StringPropertyBase
ObjectPropertyBase
请注意它们在检查新值是否等于旧值方面的差异。StringPropertyBase
类使用Object.equals
进行检查,而ObjectPropertyBase
类使用引用相等(==
/!=
)
我不能肯定地回答为什么会存在这种差异,但我可以冒险猜测:一个ObjectProperty
可以容纳任何东西,因此Object.equals
可能很昂贵;比如当使用List
或Set
时。当编码StringPropertyBase
时,我想他们决定tential不在那里,因为String
equality的语义更重要,或者两者都重要。他们这样做可能有更多/更好的原因,但由于我没有参与开发,我不知道他们
有趣的是,如果你看看他们是如何处理听众的
(com.sun.javafx.binding.ExpressionHelper
)您将看到它们使用Object.equals
检查相等性。仅当当前注册了ChangeListener
s,以便在没有ChangeListener
s的情况下支持延迟计算时,才会发生此相等性检查
如果新值和旧值等于则不会通知ChangeListener
s。但是,这不会阻止通知InvalizationListener
s。因此,您的ObservableList
将触发更新更改,因为该机制基于InvalizationListener
s而不是ChangeLi斯坦纳
s
以下是相关的源代码:
ExpressionHelper$Generic.fireValueChangedEvent
有趣的是…似乎没有指明身份或平等的变化是否构成了ObservalEvalue值的变化(至少我找不到任何东西)-并且实现没有指定它们是如何实现的。嗯…@Slaw非常感谢您的解释。至少我现在理解了为什么我无法让更改侦听器为bigdecimic
和LocalDate
列工作。我当前的解决方法是将它们定义为StringProperty
并处理在我的代码中使用它们,但我现在将尝试编写自己的“ObjectProperty”类。我怀疑这超出了我目前的Java技能,但没有任何冒险,没有任何收获。再次感谢。@kleopatra我也找不到明确的规范。不过,我注意到,更改似乎是基于equals
实现的,但无效似乎是通过引用相等实现的,除了StringPropertyBase
。可能更改的引用是无效的?它似乎与ObservalEvalue
的文档相符:“更改事件[…]值已更改。无效事件[…]值不再有效。此区别变得非常重要[…]因为对于延迟计算的值,在重新计算之前,不知道无效值是否真的发生了更改。”@GreenZebra我可以问一下有什么问题吗
@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();
}
}
@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();
}
}
@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!");
}
}