首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Swing 實現 Windows 檔案總管樹狀視圖

     編輯:Java 相關知識

本文展示如何使用 Java Swing 的 JTree 元件,結合 FileSystemView 實現一個類似 Windows 檔案總管的樹狀目錄瀏覽器。完整代碼包含自定義樹模型、節點渲染器、展開/摺疊監聽等核心功能。

核心架構設計

  • FileTree:繼承 JTree,作為主視圖元件
  • FileSystemModel:實作 TreeModel,提供樹狀資料模型
  • I_fileSystem:介面定義檔案系統節點通用行為
  • FolderNode:實作 I_fileSystem,表示資料夾節點,支援延遲加載子節點
  • FolderRenderer:繼承 DefaultTreeCellRenderer,自定義節點圖示顯示

完整代碼實現

package tl.exercise.swing;

import java.awt.Component;
import java.io.File;
import java.util.Vector;
import javax.swing.Icon;
import javax.swing.JTree;
import javax.swing.event.*;
import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.*;

public class FileTree extends JTree {
    private static final long serialVersionUID = 0;
    private FileList theList;

    public FileTree(FileList list) {
        this.theList = list;
        // 設定樹模型,根節點為桌面
        setModel(new FileSystemModel(new FolderNode()));
        // 自定義渲染器顯示系統圖示
        this.setCellRenderer(new FolderRenderer());
        
        // 選擇監聽
        addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent tse) {
                // 可在此處理選擇事件
            }
        });
        this.setSelectionRow(0); // 預設選中根節點
    }

    // 轉發選擇事件給 FileList
    public void fireValueChanged(TreeSelectionEvent tse) {
        TreePath tp = tse.getNewLeadSelectionPath();
        Object o = tp.getLastPathComponent();
        theList.fireTreeSelectionChanged((FolderNode) o);
    }

    @Override
    public void fireTreeCollapsed(TreePath path) {
        super.fireTreeCollapsed(path);
        TreePath curpath = getSelectionPath();
        if (path.isDescendant(curpath)) {
            setSelectionPath(path);
        }
    }

    @Override
    public void fireTreeWillExpand(TreePath path) {
        System.out.println("Path will expand: " + path);
    }

    @Override
    public void fireTreeWillCollapse(TreePath path) {
        System.out.println("Path will collapse: " + path);
    }

    // 內部類:樹模型
    class FileSystemModel implements TreeModel {
        private I_fileSystem theRoot;
        private char fileType = I_fileSystem.DIRECTORY;

        public FileSystemModel(I_fileSystem fs) {
            this.theRoot = fs;
        }

        @Override public Object getRoot() { return theRoot; }
        
        @Override public Object getChild(Object parent, int index) {
            return ((I_fileSystem) parent).getChild(fileType, index);
        }
        
        @Override public int getChildCount(Object parent) {
            return ((I_fileSystem) parent).getChildCount(fileType);
        }
        
        @Override public boolean isLeaf(Object node) {
            return ((I_fileSystem) node).isLeaf(fileType);
        }
        
        @Override public int getIndexOfChild(Object parent, Object child) {
            return ((I_fileSystem) parent).getIndexOfChild(fileType, child);
        }
        
        @Override public void valueForPathChanged(TreePath path, Object newValue) {}
        @Override public void addTreeModelListener(TreeModelListener l) {}
        @Override public void removeTreeModelListener(TreeModelListener l) {}
    }

    // 介面:檔案系統節點契約
    interface I_fileSystem {
        char DIRECTORY = 'D';
        char FILE = 'F';
        char ALL = 'A';
        
        Icon getIcon();
        I_fileSystem getChild(char fileType, int index);
        int getChildCount(char fileType);
        boolean isLeaf(char fileType);
        int getIndexOfChild(char fileType, Object child);
    }

    // 實作:資料夾節點(核心邏輯)
    class FolderNode implements I_fileSystem {
        private static FileSystemView fsView = FileSystemView.getFileSystemView();
        private static boolean showHidden = true;
        private File theFile;
        private Vector<File> all = new Vector<>();
        private Vector<File> folder = new Vector<>();

        public void setShowHidden(boolean ifshow) { showHidden = ifshow; }

        @Override public Icon getIcon() { return fsView.getSystemIcon(theFile); }
        
        @Override public String toString() { 
            return fsView.getSystemDisplayName(theFile); 
        }

        // 根節點建構子:預設為桌面
        public FolderNode() {
            theFile = fsView.getHomeDirectory();
            prepareChildren();
        }

        // 子節點建構子
        private FolderNode(File file) {
            this.theFile = file;
            prepareChildren();
        }

        // 掃描子目錄/檔案
        private void prepareChildren() {
            File[] files = fsView.getFiles(theFile, showHidden);
            for (File f : files) {
                all.add(f);
                // 只加入資料夾(排除捷徑 .lnk)
                if (f.isDirectory() && !f.toString().toLowerCase().endsWith(".lnk")) {
                    folder.add(f);
                }
            }
        }

        @Override public I_fileSystem getChild(char fileType, int index) {
            switch (fileType) {
                case DIRECTORY: return new FolderNode(folder.get(index));
                case ALL: return new FolderNode(all.get(index));
                default: return null;
            }
        }

        @Override public int getChildCount(char fileType) {
            switch (fileType) {
                case DIRECTORY: return folder.size();
                case ALL: return all.size();
                default: return -1;
            }
        }

        @Override public boolean isLeaf(char fileType) {
            switch (fileType) {
                case DIRECTORY: return folder.isEmpty();
                case ALL: return all.isEmpty();
                case FILE: return true;
                default: return true;
            }
        }

        @Override public int getIndexOfChild(char fileType, Object child) {
            if (!(child instanceof FolderNode)) return -1;
            File childFile = ((FolderNode) child).theFile;
            switch (fileType) {
                case DIRECTORY: return folder.indexOf(childFile);
                case ALL: return all.indexOf(childFile);
                default: return -1;
            }
        }
    }

    // 節點渲染器:使用系統原生圖示
    class FolderRenderer extends DefaultTreeCellRenderer {
        private static final long serialVersionUID = 1L;

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value,
                boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
            I_fileSystem node = (I_fileSystem) value;
            Icon icon = node.getIcon();
            setLeafIcon(icon);
            setOpenIcon(icon);
            setClosedIcon(icon);
            return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
        }
    }
}

