Java linux 中的 TreeView 问题

Java TreeView issue in linux

我正在尝试在我正在开发的应用程序中创建一个文件管理器。这个文件管理器使用 TreeView 和 TreeItem,但我坚持获取根文件夹。 I hope this image let me explain better。 这是主要的 class:

public class JavaFXFileBrowseDemoApp extends Application {
private TreeView<String> treeView;

public static void main(String[] args){
    launch(args);
}

@Override
public void start(Stage primaryStage){

    //create tree pane
    VBox treeBox=new VBox();
    treeBox.setPadding(new Insets(10,10,10,10));
    treeBox.setSpacing(10);
    //setup the file browser root
    String hostName="computer";

    try{
        hostName=InetAddress.getLocalHost().getHostName();
    }
    catch(UnknownHostException x){}

    TreeItem<String> rootNode = new TreeItem<>(hostName, new ImageView(FilePathTreeItem.computer)); // FilePathTreeItem.computer is just an image

    Iterable<Path> rootDirectories = FileSystems.getDefault().getRootDirectories();

    for(Path name : rootDirectories){
        System.out.println(name.getFileName()); // <----- it gives me null
        FilePathTreeItem treeNode=new FilePathTreeItem(name);
        rootNode.getChildren().add(treeNode);
    }


    rootNode.setExpanded(true);
    //create the tree view
    treeView=new TreeView<>(rootNode);
    //add everything to the tree pane
    treeBox.getChildren().addAll(new Label("File browser"),treeView);
    VBox.setVgrow(treeView,Priority.ALWAYS);

    //setup and show the window
    primaryStage.setTitle("JavaFX File Browse Demo");
    StackPane root=new StackPane();
    root.getChildren().addAll(treeBox);
    primaryStage.setScene(new Scene(root,400,300));
    primaryStage.show();
}

}

这里有 FilePathTreeItem class:

public class FilePathTreeItem extends TreeItem<String>{
public static Image computer = new Image(ClassLoader.getSystemResourceAsStream("computer.png"));
public static Image folderClosed = new Image(ClassLoader.getSystemResourceAsStream("folder-closed.png"));
public static Image folderOpened = new Image(ClassLoader.getSystemResourceAsStream("folder-opened.png"));
public static Image genericText= new Image(ClassLoader.getSystemResourceAsStream("generic-text.png"));

private String fullPath;
private boolean isDirectory;

public FilePathTreeItem(Path file){
    super(file.toString());
    fullPath = file.toString();

    // test if this is a directory and set the icon
    if (Files.isDirectory(file)){
        isDirectory = true;
        setGraphic(new ImageView(folderClosed));
    }
    else{
        isDirectory = false;

        if (file.endsWith("txt")){
            setGraphic(new ImageView(genericText));
        }
    }

    if(!fullPath.endsWith(File.separator)){
        String value = file.toString();
        int indexOf = value.lastIndexOf(File.separator);

        if (indexOf > 0){
            setValue(value.substring(indexOf + 1));
        }
        else{
            setValue(value);
        }
    }

    this.addEventHandler(TreeItem.<Object>branchExpandedEvent(), new EventHandler(){
        @Override
        public void handle(Event e){
            FilePathTreeItem source = (FilePathTreeItem)e.getSource();
            if (source.isDirectory() && source.isExpanded()){
                ImageView iv = (ImageView)source.getGraphic();
                iv.setImage(folderOpened);
            }

            try{
                if (source.getChildren().isEmpty()){
                    Path path = Paths.get(source.getFullPath());
                    BasicFileAttributes attribs = Files.readAttributes(path, BasicFileAttributes.class);
                    if (attribs.isDirectory()){
                        DirectoryStream<Path> dir = Files.newDirectoryStream(path);

                        for(Path file : dir){
                            FilePathTreeItem treeNode = new FilePathTreeItem(file);
                            source.getChildren().add(treeNode);
                        }
                    }
                }
                else{
                    //if you want to implement rescanning a directory for changes this would be the place to do it
                }
            }
            catch(IOException x){
                x.printStackTrace();
            }
        }
    });

    this.addEventHandler(TreeItem.<Object>branchCollapsedEvent(), new EventHandler(){
        @Override
        public void handle(Event e){
            FilePathTreeItem source = (FilePathTreeItem) e.getSource();
            if (source.isDirectory() && !source.isExpanded()){
                ImageView iv = (ImageView) source.getGraphic();
                iv.setImage(folderOpened);
            }
        }
    });
}

public String getFullPath() {
    return fullPath;
}

public boolean isDirectory() {
    return isDirectory;
}

}

