Toni_Entranced
Toni_Entranced

Reputation: 979

Automatically Updating TreeView of Nested Items

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

Answers (1)

James_D
James_D

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

Related Questions