import com.sun.javafx.application.PlatformImpl; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * @author Alexander Gulko */ public class FileTreeView extends TreeView { private static Executor executor = Executors.newSingleThreadExecutor(); private FileTreeItem root = createNode(new Category("root") { { setHasChildren(true); } }); public FileTreeView() { setRoot(root); setShowRoot(false); init(); } private static FileTreeItem createNode(Category category) { FileTreeItem treeItem = new FileTreeItem(category); if (category.isHasChildren()) { treeItem.expandedProperty().addListener(new ExpandListener(treeItem)); treeItem.getChildren().add(new FileTreeItem(new Category("Loading..."))); } return treeItem; } private void init() { new Loader() { @Override public List loadNodes() { return getRootNodes(); } @Override public void onLoadFinish(List categories) { fillNode(root, categories); root.setExpanded(true); } }.run(); } private static void fillNode(FileTreeItem node, List categories) { List children = new ArrayList(); for (Category category : categories) { children.add(createNode(category)); } node.getChildren().clear(); node.getChildren().addAll(children); } public static class ExpandListener implements ChangeListener { private FileTreeItem treeItem; public ExpandListener(FileTreeItem treeItem) { this.treeItem = treeItem; } @Override public void changed(ObservableValue observableValue, Boolean oldValue, Boolean newValue) { if (newValue != null && newValue) { final Category fileTreeNode = treeItem.getValue(); if (treeItem.isCompleted()) { return; } treeItem.setCompleted(true); if (fileTreeNode.getFile() != null) { new Loader() { @Override public List loadNodes() { return getDirectoryNodes(fileTreeNode.getFile()); } @Override public void onLoadFinish(List categories) { fillNode(treeItem, categories); } }.run(); } } } } private static abstract class Loader { public final void run() { executor.execute(new Runnable() { @Override public void run() { try { final List result = loadNodes(); PlatformImpl.runLater(new Runnable() { @Override public void run() { onLoadFinish(result); } }); } catch (Exception e) { e.printStackTrace(); } } }); } public abstract List loadNodes(); public abstract void onLoadFinish(List categories); } public static class FileTreeItem extends TreeItem { private boolean completed; public FileTreeItem(Category category) { super(category); } public boolean isCompleted() { return completed; } public void setCompleted(boolean completed) { this.completed = completed; } } private List getRootNodes() { List result = new ArrayList(); File[] roots = File.listRoots(); for (File rootFile : roots) { result.add(getNodeFromFile(rootFile, true)); } return result; } private static Category getNodeFromFile(File file, boolean isRoot) { Category fileNode = new Category(file); fileNode.setName(isRoot ? file.getPath() : file.getName()); fileNode.setHasChildren(hasSubDirectories(file)); return fileNode; } protected static List getDirectoryNodes(File parentFile) { List nodes = new ArrayList(); for (File child : getDirectoryFiles(parentFile)) { if (acceptDirectory(child)) { Category directoryNode = getNodeFromFile(child, false); nodes.add(directoryNode); } } return nodes; } public static boolean hasSubDirectories(File directory) { boolean found = false; for (File child : getDirectoryFiles(directory)) { if (acceptDirectory(child)) { found = true; break; } } return found; } public static boolean acceptDirectory(File file) { return file.isDirectory() && !file.isHidden(); } public static File[] getDirectoryFiles(File directory) { File[] files = null; if (directory != null && directory.isDirectory()) { files = directory.listFiles(); } return files != null ? files : new File[0]; } }