關鍵技術點解析

1. FileSystemView 獲取系統原生資訊

FileSystemView fsView = FileSystemView.getFileSystemView();
// 獲取桌面目錄(根節點)
File desktop = fsView.getHomeDirectory();
// 獲取系統圖示
Icon icon = fsView.getSystemIcon(file);
// 獲取系統顯示名稱(含本地化)
String displayName = fsView.getSystemDisplayName(file);
// 列舉子項目(含隱藏檔選項)
File[] children = fsView.getFiles(parent, showHidden);

2. 延遲加載與記憶體考量

代碼中 FolderNode 在建構時即掃描所有子項目並存入 Vector。作者在註解中提到:

「為了速度,每個模型實例都包含子目錄的檔案物件,目前沒有釋放方法,直到程式結束。如果檔案系統夠大且 JVM 記憶體設定太小,可能會導致記憶體不足。」

改進方向:實作真正的延遲加載,僅在節點首次展開時掃描子目錄,並在摺疊時釋放子節點引用。

3. 自定義樹模型 vs DefaultTreeModel

繼承 DefaultTreeModel 並使用 DefaultMutableTreeNode 是更簡單的做法,但自定義 TreeModel 介面可:

  • 完全控制節點資料結構
  • 支援非樹狀的底層資料(如資料庫、遠程 API)
  • 避免將檔案物件包裝在 DefaultMutableTreeNode 中的額外開銷

執行與測試

需搭配 FileList 類(未在代碼中提供,需自行實作)使用。最小啟動範例:

import javax.swing.*;

public class FileTreeDemo {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("檔案總管樹狀視圖");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new JScrollPane(new FileTree(null))); // 傳入 null 或實作 FileList
            frame.setSize(400, 600);
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

常見問題與擴展

總結

這個範例展示了 Swing JTree 進階用法:自定義樹模型、系統原生圖示整合、檔案系統遍歷。雖然代碼較早期(使用 Vector、未使用 Generics 完整泛型),但架構清晰,是學習 Swing 模型-視圖分離與檔案系統 GUI 開發的優秀教材。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
需求解決方案
顯示檔案大小、修改時間擴展 I_fileSystem 介面,在 FolderNode 中快取 File 物件屬性
右鍵選單(複製、刪除、重命名)為 JTree 添加 MouseListener,彈出 JPopupMenu
拖放操作實作 TransferHandler,支援 DnD
過濾特定副檔名在 prepareChildren() 中添加過濾邏輯
多根節點(我的電腦、網路位置)修改 FileSystemModel.getRoot() 返回虛擬根節點,子節點掛載各邏輯磁碟