My TableView is containing elements which have a couple of attributes that are exposed using properties. An element consists of a list of numbers called numbers, the size of that list called numbersSize and a single number just called number. The problematic one that won't update in UI is the numbers list.
Table and Columns are defined like this
@FXML private TableView<Element> table;
@FXML private TableColumn<Element, Integer> numberCol;
@FXML private TableColumn<Element, Integer> numbersSizeCol;
@FXML private TableColumn<Element, List<Integer>> numbersCol;
The element class looks like this
private ObjectProperty<Integer> number;
private IntegerProperty numbersSize;
private ListProperty<Integer> numbers;
public ObjectProperty<Integer> numberProperty() { return number; }
public IntegerProperty numbersSizeProperty() { return numbersSize; }
public ListProperty<Integer> numbersProperty() { return numbers; }
number is of Type ObjectProperty because i want null to be a posssible value too, which is not possible using just IntegerProperty. The size property of the Element class is directly linked to the sizeProperty of the numbers list like this
numbersSize.bind(numbers.sizeProperty());
The connection between the table and the model is done like this
numberCol.setCellValueFactory(new PropertyValueFactory<>("number"));
numbersSizeCol.setCellValueFactory(new PropertyValueFactory<>("numbersSize"));
numbersCol.setCellValueFactory(new PropertyValueFactory<>("numbers"));
I plan to do some time intensive working on the table elements in a background thread and before i even start with that, i tested whether the changes in the model are reflected correctly in the table like this
List<Integer> l = new ArrayList<>();
l.add(1);
l.add(2);
l.add(3);
ObservableList<Integer> o = FXCollections.observableList(l);
Element e = new Element(0, o);
elementController.getElementList().add(e);
startBtn.setOnAction(e -> {
new Thread(() -> {
Element e = elementController.getElementList().get(0);
Platform.runLater(() -> {
e.numberProperty().set(1234);
e.numbersProperty().add(9);
});
}).start();
});
The really strange thing that happens now and that i can't wrap my head around is this:
The number column shows a 1234 instead of the specified 0 via the element constuctor, so far so good the change carried through to the table.
The numbers size column shows a 4 which fits because the Element was initialized with a list of 1,2,3 and 9 was added, so far so good the change carried through to the table.
BUT the numbers list column is still displaying [1,2,3], the 9 is missing, why is that? The TableColumn is linked to the ListProperty and the ListProperty is backed by an ArrayList wrapped in an ObservableList..., shouldn't a change to that list be detected and propagated back to the table?
If i add two Elements to the table and also add the code to remove the second Element in the Platform.runLater(...) then the numbers column is displaying [1,2,3,9]. So the problem seems to be that number and numbersSize are always displayed correctly but the list numbers is only displayed correctly when an element is added/removed from the tableview backend.
What i want however is the count of Elements in the tableview to not change at all (once it is initially filled). I want do additions/removals to the numbers list of each element but it are just those that won't show up in UI when changed :/
ListProperty<Integer>
is a Property<ObservableList<Integer>>
and implements ObservableList<Integer>
. Modifications to the property itself are applied to the value stored in the property.
This means
e.numbersProperty().add(9);
has the same effect as
e.numbersProperty().get().add(9);
This operation does not replace the value of the property and therefore the TableCell
is not updated.
To update the TableCell
even if the an the list in the property is updated, you could implement a custom cellFactory
:
public class ObservableItemTableCell<S, T extends Observable> extends TableCell<S, T> {
final InvalidationListener l = o -> setText(getItem().toString());
final WeakInvalidationListener listener = new WeakInvalidationListener(l);
@Override
protected void updateItem(T item, boolean empty) {
// remove old listener
T oldItem = getItem();
if (oldItem != null) {
oldItem.removeListener(listener);
}
super.updateItem(item, empty);
if (empty || item == null) {
setText("");
} else {
setText(item.toString());
item.addListener(listener);
}
}
}
@FXML private TableColumn<Element, ObservableList<Integer>> numbersCol;
numbersCol.setCellValueFactory(new PropertyValueFactory<>("numbers"));
numbersCol.setCellFactory(col -> new ObservableItemTableCell<>());