I actually accomplished this in a round-about way, though it is extremely flexible, and even allows you to have TreeItems that engage different classes while still being able to nest them within each other.
It can be rather daunting to look at upon initial inspection, but if you follow it closely, it will make perfect sense.
You start with your class containing the TreeView - we'll call it TreeViewClass.
This example came from a program I'm working on that interacts with GitHub Gists, which explains the various objects that I interact with, in the classes.
public class TreeViewClass {
public void start() {
TextArea itemInfo = new TextArea();
SplitPane splitPane = new SplitPane(getTreeView(), itemInfo);
splitPane.setDividerPosition(0, .1);
Stage stage = new Stage();
stage.setScene(new Scene(splitPane));
stage.show();
}
private Collection<TreeItem<MyTreeItem>> gistObjectTreeItems() {
Collection<TreeItem<MyTreeItem>> gistTreeItemObjects = FXCollections.observableArrayList();
List<GistObject> gistObjects = GitHubApi.getGistObjectList();
for (GistObject gistObject : gistObjects) {
MyTreeItem myGistObjectTreeItem = new MyTreeItem(gistObject);
TreeItem<MyTreeItem> objectTreeItem = new TreeItem<>(myGistObjectTreeItem);
List<GistFile> gistFileList = gistObject.getFileList();
for(GistFile file : gistFileList) {
TreeItem<MyTreeItem> fileTreeItem = new TreeItem<>(new MyTreeItem(file));
objectTreeItem.getChildren().add(fileTreeItem);
}
gistTreeItemObjects.add(gistObjectTreeItem);
}
return gistTreeItemObjects;
}
/**
We are returning a Collection of TreeItems, each one contains an instance
of MyTreeItem using the constructor that defines the instance as a GistObject.
We then add to each of those objects, more TreeItems that contain the same
class MyTreeItem, only we use the constructor that sets the instance as
GistFile, and obviously, each Gist can have many GistFiles.
**/
private TreeView<MyTreeItem> getTreeView() {
TreeView<MyTreeItem> treeView = new TreeObject(this);
TreeItem<MyTreeItem> treeRoot = new TreeItem<>(new MyTreeItem());
treeRoot.getChildren().addAll(gistObjectTreeItems());
treeView.setRoot(treeRoot);
treeView.setShowRoot(false);
return treeView;
}
/**
getTreeView merely builds the TreeView, then adds to it, the Collection
of MyTreeItems that contain the GistObject and each of those objects
GistFiles. We need a root in the tree, so we add one that also contains
the MyTreeItem class only we use the default constructor, since it is
neither a GistObject or a GistFile, and then we hide it because we
don't need to interact with it.
**/
}
This is the main TreeView extended class:
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.input.MouseEvent;
public class TreeObject extends TreeView<MyTreeItem> {
public TreeObject() {
super();
addEventHandler(MouseEvent.MOUSE_CLICKED, e->{
if(e.getClickCount() == 1) {
selected = getSelectionModel().getSelectedItem();
if (selected != null) {
gistFile = selected.getValue().getFile();
gist = selected.getValue().getGist();
if (gistFile != null) {
GitHubApi.setSelected(gistFile);
}
else if (gist != null) {
GitHubApi.setSelected(gist);
}
}
}
});
}
TreeItem<MyTreeItem> selected;
GistFile gistFile;
GistObject gist;
}
/**
This is the class that gives us the ability to add all manner of events that we might
want to look for. In this case, I am looking for MOUSE_CLICKED events and notice I can
even check to see how many mouse clicks the user enacted on the TreeItems. When the user
clicks on the TreeItem, the selected object returns the MyTreeItem instance that
is assigned to whichever TreeItem they click on, I can then test to see which way
MyTreeItem has been configured (GistObject or GistFile) and act accordingly.
**/
Finally, we have the MyTreeItem class:
public class MyTreeItem {
private enum TYPE {
GIST, FILE
}
public MyTreeItem(GistObject gist) {
this.gist = gist;
this.type = TYPE.GIST;
}
public MyTreeItem(GistFile file) {
this.file = file;
this.type = TYPE.FILE;
}
public MyTreeItem(){}
private TYPE type;
private GistObject gist;
private GistFile file;
public boolean isGist() { return type == TYPE.GIST; }
public boolean isFile() { return type == TYPE.FILE; }
public GistObject getGist() { return gist; }
public GistFile getFile() { return file; }
@Override
public String toString() {
if(this.type == TYPE.FILE) return StringUtils.truncate(file.toString(),25);
else if(this.type == TYPE.GIST) return StringUtils.truncate(gist.toString(),25);
return "";
}
}
/**
This should be fairly self-explanatory. This is the MyTreeItem class that can be instantiated
as either a GistObject or a GistFile. Note the toString returns different Strings depending
on how the class was instantiated. The toString() will automatically populate the label
on the TreeItem object that the class is assigned to.
**/