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

SWT/JFace FTP 客戶端完整專案實作:雙面板目錄瀏覽、拖曳下載與連線管理

     編輯:Java 相關知識

專案依賴與架構

此為一個基於 SWT/JFace + Apache Commons Net 的完整 FTP 客戶端範例。

  • 核心 Jar:org.eclipse.swt, org.eclipse.jface, org.eclipse.core.runtime, commons-net-ftp。
  • 架構模式:ApplicationWindow (主視窗) + TableViewer (JFace Viewer 框架) + SashForm (分割面板) + 背景執行緒處理網路 IO。
  • 關鍵類別:FTPWindow (主控制器), ConnectionDialog (連線設定), LocalDirectoryBrowser/RemoteDirectoryBrowser (Content/Label Provider), FileIconUtil (圖示快取)。

核心類別:FTPWindow (主視窗控制器)

UI 結構

  • ToolBar/Menu:連線/斷線、本地/遠端上一層、關於、離開。
  • SashForm (垂直):上方雙面板 (水平分割 本地/遠端),下方 StyledText 日誌區。
  • 本地面板:路徑標籤 + 上一層/瀏覽按鈕 + Table (名稱/大小/日期)。
  • 遠端面板:路徑標籤 + 上一層按鈕 + Table (名稱/大小/日期)。

關鍵功能實作

1. FTPClient 初始化與指令記錄

ftp = new FTPClient();
ftp.addProtocolCommandListener(new ProtocolCommandListener() {
    public void protocolCommandSent(ProtocolCommandEvent e) { logMessage("> " + e.getCommand(), false); }
    public void protocolReplyReceived(ProtocolCommandEvent e) { logMessage("< " + e.getMessage(), false); }
});

2. 連線流程

// Action: Connect
ftp.connect(host, port);
if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) throw new RuntimeException("Refused");
ftp.login(user, pass);
labelPathRemote.setText("Path: " + ftp.printWorkingDirectory());
remoteDirBrowser.setInput(ftp.listFiles()); // 觸發 Viewer 刷新

3. 拖曳下載 (Drag & Drop)

來源:RemoteDirectoryBrowser (Table) -> 目標:LocalDirectoryBrowser (Table)。

  • DragSource:檢查選取項目是否為檔案 (非目錄),event.data = fileName。
  • DropTarget:接收檔名,在本地目錄建立 FileOutputStream,呼叫 ftp.retrieveFile(fileName, stream) 下載。完成後 localDirBrowser.refresh()。

4. 雙擊進入目錄

  • 本地:File.isDirectory() -> setInput(dir) 更新路徑。
  • 遠端:FTPFile.isDirectory() -> ftp.changeWorkingDirectory(name) -> setInput(ftp.listFiles())。

輔助類別實作

ConnectionDialog (連線設定持久化)

繼承 Dialog,使用 DialogSettings 載入/儲存 XML (ftp.connection.xml) 記住上次輸入的 Host, Port, User, Pass。

FileIconUtil (系統圖示快取)

利用 Program.findProgram(extension) 取得系統關聯圖示,ImageRegistry 快取避免重複建立。支援 File 與 FTPFile 統一介面。

LocalDirectoryBrowser / RemoteDirectoryBrowser (JFace Viewer 封裝)

繼承 TableViewer,內建 IStructuredContentProvider (提供陣列資料) 與 ITableLabelProvider (提供圖示+三欄文字)。

// Local ContentProvider
public Object[] getElements(Object input) { return ((File)input).listFiles(); }
// LabelProvider Column 0: Icon, Name; Column 1: Size; Column 2: Date

完整主類別代碼 (FTPWindow.java 精簡版)

