Reputation: 695
I have the following ObservableMap that I would like to display in a TableView
:
private ObservableMap<String, Shape> myShapes = FXCollections.observableHashMap();
Where Shape
is defined as follows:
public class Shape {
private StringProperty areaFormula = new SimpleStringProperty();
private IntegerProperty numSides = new SimpleIntegerProperty();
public Shape(String areaFormula, int numSides)
{
this.areaFormula.set(areaFormula);
this.numSides.set(numSides);
}
public String getAreaFormula() { return areaFormula.get(); }
public void setAreaFormula(String areaFormula) { this.areaFormula.set(areaFormula); }
public StringProperty areaFormulaProperty() { return this.areaFormula; }
public int getNumSides() { return numSides.get(); }
public void setNumSides(int sides) { this.numSides.set(sides); }
public IntegerProperty numSidesProperty() { return this.numSides; }
}
I would like the first column of the TableView to be the Map Key (call it key
), the second column to be myShapes.get(key).getAreaFormula()
, and the third column to be myShapes.get(key).getnumSides()
. And I would like the TableView to automatically update when the Map is changed.
It would also be really nice to make the second and third columns of the TableView
editable by the user, if possible, with those edits being updated in the Map.
I have already asked stackoverflow about populating a TableView with an ObservableMap such that it will update on changes here: Populating a TableView with a HashMap that will update when HashMap changes . However as you can see in the discussion there, it does not handle a custom class like I have here.
Here is my solution so far:
ObservableMap<String, Shape> map = FXCollections.observableHashMap();
ObservableList<String> keys = FXCollections.observableArrayList();
map.addListener((MapChangeListener.Change<? extends String, ? extends Shape> change) -> {
boolean removed = change.wasRemoved();
if (removed != change.wasAdded()) {
// no put for existing key
if (removed) {
keys.remove(change.getKey());
} else {
keys.add(change.getKey());
}
}
});
map.put("square", new Shape("L^2", 4));
map.put("rectangle", new Shape("LW", 4));
map.put("triangle", new Shape("0.5HB", 3));
final TableView<String> table = new TableView<>(keys);
TableColumn<String, String> column1 = new TableColumn<>("Key");
column1.setCellValueFactory(cd -> Bindings.createStringBinding(() -> cd.getValue()));
TableColumn<String, String> column2 = new TableColumn<>("Value");
column2.setCellValueFactory( ... );
table.getColumns().setAll(column1, column2);
The place where I'm stuck is the second-to-last line column2.setCellValueFactory( ... )
. I would like column2
to display the Shape
's getAreaFormula()
SimpleStringProperty
but I don't know how to set up the CellValueFactory
to do so.
Thanks for your help.
Upvotes: 1
Views: 2021
Reputation: 82461
It's easiest, if you create a list of immutable key-value pairs instead of simply a list of keys. This drastically reduces the complexity of the listeners you need to use, since the MapChangeListener
will always replace an item, if a value is updated and you don't need to add listeners to the Map
in the cellValueFactory
s:
public final class MapEntry<K, V> {
private final K key;
private final V value;
public MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public boolean equals(Object obj) {
// check equality only based on keys
if (obj instanceof MapEntry) {
MapEntry<?, ?> other = (MapEntry<?, ?>) obj;
return Objects.equals(key, other.key);
} else {
return false;
}
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
ObservableMap<String, Shape> map = FXCollections.observableHashMap();
ObservableList<MapEntry<String, Shape>> entries = FXCollections.observableArrayList();
map.addListener((MapChangeListener.Change<? extends String, ? extends Shape> change) -> {
boolean removed = change.wasRemoved();
if (removed != change.wasAdded()) {
if (removed) {
// no put for existing key
// remove pair completely
entries.remove(new MapEntry<>(change.getKey(), (Shape) null));
} else {
// add new entry
entries.add(new MapEntry<>(change.getKey(), change.getValueAdded()));
}
} else {
// replace existing entry
MapEntry<String, Shape> entry = new MapEntry<>(change.getKey(), change.getValueAdded());
int index = entries.indexOf(entry);
entries.set(index, entry);
}
});
map.put("one", new Shape("a", 1));
map.put("two", new Shape("b", 2));
map.put("three", new Shape("c", 3));
final TableView<MapEntry<String, Shape>> table = new TableView<>(entries);
TableColumn<MapEntry<String, Shape>, String> column1 = new TableColumn<>("Key");
// display item value (= constant)
column1.setCellValueFactory(cd -> Bindings.createStringBinding(() -> cd.getValue().getKey()));
TableColumn<MapEntry<String, Shape>, String> column2 = new TableColumn<>("formula");
column2.setCellValueFactory(cd -> cd.getValue().getValue().areaFormulaProperty());
TableColumn<MapEntry<String, Shape>, Number> column3 = new TableColumn<>("sides");
column3.setCellValueFactory(cd -> cd.getValue().getValue().numSidesProperty());
table.getColumns().setAll(column1, column2, column3);
Upvotes: 2