首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

Java Swing 兩個 JFrame 互通:共享模型/控制器/事件總線三種模式

     編輯:程式設計疑難解答

需求:主視窗 (庫存查詢) 開啟子視窗 (高級查詢),子視窗組合 SQL 條件後傳回主視窗,主視窗據此刷新 `JTable`。本文提供三種耦合度遞減的實現模式。

模式一:直接持有引用(耦合度最高,最簡單)

public class MainFrame extends JFrame {
    private JTable table;
    private DefaultTableModel model;

    public void openAdvancedQuery() {
        AdvancedQueryDialog dialog = new AdvancedQueryDialog(this);  // 傳 this
        dialog.setVisible(true);
    }

    // 供子視窗呼叫
    public void refreshTable(String sql) {
        // 執行查詢、更新 model
        model.setRowCount(0);
        // ... 查詢資料庫填充 model ...
    }
}

public class AdvancedQueryDialog extends JDialog {
    private MainFrame parent;
    public AdvancedQueryDialog(MainFrame parent) {
        this.parent = parent;
        // 確定按鈕事件
        btnOk.addActionListener(e -> {
            String sql = buildSql();
            parent.refreshTable(sql);  // 直接呼叫父視窗方法
            dispose();
        });
    }
}

模式二:中央控制器/中介者(推薦,符合 MVC)

// 共享介面
public interface QueryListener {
    void onQueryBuilt(String sql);
}

// 控制器持有兩個視圖引用
public class AppController {
    private MainFrame mainFrame;
    private AdvancedQueryDialog queryDialog;

    public AppController() {
        mainFrame = new MainFrame(this);
        queryDialog = new AdvancedQueryDialog(this);
    }

    // 主視窗呼叫
    public void showAdvancedQuery() { queryDialog.setVisible(true); }

    // 子視窗呼叫(實作 QueryListener)
    public void onQueryBuilt(String sql) {
        mainFrame.refreshTable(sql);
        queryDialog.dispose();
    }
}

// 主視窗
public class MainFrame extends JFrame {
    private AppController controller;
    public MainFrame(AppController controller) { this.controller = controller; }
    // 開啟查詢按鈕
    btnAdvanced.addActionListener(e -> controller.showAdvancedQuery());
    public void refreshTable(String sql) { /* ... */ }
}

// 子視窗
public class AdvancedQueryDialog extends JDialog {
    private AppController controller;
    public AdvancedQueryDialog(AppController controller) { this.controller = controller; }
    btnOk.addActionListener(e -> controller.onQueryBuilt(buildSql()));
}

模式三:事件總線/觀察者模式(解耦最徹底)

// 事件類別
public class QueryBuiltEvent {
    public final String sql;
    public QueryBuiltEvent(String sql) { this.sql = sql; }
}

// 使用 Guava EventBus / Spring ApplicationEvent / 自訂簡易總線
public class EventBus {
    private static final List> subscribers = new CopyOnWriteArrayList<>();
    public static void subscribe(Consumer listener) { subscribers.add(listener); }
    public static void post(Object event) { subscribers.forEach(l -> l.accept(event)); }
}

// 子視窗發布事件
btnOk.addActionListener(e -> {
    EventBus.post(new QueryBuiltEvent(buildSql()));
    dispose();
});

// 主視窗訂閱事件(建構時註冊)
EventBus.subscribe(event -> {
    if (event instanceof QueryBuiltEvent qbe) {
        SwingUtilities.invokeLater(() -> refreshTable(qbe.sql));
    }
});

三種模式對比

模式耦合度優點缺點適用場景
直接引用高代碼最少、直觀難測試、難復用、循環依賴風險小工具、原型、兩視窗固定綁定
中央控制器中符合 MVC、易單元測試、邏輯集中控制器類別可能膨脹標準桌面應用、中型專案
事件總線低完全解耦、支援多訂閱者、易擴展流程隱式、除錯稍難大型模組化專案、插件架構

關鍵技術細節

  • **執行緒安全**:子視窗關閉後在 EDT 更新 UI → `SwingUtilities.invokeLater(() -> ...)`
  • **資料模型共享**:`DefaultTableModel` 可直接 `setRowCount(0)` + `addRow()` 刷新
  • **模態對話框**:`JDialog` 設 `setModal(true)` 阻塞主視窗,確保查詢完成再操作
  • **記憶體洩漏防護**:視窗關閉時 `removeListener` / `EventBus.unsubscribe`

根據專案規模選擇:小專案用「直接引用」,中型用「中央控制器」,大型模組化用「事件總線」。核心都是**主視窗暴露 `refreshTable(sql)` 方法,子視窗構建 SQL 後回呼**。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved