- Loading...
...
| Code Block |
|---|
final TreeItem<String> root = new TreeItem<String>("Root node");
final TreeItem<String> childNode1 = new TreeItem<String>("Child Node 1");
final TreeItem<String> childNode2 = new TreeItem<String>("Child Node 2");
final TreeItem<String> childNode3 = new TreeItem<String>("Child Node 3");
root.setExpanded(true);
root.getChildren().setAll(childNode1, childNode2, childNode3);
TreeTableColumn<String, String> column = new TreeTableColumn<String, String>("Column");
column.setCellValueFactory(new Callback<CellDataFeatures<String, String>, ObservableValue<String>>() {
@Override public ObservableValue<String> call(CellDataFeatures<String, String> p) {
return new ReadOnlyStringWrapper(p.getValue().getValue());
}
});
final TreeTableView<String> treeTableView = new TreeTableView<String>(root);
treeTableView.getColumns().add(column);
|
A slightly more complete code example is shown below, which builds a TreeTableView instance that will dynamically populate the tree on demand with the content of the users filesystem.
| Code Block |
|---|
private TreeTableView buildFileBrowserTreeTableView() {
// create a simple String treeview
TreeItem<File> root = createNode(new File("/"));
root.setExpanded(true);
final TreeTableView<File> treeTableView = new TreeTableView<File>();
treeTableView.setShowRoot(true);
treeTableView.setRoot(root);
// --- name column
TreeTableColumn<File, String> nameColumn = new TreeTableColumn<File, String>("Name");
nameColumn.setPrefWidth(300);
nameColumn.setCellValueFactory(new Callback<CellDataFeatures<File, String>, ObservableValue<String>>() {
@Override public ObservableValue<String> call(CellDataFeatures<File, String> p) {
File f = p.getValue().getValue();
String text = f.getParentFile() == null ? "/" : f.getName();
return new ReadOnlyObjectWrapper<String>(text);
}
});
// temporary cell factory to simulate expand / collapse instructions
nameColumn.setCellFactory(new Callback<TreeTableColumn<File, String>, TreeTableCell<File, String>>() {
@Override public TreeTableCell<File, String> call(TreeTableColumn<File, String> p) {
final TreeTableCell<File, String> cell = new TreeTableCell<File, String>() {
@Override protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
setText(item);
}
}
};
return cell;
}
});
// --- size column
TreeTableColumn<File, File> sizeColumn = new TreeTableColumn<File, File>("Size");
sizeColumn.setPrefWidth(100);
sizeColumn.setCellValueFactory(new Callback<CellDataFeatures<File, File>, ObservableValue<File>>() {
@Override public ObservableValue<File> call(CellDataFeatures<File, File> p) {
return new ReadOnlyObjectWrapper<File>(p.getValue().getValue());
}
});
sizeColumn.setCellFactory(new Callback<TreeTableColumn<File, File>, TreeTableCell<File, File>>() {
@Override public TreeTableCell<File, File> call(final TreeTableColumn<File, File> p) {
return new TreeTableCell<File, File>() {
@Override protected void updateItem(File item, boolean empty) {
super.updateItem(item, empty);
TreeTableView treeTable = p.getTreeTableView();
// if the File is a directory, it has no size...
if (getIndex() >= treeTable.impl_getTreeItemCount()) {
setText(null);
} else {
TreeItem<File> treeItem = treeTable.getTreeItem(getIndex());
if (item == null || empty || treeItem == null ||
treeItem.getValue() == null || treeItem.getValue().isDirectory()) {
setText(null);
} else {
setText(nf.format(item.length()) + " KB");
}
}
}
};
}
});
sizeColumn.setComparator(new Comparator<File>() {
@Override public int compare(File f1, File f2) {
long s1 = f1.isDirectory() ? 0 : f1.length();
long s2 = f2.isDirectory() ? 0 : f2.length();
long result = s1 - s2;
if (result < 0) {
return -1;
} else if (result == 0) {
return 0;
} else {
return 1;
}
}
});
// --- modified column
TreeTableColumn<File, Date> lastModifiedColumn = new TreeTableColumn<File, Date>("Last Modified");
lastModifiedColumn.setPrefWidth(130);
lastModifiedColumn.setCellValueFactory(new Callback<CellDataFeatures<File, Date>, ObservableValue<Date>>() {
@Override public ObservableValue<Date> call(CellDataFeatures<File, Date> p) {
return new ReadOnlyObjectWrapper<Date>(new Date(p.getValue().getValue().lastModified()));
}
});
lastModifiedColumn.setCellFactory(new Callback<TreeTableColumn<File, Date>, TreeTableCell<File, Date>>() {
@Override public TreeTableCell<File, Date> call(TreeTableColumn<File, Date> p) {
return new TreeTableCell<File, Date>() {
@Override protected void updateItem(Date item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else {
setText(df.format(item));
}
}
};
}
});
treeTableView.getColumns().setAll(nameColumn, sizeColumn, lastModifiedColumn);
return treeTableView;
}
private TreeItem<File> createNode(final File f) {
final TreeItem<File> node = new TreeItem<File>(f) {
private boolean isLeaf;
private boolean isFirstTimeChildren = true;
private boolean isFirstTimeLeaf = true;
@Override
public ObservableList<TreeItem<File>> getChildren() {
if (isFirstTimeChildren) {
isFirstTimeChildren = false;
super.getChildren().setAll(buildChildren(this));
}
return super.getChildren();
}
@Override
public boolean isLeaf() {
if (isFirstTimeLeaf) {
isFirstTimeLeaf = false;
File f = (File) getValue();
isLeaf = f.isFile();
}
return isLeaf;
}
};
return node;
}
private ObservableList<TreeItem<File>> buildChildren(TreeItem<File> TreeItem) {
File f = (File) TreeItem.getValue();
if (f != null && f.isDirectory()) {
File[] files = f.listFiles();
if (files != null) {
ObservableList<TreeItem<File>> children = FXCollections.observableArrayList();
for (File childFile : files) {
children.add(createNode(childFile));
}
return children;
}
}
return FXCollections.emptyObservableList();
}
|
I do not desire to cover the implementation in a huge depth, but I do think it is useful to clarify that the intention is to reuse as much of the existing implementation code from TableView and TreeView wherever possible, without having to resort to copy/paste. Therefore, whilst it is very critical that the API defined above be able to stand on its own, I feel I should clarify that many of the decisions made (particularly around extracting common *Base classes) was driven by a desire to minimise code duplication in the implementation. I believe that the current level of extraction has taken us as far as we can go in reducing code duplication, and that there is in fact now very little TreeTableView specific code in the implementation - most classes in the implementation related to TreeTableView are simply extending an abstract class and providing the necessary code (normally 5-10 methods), most of which are one-line methods returning some TreeTableView-specific implementation of a public API.
...