使用 JavaFX 構建一個輕量級文件資源管理器,左側 TreeView 顯示目錄樹結構,右側 TableView 顯示當前目錄下文件詳情 (名稱、類型、修改時間、大小)。採用惰性加載優化大目錄性能。
TreeItem<File>,重寫 getChildren() 與 isLeaf() 實現按需掃描子目錄。
package com.example.filemanager;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
public class FileDetail {
private final String fileName;
private final String type;
private final String lastModified;
private final long length;
private final boolean isDirectory;
public FileDetail(File file) {
this.fileName = file.getName();
this.isDirectory = file.isDirectory();
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm");
this.lastModified = fmt.format(new Date(file.lastModified()));
if (isDirectory) {
this.type = "文件夾";
this.length = 0;
} else {
this.length = file.length();
this.type = String.format("%.1f KB", length / 1024.0);
}
}
// Getter 方法 (PropertyValueFactory 需要)
public String getFileName() { return fileName; }
public String getType() { return type; }
public String getLastModified() { return lastModified; }
public long getLength() { return length; }
public boolean isDirectory() { return isDirectory; }
}
package com.example.filemanager;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TreeItem;
import java.io.File;
public class FileTreeItem extends TreeItem {
private boolean isFirstTimeChildren = true;
private boolean isFirstTimeLeaf = true;
public FileTreeItem(File file) {
super(file);
// 設置圖標 (可選)
// setGraphic(new ImageView(isDirectory ? folderIcon : fileIcon));
}
@Override
public ObservableList> getChildren() {
if (isFirstTimeChildren) {
isFirstTimeChildren = false;
super.getChildren().setAll(buildChildren(this));
}
return super.getChildren();
}
@Override
public boolean isLeaf() {
if (isFirstTimeLeaf) {
isFirstTimeLeaf = false;
File f = getValue();
isLeaf = f.isFile(); // 文件是葉子節點,目錄不是
}
return isLeaf;
}
private ObservableList> buildChildren(TreeItem item) {
File dir = item.getValue();
if (dir != null && dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
ObservableList> children = FXCollections.observableArrayList();
for (File child : files) {
// 可選: 過濾隱藏文件、系統文件
if (!child.isHidden()) {
children.add(new FileTreeItem(child));
}
}
return children;
}
}
return FXCollections.emptyObservableList();
}
}
package com.example.filemanager;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
import java.io.File;
public class FileManagerApp extends Application {
private final ObservableList tableData = FXCollections.observableArrayList();
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("JavaFX 文件資源管理器");
// 根節點: 計算機名稱
String computerName = System.getenv("COMPUTERNAME") != null ?
System.getenv("COMPUTERNAME") : "This PC";
TreeItem rootItem = new TreeItem<>(new File(computerName));
// 添加所有根目錄 (Windows: C:\, D:\... ; Linux: /)
for (File root : File.listRoots()) {
rootItem.getChildren().add(new FileTreeItem(root));
}
rootItem.setExpanded(true); // 默認展開根節點
// TreeView
TreeView treeView = new TreeView<>(rootItem);
treeView.setShowRoot(true);
treeView.setPrefWidth(300);
// TableView
TableView tableView = new TableView<>(tableData);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
TableColumn colName = new TableColumn<>("名稱");
colName.setCellValueFactory(new PropertyValueFactory<>("fileName"));
colName.setPrefWidth(200);
TableColumn colType = new TableColumn<>("類型");
colType.setCellValueFactory(new PropertyValueFactory<>("type"));
colType.setPrefWidth(100);
TableColumn colModified = new TableColumn<>("修改時間");
colModified.setCellValueFactory(new PropertyValueFactory<>("lastModified"));
colModified.setPrefWidth(180);
TableColumn colSize = new TableColumn<>("大小");
colSize.setCellValueFactory(new PropertyValueFactory<>("length"));
colSize.setPrefWidth(100);
tableView.getColumns().addAll(colName, colType, colModified, colSize);
// 選中事件: 更新右側表格
treeView.getSelectionModel().selectedItemProperty()
.addListener((obs, oldSel, newSel) -> {
if (newSel != null) {
ObservableList> kids = newSel.getChildren();
ObservableList details = FXCollections.observableArrayList();
for (TreeItem kid : kids) {
details.add(new FileDetail(kid.getValue()));
}
tableData.setAll(details);
}
});
// 布局: HBox 雙欄自適應
HBox root = new HBox();
HBox.setHgrow(treeView, Priority.ALWAYS);
HBox.setHgrow(tableView, Priority.ALWAYS);
root.getChildren().addAll(treeView, tableView);
Scene scene = new Scene(root, 900, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
| 技術點 | 說明 |
|---|---|
| TreeItem 惰性加載 | 重寫 getChildren() 與 isLeaf(),僅在展開節點時掃描磁盤,避免啟動時遞歸掃描全盤導致卡頓 |
| PropertyValueFactory | 依據 JavaBean 命名慣例 (getXxx) 自動綁定表格列,要求模型類有公共 Getter |
| ObservableList | FXCollections 可觀察列表,自動通知 UI 更新,無需手動刷新 |
| ChangeListener | 監聽 TreeView 選中項變化,觸發右側數據重載 |
| HBox + Priority.ALWAYS | 雙欄自適應寬度,拖動窗口時左右面板同步縮放 |
Task<ObservableList> 後台線程掃描大目錄,完成後 Platform.runLater 更新 UI,防止界面凍結。FileSystemView.getFileSystemView().getSystemIcon(file) 獲取系統原生圖標 (需 Swing 互操作) 或第三方圖標庫 (如 FontAwesomeFX)。FilteredList 包裝)。java.nio.file.Files API)。
左側目錄樹,右側文件詳情表格,支援多級展開、欄位排序、自適應佈局
JavaFX 的 TreeView + TableView + ObservableList 組合極適合構建文件瀏覽類應用。核心在於 FileTreeItem 惰性加載 解決性能問題,PropertyValueFactory 簡化數據綁定。該架構易於擴展為功能完整的資源管理器,也是學習 JavaFX MVC 模式、屬性綁定、集合觀察的優秀實戰項目。