Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange TableView.itemsProperty Behavior?

Let's say I'll add a ChangeListener to a TableView's itemsProperty. When would the ChangeListener's changed method be called?

I tried adding to the empty List where the TableView's items points. The result - The ChangeListener's changed method didn't get called.

tableView.itemsProperty().addListener(new ChangeListener() {
    @Override
    public void changed(ObservableValue ov, Object t, Object t1) {
        System.out.println("Changed!");
    }
});

final ObservableList data = FXCollections.observableArrayList(new ArrayList());
data.clear();
//data.add(new Object()); don't call this yet
tableView.setItems(data);
data.clear();
data.add(new Object());
tableView.setItems(data);

However, I also tried adding to an empty List and then let TableView's items point on it. The result - The ChangeListener's changed method got called.

tableView.itemsProperty().addListener(new ChangeListener() {
    @Override
    public void changed(ObservableValue ov, Object t, Object t1) {
        System.out.println("Changed!");
    }
});

final ObservableList data = FXCollections.observableArrayList(new ArrayList());
data.clear();
data.add(new Object()); 
tableView.setItems(data);
data.clear();
data.add(new Object());
tableView.setItems(data);

I looked it up on http://docs.oracle.com/javafx/2/api/javafx/scene/control/TableView.html#itemsProperty() but it only says "The underlying data model for the TableView. Note that it has a generic type that must match the type of the TableView itself."

I'm asking this because I might miss out on some other important circumstances.

like image 860
damat-perdigannat Avatar asked Dec 29 '25 01:12

damat-perdigannat


1 Answers

A not fully documented fact (aka: implementation detail) is that ObjectProperty only fires on

!oldValue.equals(newValue); // modulo null checking 

That's critical for a list-valued object property, as lists are specified to be equal if all their elements are equal. In particular, all empty lists are equal to each other, thus replacing one empty list by another empty list as in your first snippet will not make the property fire:

 // items empty initially
 TableView table = new TableView()
 table.itemsProperty().addListener(....)
 ObservableList empty = FXCollections.observableArrayList();
 // replace initial empty list by new empty list
 table.setItems(empty);
 // no change event was fired!

That's nasty if your code wants to listen to changes of the content - it would need to re-wire the ListChangeListeners whenever the identity of the items value changes but can't with a changeListener because that fires based on equality. BTW, even fx-internal code got that wrong and hot-fixed by a dirty hack

And no nice solution available, just a couple of suboptimal options

  • use an InvalidationListener instead of a changeListener
  • bind (unidirectionally!) a ListProperty to the list-valued object property and listen to the latter
  • use an adapter that combines the above to at least have it out off the way

A code snippet I use:

public static <T> ListProperty<T> listProperty(final Property<ObservableList<T>> property) {
    Objects.requireNonNull(property, "property must not be null");
    ListProperty<T> adapter = new ListPropertyBase<T>() {
        // PENDING JW: need weakListener?
        private InvalidationListener hack15793;
        {
            Bindings.bindBidirectional(this, property);
            hack15793 = o -> {
                ObservableList<T> newItems =property.getValue();
                ObservableList<T> oldItems = get();
                // force rewiring to new list if equals
                boolean changedEquals = (newItems != null) && (oldItems != null) 
                        && newItems.equals(oldItems);
                if (changedEquals) {
                    set(newItems);
                }
            };
            property.addListener(hack15793);
        }

        @Override
        public Object getBean() {
            return null; // virtual property, no bean
        }

        @Override
        public String getName() {
            return property.getName();
        }

        @Override
        protected void finalize() throws Throwable {
            try {
                Bindings.unbindBidirectional(property, this);
                property.removeListener(hack15793);
            } finally {
                super.finalize();
            }
        }

    };
    return adapter;
}
like image 180
kleopatra Avatar answered Dec 30 '25 16:12

kleopatra



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!