您的代码(实际上是您基于它的示例)不起作用,因为在树项展开之前没有子节点被添加到树项中。但是JavaFXTreeItem如果是叶子节点就不能展开,叶子节点的定义是没有子节点的。所以你永远没有机会扩展节点,它也永远不会被填充。

您需要按照以下方式做一些事情:

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Collectors;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TreeItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

public class FilePathTreeItem extends TreeItem<String>{
    public static Image computer = new Image(ClassLoader.getSystemResourceAsStream("computer.png"));
    public static Image folderClosed = new Image(ClassLoader.getSystemResourceAsStream("folder.png"));
    public static Image folderOpened = new Image(ClassLoader.getSystemResourceAsStream("folder-open.png"));
    public static Image genericText= new Image(ClassLoader.getSystemResourceAsStream("text-x-generic.png"));

    private ImageView imageView = new ImageView();

    private String fullPath;
    private boolean isDirectory;

    private boolean isFirstTimeLeaf = true ;
    private boolean isFirstTimeChildren = true ;
    private boolean isLeaf ;    

    public FilePathTreeItem(Path file){
        super(file.toString());
        fullPath = file.toString();

        // test if this is a directory and set the icon
        if (Files.isDirectory(file)){
            isDirectory = true;
            setGraphic(new ImageView(folderClosed));
        }
        else{
            isDirectory = false;

            if (file.endsWith("txt")){
                setGraphic(new ImageView(genericText));
            }
        }

        if(!fullPath.endsWith(File.separator)){
            String value = file.toString();
            int indexOf = value.lastIndexOf(File.separator);

            if (indexOf > 0){
                setValue(value.substring(indexOf + 1));
            }
            else{
                setValue(value);
            }
        }

        this.expandedProperty().addListener((obs, wasExpanded, isNowExpanded) -> {
            if (Files.isDirectory(Paths.get(fullPath))) {
                if (isNowExpanded) {
                    imageView.setImage(folderOpened);
                } else {
                    imageView.setImage(folderClosed);
                }
                setGraphic(imageView);
            }
        });


    }

    @Override
    public ObservableList<TreeItem<String>> getChildren() {
        if (isFirstTimeChildren) {
            isFirstTimeChildren = false ;
            super.getChildren().setAll(buildChildren());
        }
        return super.getChildren() ;
    }

    @Override
    public boolean isLeaf() {
        if (isFirstTimeLeaf) {
            isFirstTimeLeaf = false ;
            Path path = Paths.get(fullPath);
            isLeaf = ! Files.isDirectory(path);
        }
        return isLeaf ;
    }

    private ObservableList<TreeItem<String>> buildChildren() {
        Path path = Paths.get(fullPath);
        if (Files.isDirectory(path)) {
            try {
                return Files.list(path).map(FilePathTreeItem::new)
                        .collect(Collectors.toCollection(FXCollections::observableArrayList));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return FXCollections.observableArrayList();
    }

    public String getFullPath() {
        return fullPath;
    }

    public boolean isDirectory() {
        return isDirectory;
    }
}

虽然我不是特别喜欢他的方法,但我使它与您借用的示例非常接近。我确实清理了很多事件处理并更新了其中一些(使用 lambda 等)。但实际上 Strings 和 Paths 之间仍然存在太多的转换:如果你正在构建一个树,其中每个节点基本上代表一个 Path,那么你应该使用 TreeView<Path>TreeItem<Path> 等,不会不断地转换字符串和路径之间的所有内容。