File Tree View Sample
I just wanted to create an application like kind of file explorer to learn about the following contents.
- 1. How to use directory chooser
- 2. Tree View with Path
- 3. Watching for file systems by WatchService
- 4. Searching files by FileVisitor and PathMatcher
- 5. How to bind to JavaFX UI with background threads
- Figure 1
- code 1(FileTreeViewSample)
This code shows how to use Directory Chooser(Figure 2)
final DirectoryChooser chooser = new DirectoryChooser(); chooser.setTitle("select Directory"); Image image = new Image(getClass().getResourceAsStream("OpenDirectory.png")); Button chooserBtn = new Button("", new ImageView(image)); chooser.setTitle("Select Root Directory"); chooserBtn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent t) { File selDir = chooser.showDialog(stage); if (selDir != null) { rootDirText.setText(selDir.getAbsolutePath()); } } });
- Figure 2(Directory Chooser)
- code 2-1(PathTreeItem)
This refers to the JavaFX API site[Class ListItem]
But I just wanted to use Path instead of File.
If you use Path for TreeItem it shows absolute paths.(Figure 3)
So I create PathItem class to show file names.(code 2-2)
public class PathTreeItem extends TreeItem<PathItem> { private boolean isLeaf = false; private boolean isFirstTimeChildren = true; private boolean isFirstTimeLeft = true; private PathTreeItem(PathItem pathItem) { super(pathItem); } public static TreeItem<PathItem> createNode(PathItem pathItem) { return new PathTreeItem(pathItem); } @Override public ObservableList<TreeItem<PathItem>> getChildren() { if (isFirstTimeChildren) { isFirstTimeChildren = false; super.getChildren().setAll(buildChildren(this)); } return super.getChildren(); } @Override public boolean isLeaf() { if (isFirstTimeLeft) { isFirstTimeLeft = false; Path path = getValue().getPath(); isLeaf = !Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS); } return isLeaf; } private ObservableList<TreeItem<PathItem>> buildChildren(TreeItem<PathItem> treeItem) { Path path = treeItem.getValue().getPath(); if (path != null && Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS)) { ObservableList<TreeItem<PathItem>> children = FXCollections.observableArrayList(); try (DirectoryStream<Path> dirs = Files.newDirectoryStream(path)) { for (Path dir : dirs) { PathItem pathItem = new PathItem(dir); children.add(createNode(pathItem)); } } catch (IOException ex) { Logger.getLogger(FileTreeViewSample.class.getName()).log(Level.SEVERE, null, ex); } return children; } return FXCollections.emptyObservableList(); } }
- code 2-2(PathItem class)
public class PathItem { private Path path; public PathItem(Path path) { this.path = path; } public Path getPath() { return path; } @Override public String toString() { if (path.getFileName() == null) { return path.toString(); } else { return path.getFileName().toString(); // showing file name on the TreeView } } }
- Figure 3(TreeView with Path)
- code 3
This refers to the site Watching a Directory for Changes (The Java™ Tutorials > Essential Classes > Basic I/O)
public class WatchTask extends Task<Void>{ private Path path; private StringBuilder message = new StringBuilder(); public WatchTask(Path path) { this.path = path; } @Override protected Void call() throws Exception { WatchService watcher = FileSystems.getDefault().newWatchService(); path.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); while (true) { WatchKey key; try { key = watcher.take(); } catch (InterruptedException e) { break; } for (WatchEvent<?> event : key.pollEvents()) { if (event.kind() == OVERFLOW) { continue; } Path context = (Path) event.context(); message.append(context.toAbsolutePath()); message.append(getKindToMessage(event.kind())); message.append(System.getProperty("line.separator")); updateMessage(message.toString()); // to bind to the TextArea(see code 5-1) } boolean valid = key.reset(); if (!valid) { break; } } return null; } @Override protected void cancelled() { updateMessage("Watch task was cancelled"); // to bind to the TextArea(see code 5-1) } private String getKindToMessage(WatchEvent.Kind<?> kind) { if (kind == ENTRY_CREATE) { return " is created"; } else if (kind == ENTRY_DELETE) { return " is deleted"; } return " is updated"; } }
- code 4
This code uses FileVisitor and PathMatcher to search files.
you can also use regular expressions, or regex, syntax as well.
public class SearchTask extends Task<Void> { private Path path; private String pattern; private StringProperty resultString; public SearchTask(Path path, String pattern) { this.path = path; this.pattern = pattern; resultString = new SimpleStringProperty(); } @Override protected Void call() throws Exception { updateProgress(0, 0); updateMessage("searching ..."); final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern); Files.walkFileTree(path, new SimpleFileVisitor<Path>() { int cnt = 0; @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (matcher.matches(file.getFileName())) { cnt++; resultString.setValue(file.toAbsolutePath().toString()); // to bind to the ListItem(see code5-2) updateMessage(String.format("%d files founded", cnt)); // to bind to the Label(see code5-2) updateProgress(cnt, cnt); // this does not work } return FileVisitResult.CONTINUE; } }); return null; } public StringProperty getResultString() { return resultString; } }
- code 5-1
When watch checkbox checked WatchTask starts and shows the changes to the TextArea
public class FileTreeViewSample extends Application { ..... watchChkbox.selectedProperty().addListener(new ChangeListener<Boolean>() { @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean oldVal, Boolean newVal) { if (newVal) { watchTask = new WatchTask(rootPath); service.submit(watchTask); watchText.textProperty().bind(watchTask.messageProperty()); // see code-3 } else { if (watchTask != null && watchTask.isRunning()) { watchTask.cancel(); watchText.textProperty().unbind(); } } } }); }
- code 5-2
When search button is clicked the searchTask starts and shows the number of the results to the Label.
The label binding works but I do not know how to bind to the ListView.
So I use StringProperty[searchResult] to get the result from searchTask and add it to the ObservableList.
Once searchTask succeed the ObservableList is added to ListItem.
I think this is not the good way to bind but I do not how to implement ListBinding....
public class FileTreeViewSample extends Application { ..... searchBtn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent t) { searchListItem.clear(); searchTask = new SearchTask(rootPath, patternText.getText()); searchCountLabel.textProperty().bind(searchTask.messageProperty()); // showing the number of files(see code-4) searchList.clear(); searchResult.bind(searchTask.getResultString()); searchTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() { @Override public void handle(WorkerStateEvent t) { searchListItem.addAll(searchList); // add all search results at once(see code-4) } }); // this bind does not work??? // searchCountLabel.textProperty().bind( // new StringBinding() { // { // bind(searchTask.progressProperty()); // } // @Override // protected String computeValue() { // return String.format("%1.0f files founded", searchTask.progressProperty().getValue()); // } // }); service.submit(searchTask); } }); // *** binding to search task result one by one *** searchResult.addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> ov, String oldVal, String newVal) { if (newVal != null) { searchList.add(newVal); } } }); }
The full code is here tomoTaka01/FileTreeViewSample: JavaFX File ... - GitHub
just keep coding...