文件複製操作若無進度提示,用戶體驗極差。本文完整實現基於 Swing 的文件複製進度對話框,採用 ProgressMonitorInputStream 內建組件,結合多線程技術避免界面凍結,提供可直接整合的完整代碼。
採用雙窗體架構:主窗體 UserMonitorFrame 負責用戶交互(選擇源文件/目標目錄、啟動複製),進度監控窗體 ProgressMonitorTest 封裝複製邏輯與進度顯示。複製操作在獨立線程執行,不阻塞 EDT (Event Dispatch Thread)。
ProgressMonitorInputStream 會自動監控輸入流讀取進度,當預估耗時超過 2 秒自動彈出進度對話框,支援用戶取消操作。
public class ProgressMonitorTest {
public void useProgressMonitor(JFrame frame, String copyPath, String newPath) {
try {
File file = new File(copyPath);
File newFile = new File(newPath);
try (FileOutputStream fop = new FileOutputStream(newFile);
FileInputStream in = new FileInputStream(file);
ProgressMonitorInputStream pm = new ProgressMonitorInputStream(
frame, "文件讀取中,請稍後...", in)) {
byte[] bytes = new byte[1024];
int c;
while ((c = pm.read(bytes)) != -1) {
fop.write(bytes, 0, c);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
實現 Runnable 接口,在 run() 方法中啟動複製任務。界面包含源文件選擇、目標目錄選擇、開始複製三個核心控件,使用 JFileChooser 實現文件/目錄選擇對話框。
public class UserMonitorFrame extends JFrame implements Runnable {
private JPanel contentPane;
private JTextField textField; // 源文件路徑
private JTextField textField_1; // 目標目錄路徑
private File file;
public UserMonitorFrame() {
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 501, 184);
setTitle("文件複製進度條演示");
getContentPane().setLayout(null);
// 源文件選擇區域
JLabel label = new JLabel("文件地址:");
label.setBounds(10, 10, 70, 15);
getContentPane().add(label);
textField = new JTextField();
textField.setBounds(90, 7, 300, 21);
getContentPane().add(textField);
textField.setColumns(10);
JButton btnSelectFile = new JButton("選擇文件");
btnSelectFile.addActionListener(e -> doSelectFile());
btnSelectFile.setBounds(400, 6, 93, 23);
getContentPane().add(btnSelectFile);
// 目標目錄選擇區域
JLabel label_1 = new JLabel("複製地址:");
label_1.setBounds(10, 40, 70, 15);
getContentPane().add(label_1);
textField_1 = new JTextField();
textField_1.setBounds(90, 38, 300, 21);
getContentPane().add(textField_1);
textField_1.setColumns(10);
JButton btnSelectDir = new JButton("選擇地址");
btnSelectDir.addActionListener(e -> doSelectDir());
btnSelectDir.setBounds(400, 39, 93, 23);
getContentPane().add(btnSelectDir);
// 開始複製按鈕
JButton btnCopy = new JButton("開始複製");
btnCopy.addActionListener(e -> doCopy());
btnCopy.setBounds(182, 69, 93, 23);
getContentPane().add(btnCopy);
}
// 選擇源文件
protected void doSelectFile() {
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
file = chooser.getSelectedFile();
textField.setText(file.toString());
}
}
// 選擇目標目錄
protected void doSelectDir() {
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
file = chooser.getSelectedFile();
textField_1.setText(file.toString());
}
}
// 啟動複製線程
protected void doCopy() {
new Thread(this).start();
}
@Override
public void run() {
ProgressMonitorTest test = new ProgressMonitorTest();
String sourcePath = textField.getText();
String targetDir = textField_1.getText();
// 保持原文件名
String fileName = sourcePath.substring(sourcePath.lastIndexOf(File.separator));
String targetPath = targetDir + fileName;
test.useProgressMonitor(this, sourcePath, targetPath);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
try {
UserMonitorFrame frame = new UserMonitorFrame();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
| 技術組件 | 作用 | 關鍵配置 |
|---|---|---|
| ProgressMonitorInputStream | 自動進度監控與對話框顯示 | 2秒延遲自動彈出、支援用戶取消 |
| SwingUtilities.invokeLater | 確保 GUI 創建在 EDT 執行 | 線程安全初始化 |
| Runnable + Thread | 耗時操作移出 EDT | 防止界面凍結、支援取消響應 |
| JFileChooser | 原生文件/目錄選擇對話框 | FILES_ONLY / DIRECTORIES_ONLY 模式 |
| try-with-resources | 自動資源管理 | JDK 7+ 語法糖,保證流關閉 |