Reputation: 979
I have a Goal, which has a list of Objectives. An objective has a list of Strategies. A strategy has a list of Tactics. A tactic has a list of Tasks.
I want to be able to display this in a TreeView, and I want for the tree to be synchronized to the items. That is, if I delete an Objective, that Objective, along with its children, will disappear from the TreeView as well.
So far, this is what I have been trying.
/**
* The base class for all PlanItems, which include ActionItems down to
* ActionTasks, and Objectives down to Tasks.
*
* @author Toni-Tran
*/
public class PlanItem implements Comparable<PlanItem> {
protected ObservableList<PlanItem> childPlanItems = FXCollections
.observableArrayList();
protected TreeItem<PlanItem> treeItem = new TreeItem<>(this);
This is the base class for all those items. In its constructor:
public PlanItem() {
CustomBinding.bindLists(treeItem.getChildren(), childPlanItems, PlanItem::getTreeItem);
}
I am using my custom binding, which binds two lists of different objects together. (Alternatively, I could have used EasyBind).
/**
* Binds a source list's elements to a destination list. Any changes made in
* the source list will reflect in the destination list.
*
* @param <SRC> The source list's object type.
* @param <DEST> The destination list's object type.
* @param dest The destination list that will be bound to the src list.
* @param src The source list to watch for changes, and propagate up to the
* destination list.
* @param transformer A function that will transform a source list data
* type, A, into a destination list data type, B.
*/
public static <SRC extends Object, DEST extends Object> void bindLists(
ObservableList<DEST> dest, ObservableList<SRC> src, Function<SRC, DEST> transformer) {
/*Add the initial data into the destination list.*/
for (SRC a : src) {
dest.add(transformer.apply(a));
}
/*Watch for future data to add to the destination list. Also watch for removal
of data form the source list to remove its respective item in the destination
list.*/
src.addListener((ListChangeListener.Change<? extends SRC> c) -> {
while (c.next()) {
/*Watch for removed data.*/
if (c.wasRemoved()) {
for (SRC a : c.getRemoved()) {
int from = c.getFrom();
dest.remove(from);
}
}
/*Watch for added data.*/
if (c.wasAdded()) {
for (SRC a : c.getAddedSubList()) {
int indexAdded = src.indexOf(a);
dest.add(indexAdded, transformer.apply(a));
}
}
}
});
}
I'm not sure if this is the right approach. The list of child items is usually a list of objects that EXTEND PlanItem, rather than just PlanItem itself. Shouldn't it be an ObservableList<? extends PlanItem>
then? Doing so makes the rest of my code complicated.
The plan is to create a TreeItem that wraps the PlanItem. Then, synchronize the TreeItem's child TreeItems to the PlanItem's child PlanItems. This repeats recursively for each nested PlanItem too.
Upvotes: 0
Views: 466
Reputation: 209319
Your bindLists
method is quite general, and can be written (as you have it now) without any reference to the lists you're working with right now (i.e. without reference to the PlanItem
class). So I would probably just make it
public static <SRC, DEST> void bindLists(ObservableList<DEST> dest, ObservableList<SRC> src, ...) { ... }
Note though that the mapping function just has to be able to apply to the elements of src
(so it has to act on things of which the elements of src
are a type) and produce something that can be placed into the dest
list. So using Function<SRC, DEST>
is probably too restrictive. Try
public static <SRC, DEST> void bindLists(ObservableList<DEST> dest, ObservableList<SRC> src,
Function<? super SRC, ? extends DEST> transformer) { ... }
Now you can call
bindLists(ObservableList<TreeItem<PlanItem>>, ObservableList<T>, PlanItem::getTreeItem)
where T
is PlanItem
or any subclass, and it should be easy to use.
Upvotes: 1