package swt_jface.demo13;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.commons.net.ProtocolCommandEvent;
import org.apache.commons.net.ProtocolCommandListener;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.eclipse.jface.action.*;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.dnd.*;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class FTPWindow extends ApplicationWindow {
    TableViewer localDirBrowser, remoteDirBrowser;
    Label labelPathLocal, labelPathRemote;
    StyledText textLog;
    ConnectionDialog connectionDialog;
    FTPClient ftp;
    ConnectionInfo connectionInfo;

    public FTPWindow(Shell parentShell) {
        super(parentShell);
        createActions();
        addStatusLine();
        addToolBar(SWT.FLAT);
        addMenuBar();
        ftp = new FTPClient();
        ftp.addProtocolCommandListener(new ProtocolCommandListener() {
            public void protocolCommandSent(ProtocolCommandEvent e) { logMessage("> " + e.getCommand(), false); }
            public void protocolReplyReceived(ProtocolCommandEvent e) { logMessage("< " + e.getMessage(), false); }
        });
    }

    // ... createActions() 定義 Action: connect, disconnect, upLocal, upRemote, browseLocal, about, exit ...
    // ... createMenuManager() / createToolBarManager() 組裝選單與工具列 ...

    @Override
    protected Control createContents(Composite parent) {
        Composite composite = new Composite(parent, SWT.NULL);
        composite.setLayout(new FillLayout());
        
        SashForm verticalForm = new SashForm(composite, SWT.VERTICAL);
        SashForm horizontalForm = new SashForm(verticalForm, SWT.HORIZONTAL);

        // --- Local Panel ---
        Composite localComp = createDirPanel(horizontalForm, "Local", true);
        // --- Remote Panel ---
        Composite remoteComp = createDirPanel(horizontalForm, "Remote", false);

        // --- Log Panel ---
        textLog = new StyledText(verticalForm, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
        
        verticalForm.setWeights(new int[]{4, 1}); // 目錄區:日誌區 = 4:1
        
        // 初始化本地根目錄
        localDirBrowser.setInput(File.listRoots()[0]);
        labelPathLocal.setText("Path: " + File.listRoots()[0]);
        
        dragNDropSupport(); // 啟用拖曳
        return composite;
    }

    private Composite createDirPanel(SashForm parent, String title, boolean isLocal) {
        Composite comp = new Composite(parent, SWT.NULL);
        comp.setLayout(new GridLayout());
        Group top = new Group(comp, SWT.NULL);
        top.setText(title);
        top.setLayout(new GridLayout(isLocal ? 3 : 2, false));
        top.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        
        Label pathLabel = new Label(top, SWT.NULL);
        pathLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        pathLabel.setText("Path: ");
        if (isLocal) labelPathLocal = pathLabel; else labelPathRemote = pathLabel;

        Button btnUp = new Button(top, SWT.PUSH);
        btnUp.setText("Up");
        btnUp.addListener(SWT.Selection, e -> {
            if (isLocal) actionUpLocalDir.run();
            else actionUpRemoteDir.run();
        });
        
        if (isLocal) {
            Button btnBrowse = new Button(top, SWT.PUSH);
            btnBrowse.setText("Browse...");
            btnBrowse.addListener(SWT.Selection, e -> actionBrowseLocalDir.run());
        }

        Table table = new Table(comp, SWT.BORDER);
        new TableColumn(table, SWT.LEFT).setText("Name"); table.getColumn(0).setWidth(200);
        new TableColumn(table, SWT.NULL).setText("Size"); table.getColumn(1).setWidth(100);
        new TableColumn(table, SWT.NULL).setText("Date"); table.getColumn(2).setWidth(100);
        table.setHeaderVisible(true);
        table.setLayoutData(new GridData(GridData.FILL_BOTH));

        if (isLocal) {
            localDirBrowser = new LocalDirectoryBrowser(table);
            table.addListener(SWT.MouseDoubleClick, e -> {
                File f = (File) ((IStructuredSelection)localDirBrowser.getSelection()).getFirstElement();
                if (f != null && f.isDirectory()) { localDirBrowser.setInput(f); labelPathLocal.setText("Path: " + f); }
            });
        } else {
            remoteDirBrowser = new RemoteDirectoryBrowser(table);
            table.addListener(SWT.MouseDoubleClick, e -> {
                FTPFile f = (FTPFile) ((IStructuredSelection)remoteDirBrowser.getSelection()).getFirstElement();
                if (f != null && f.isDirectory()) {
                    try { ftp.changeWorkingDirectory(f.getName()); 
                          labelPathRemote.setText("Path: " + ftp.printWorkingDirectory());
                          remoteDirBrowser.setInput(ftp.listFiles());
                    } catch (IOException ex) { logError(ex.toString()); }
                }
            });
        }
        return comp;
    }

    // ... dragNDropSupport(), logMessage(), logError(), main() 等輔助方法 ...
}

關鍵技術難點與解決方案

挑戰解決方案
網路阻塞 UI所有 FTP 指令 (connect, listFiles, retrieveFile) 均在 UI 執行緒執行會卡死。本範例簡化為直接在 UI 執行緒呼叫,**生產環境必須移至背景執行緒** (如 Thread + asyncExec 或 Jobs API)。
被動/主動模式預設主動模式。若需被動模式 (PASV) 穿透防火牆:ftp.enterLocalPassiveMode()。
編碼問題中文檔名需設定 ftp.setControlEncoding("UTF-8") (Commons Net 3.0+)。
圖示效能FileIconUtil 使用 ImageRegistry 快取副檔名對應圖示,避免重複 Program.findProgram 呼叫。
資源洩漏FileOutputStream、FTPClient、Image、DragSource/DropTarget 皆需在 DisposeListener 中釋放。

擴充建議

  1. 上傳功能:反向拖曳 (Local -> Remote) + ftp.storeFile()。
  2. 進度條:利用 org.apache.commons.net.ftp.FTPClientUtil 或自行實作 CopyStreamListener 監聽傳輸進度更新 UI。
  3. 站台管理器:使用 DialogSettings 或 XML 儲存多組連線設定,下拉選單快速切換。
  4. 非同步框架:引入 Eclipse Jobs API (Job, IProgressMonitor) 統一管理背景任務、取消、排程。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved