问题
Okay so I used the second block of code from this website http://www.java2s.com/Tutorials/Java/JavaFX/0660__JavaFX_Tree_View.htm where it states "The following code create a dynamic tree from the local file system"
I do not understand how this code works in order to customise it to my needs. Particularly the overriding methods, there did not seem to be a place where I could add in "only add folders down to the sub directory which contain mp3 files". I reckon it's likely going to require something more complex such as something that goes through and removes folders. I'm honestly not sure.
I have tried to use this code in my program which is to show mp3 files. The idea was to have two treeViews side by side, the left side shows the hierarchy of folders to the folders which have mp3 files in them (and do not shows other folders which do not have mp3 files in them) and the right side shows files which are of only mp3 file type in those folders. There is a screenshot further down.
This is the code I have so far which returns a TreeView in a VBox. There are two segments of code which are commented out. The first is due to the fact that java: search file according to its name in directory and subdirectories does not wish to search my C: drive. (I do not know why). So I changed it to only scan my D: (Partition drive). The second is from the webpage where I got the main segment of code. This code was moved to an outer class which handles. As well as a cheeky bit of code to handle more than one drive.
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.VBox;
import org.apache.commons.io.FileUtils;
/**
* https://stackoverflow.com/questions/6251762/java-search-file-according-to-its-name-in-directory-and-subdirectories
* https://stackoverflow.com/questions/26690247/how-to-make-directories-expandable-in-javafx-treeview
* http://www.java2s.com/Tutorials/Java/JavaFX/0660__JavaFX_Tree_View.htm
*
* @author Scorchgid
*/
public class FolderTreeView {
int x = 0;
private final String fileName = ".mp3";
private MainView mainView;
private TreeView<File> treeViewFile = new TreeView<>();
public TreeView<File> getTreeViewFile() {
return treeViewFile;
}
public void setTreeViewFile(TreeView<File> treeViewFile) {
this.treeViewFile = treeViewFile;
}
public VBox treeStack() throws IOException {
VBox vbox = new VBox();
File[] drives = File.listRoots();
ArrayList<File> fileListing;
/*for (File dir : drives) {
System.out.println(dir.toString());
fileListing = restrictingList(dir);
}*/
fileListing = restrictingList(new File("D:\\"));
ArrayList<TreeItem> treeItems = new ArrayList<>();
for (File dir : drives) {
//System.out.println(dir.toString());
treeItems.add(createNode(dir));
}
TreeView<File> tree = proxyCreateNode(treeItems);
vbox.getChildren().add(tree);
return vbox;
}
// https://stackoverflow.com/questions/22260032/set-two-root-nodes-for-treeview
public TreeView<File> proxyCreateNode(ArrayList<TreeItem> arrayListTreeItem) {
TreeItem<File> proxyItem = new TreeItem<>();
proxyItem.setExpanded(true);
for (TreeItem<File> item : arrayListTreeItem) {
proxyItem.getChildren().addAll(item);
}
TreeView<File> tree = new TreeView<>(proxyItem);
tree.setShowRoot(false);
return tree;
}
private ArrayList<File> restrictingList(File root) {
ArrayList<File> fileArray = new ArrayList<>();
boolean recursive = true;
Collection files = FileUtils.listFiles(root, null, recursive);
for (Iterator iterator = files.iterator(); iterator.hasNext();) {
File file = (File) iterator.next();
if (file.getName().endsWith(fileName)) {
fileArray.add(file);
}
}
return fileArray;
}
/* @Override
public void start(Stage stage) {
Scene scene = new Scene(new Group(), 300, 300);
TreeItem<File> root = createNode(new File("c:/"));
TreeView treeView = new TreeView<File>(root);
vbox.getChildren().add(treeView);
((Group) scene.getRoot()).getChildren().add(vbox);
stage.setScene(scene);
stage.show();
}
*/
private TreeItem<File> createNode(final File f) {
return 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;
}
private ObservableList<TreeItem<File>> buildChildren(
TreeItem<File> TreeItem) {
File f = TreeItem.getValue();
if (f == null) {
return FXCollections.emptyObservableList();
}
if (f.isFile()) {
return FXCollections.emptyObservableList();
}
File[] files = f.listFiles();
if (files != null) {
ObservableList<TreeItem<File>> children = FXCollections
.observableArrayList();
for (File childFile : files) {
//System.out.println("Adding " + childFile.getAbsolutePath());
if (childFile.isDirectory()) {
children.add(createNode(childFile));
}
}
return children;
}
return FXCollections.emptyObservableList();
}
};
}
}
回答1:
You can create the tree structure recursively. There are various mechanisms (please also note jewelsea's comment to your question), here's one:
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.function.Function;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class FolderTreeView extends Application {
private static final String ROOT_FOLDER = "c:/music"; // TODO: change or make selectable
@Override
public void start(Stage primaryStage) throws IOException {
// create root
TreeItem<Path> treeItem = new TreeItem<Path>(Paths.get( ROOT_FOLDER));
treeItem.setExpanded(true);
// create tree structure
createTree( treeItem);
// sort tree structure by name
treeItem.getChildren().sort( Comparator.comparing( new Function<TreeItem<Path>, String>() {
@Override
public String apply(TreeItem<Path> t) {
return t.getValue().toString().toLowerCase();
}
}));
// create components
TreeView<Path> treeView = new TreeView<Path>(treeItem);
StackPane root = new StackPane();
root.getChildren().add(treeView);
primaryStage.setScene(new Scene(root, 1024, 768));
primaryStage.setTitle("Folder Tree View Example");
primaryStage.show();
}
/**
* Recursively create the tree
* @param rootItem
* @throws IOException
*/
public static void createTree(TreeItem<Path> rootItem) throws IOException {
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(rootItem.getValue())) {
for (Path path : directoryStream) {
TreeItem<Path> newItem = new TreeItem<Path>(path);
newItem.setExpanded(true);
rootItem.getChildren().add(newItem);
if (Files.isDirectory(path)) {
createTree(newItem);
}
}
}
}
public static void main(String[] args) {
launch(args);
}
}
Then you have a tree with all of your files. Since you intend to scan the entire tree structure from a given root path and keep it in memory, you may as well simply filter the tree. I took the code above and the filter code from rli's answer in this post and adapted it. It basically creates a filtered tree structure from the original structure.
Here's the full example code:
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.function.Function;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FolderTreeViewWithFilter extends Application {
private static final String ROOT_FOLDER = "c:/music"; // TODO: change or make selectable
TreeItem<FilePath> rootTreeItem;
TreeView<FilePath> treeView;
@Override
public void start(Stage primaryStage) throws IOException {
// root component
VBox root = new VBox();
// filter
TextField filter = new TextField();
filter.textProperty().addListener((observable, oldValue, newValue) -> filterChanged(newValue));
// treeview
treeView = new TreeView<FilePath>();
VBox.setVgrow(treeView, Priority.ALWAYS);
root.getChildren().addAll( filter, treeView);
// stage
primaryStage.setScene(new Scene(root, 1024, 768));
primaryStage.setTitle("Folder Tree View With Filter Example");
primaryStage.show();
// create tree
createTree();
// show tree structure in tree view
treeView.setRoot(rootTreeItem);
}
/**
* Create original tree structure
* @throws IOException
*/
private void createTree() throws IOException {
// create root
rootTreeItem = createTreeRoot();
// create tree structure recursively
createTree( rootTreeItem);
// sort tree structure by name
rootTreeItem.getChildren().sort( Comparator.comparing( new Function<TreeItem<FilePath>, String>() {
@Override
public String apply(TreeItem<FilePath> t) {
return t.getValue().toString().toLowerCase();
}
}));
}
/**
* Iterate through the directory structure and create a file tree
* @param rootItem
* @throws IOException
*/
public static void createTree(TreeItem<FilePath> rootItem) throws IOException {
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(rootItem.getValue().getPath())) {
for (Path path : directoryStream) {
TreeItem<FilePath> newItem = new TreeItem<FilePath>( new FilePath( path));
newItem.setExpanded(true);
rootItem.getChildren().add(newItem);
if (Files.isDirectory(path)) {
createTree(newItem);
}
}
}
// catch exceptions, e. g. java.nio.file.AccessDeniedException: c:\System Volume Information, c:\$RECYCLE.BIN
catch( Exception ex) {
ex.printStackTrace();
}
}
/**
* Create new filtered tree structure
* @param root
* @param filter
* @param filteredRoot
*/
private void filter(TreeItem<FilePath> root, String filter, TreeItem<FilePath> filteredRoot) {
for (TreeItem<FilePath> child : root.getChildren()) {
TreeItem<FilePath> filteredChild = new TreeItem<>( child.getValue());
filteredChild.setExpanded(true);
filter(child, filter, filteredChild );
if (!filteredChild.getChildren().isEmpty() || isMatch(filteredChild.getValue(), filter)) {
filteredRoot.getChildren().add(filteredChild);
}
}
}
/**
* Comparator for tree filter
* @param value
* @param filter
* @return
*/
private boolean isMatch(FilePath value, String filter) {
return value.toString().toLowerCase().contains( filter.toLowerCase()); // TODO: optimize or change (check file extension, etc)
}
/**
* Show original tree or filtered tree depending on filter
* @param filter
*/
private void filterChanged(String filter) {
if (filter.isEmpty()) {
treeView.setRoot(rootTreeItem);
}
else {
TreeItem<FilePath> filteredRoot = createTreeRoot();
filter(rootTreeItem, filter, filteredRoot);
treeView.setRoot(filteredRoot);
}
}
/**
* Create root node. Used for the original tree and the filtered tree.
* Another option would be to clone the root.
* @return
*/
private TreeItem<FilePath> createTreeRoot() {
TreeItem<FilePath> root = new TreeItem<FilePath>( new FilePath( Paths.get( ROOT_FOLDER)));
root.setExpanded(true);
return root;
}
/**
* Wrapper for the path with overwritte toString method. We only want to see the last path part as tree node, not the entire path.
*/
private static class FilePath {
Path path;
String text;
public FilePath( Path path) {
this.path = path;
// display text: the last path part
// consider root, e. g. c:\
if( path.getNameCount() == 0) {
this.text = path.toString();
}
// consider folder structure
else {
this.text = path.getName( path.getNameCount() - 1).toString();
}
}
public Path getPath() {
return path;
}
public String toString() {
// hint: if you'd like to see the entire path, use this:
// return path.toString();
// show only last path part
return text;
}
}
public static void main(String[] args) {
launch(args);
}
}
Simply enter search text in the text field, the tree will be filtered accordingly.
You may change the toString() method if you'd like to see the full path. Also, currently a substring is searched instead of the file extension. That's just for demo, adapting it for your needs is trivial.
Similar mechanism applies if you'd like to display parts of the tree in another tree view or list view.
Example if you'd like to get a tree structure with all nodes that contain a file with e. g. substring "mp3" in the text filter while not displaying the file itself in the tree, here's the modified version of the filter method:
/**
* Create new filtered tree structure
* @param root
* @param filter
* @param filteredRoot
*/
private void filter(TreeItem<FilePath> root, String filter, TreeItem<FilePath> filteredRoot) {
for (TreeItem<FilePath> child : root.getChildren()) {
TreeItem<FilePath> filteredChild = new TreeItem<>( child.getValue());
filteredChild.setExpanded(true);
filter(child, filter, filteredChild );
boolean hasItem = false;
for (TreeItem<FilePath> subChild: child.getChildren()) {
if( isMatch( subChild.getValue(), filter)) {
hasItem = true;
break;
}
}
if (!filteredChild.getChildren().isEmpty() || hasItem) {
filteredRoot.getChildren().add(filteredChild);
}
}
}
If you'd like to do it the performing / memory efficient way, you could take a look at the TreeItem example code which will scan a folder only when you navigate into it.
来源:https://stackoverflow.com/questions/34534775/configuring-a-treeview-which-scans-local-fie-system-to-only-include-folders-whic