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

C# WinForms 進階:子視窗存取父視窗控制項與屬性的三種標準模式

     編輯:關於C語言

核心問題:為什麼 new ParentForm().TextBox.Text 失效?

new ParentForm() 建立的是全新實例,而非目前顯示的那個父視窗實例。兩者在記憶體中完全無關,自然讀不到使用者輸入的資料。正確關鍵在於取得現有父視窗實例的參考。

模式一:建構子注入 (Constructor Injection) —— 最推薦

明確宣告依賴,編譯期檢查,單元測試友善,符合「依賴倒置原則」。

子視窗


public partial class ChildForm : Form
{
    private readonly ParentForm _parent; // 唯讀欄位,保證不可變

    // 強制要求傳入父視窗實例
    public ChildForm(ParentForm parent)
    {
        InitializeComponent();
        _parent = parent ?? throw new ArgumentNullException(nameof(parent));
    }

    private void ChildForm_Load(object sender, EventArgs e)
    {
        // 安全存取父視窗公開成員
        txtSearch.Text = _parent.SearchKeyword; // 假設父視窗有此公開屬性
        // 或直接存取公用控制項 (需將控制項 Modifiers 設為 Public/Internal)
        // txtSearch.Text = _parent.txtKeyword.Text; 
    }
}

父視窗開啟子視窗


public partial class ParentForm : Form
{
    // 對外公開的屬性 (封裝控制項細節)
    public string SearchKeyword => txtKeyword.Text;

    private void btnOpenChild_Click(object sender, EventArgs e)
    {
        using var child = new ChildForm(this); // 傳遞 this (目前實例)
        child.ShowDialog(); // 模態對話框
        // 或 child.Show(); // 非模態
    }
}

模式二:屬性注入 (Property Injection) —— 適用非模態/延遲建立

子視窗先建立,後設定父視窗參考。適用於 MDI 子視窗、動態建立的非模態視窗。

子視窗


public partial class ChildForm : Form
{
    // 公開屬性,供父視窗在 Show 前設定
    public ParentForm ParentForm { get; set; }

    private void btnUseParentData_Click(object sender, EventArgs e)
    {
        if (ParentForm == null) return; // 防禦性檢查
        MessageBox.Show($"父視窗關鍵字:{ParentForm.SearchKeyword}");
    }
}

父視窗


private void btnOpenNonModal_Click(object sender, EventArgs e)
{
    var child = new ChildForm();
    child.ParentForm = this; // 關鍵:建立反向參考
    child.Show(); // 非模態,父視窗繼續可操作
}

模式三:事件/回調模式 —— 解耦合,雙向通訊首選

子視窗不直接持有父視窗參考,而是定義事件,父視窗訂閱。符合「好萊塢法則」,降低耦合度。

子視窗定義事件


public partial class ChildForm : Form
{
    // 1. 定義事件參數類別
    public class SearchRequestEventArgs : EventArgs
    {
        public string Keyword { get; set; }
    }

    // 2. 宣告事件
    public event EventHandler SearchRequested;

    // 3. 觸發事件 (受保護虛擬方便衍生類別覆寫)
    protected virtual void OnSearchRequested(string keyword)
    {
        SearchRequested?.Invoke(this, new SearchRequestEventArgs { Keyword = keyword });
    }

    private void btnSearch_Click(object sender, EventArgs e)
    {
        OnSearchRequested(txtLocalKeyword.Text);
    }
}

父視窗訂閱並回應


private void btnOpenChild_Click(object sender, EventArgs e)
{
    using var child = new ChildForm();
    // 訂閱子視窗事件
    child.SearchRequested += (s, args) => 
    {
        // 父視窗執行查詢並回傳結果給子視窗 (若需)
        var results = DoSearch(args.Keyword);
        // 子視窗需暴露接收結果的方法或屬性
        child.DisplayResults(results); 
    };
    child.ShowDialog();
}

模式對比與選型指南

維度建構子注入屬性注入事件/回調
耦合度中 (知父類型)中 (知父類型)低 (只知事件簽章)
適用視窗類型模態對話框非模態/MDI 子視窗雙向通訊/多對一
生命週期管理父控制子較鬆散雙向獨立
測試容易度高 (可 Mock 介面)中高
資料流向單向 (父→子)單向 (父→子)雙向

進階:介面抽象解耦 (Dependency Inversion)

若不想子視窗直接依賴具體 ParentForm,定義介面:


public interface ISearchProvider
{
    string SearchKeyword { get; }
    event EventHandler SearchCompleted;
    void RequestSearch(string keyword);
}

public partial class ParentForm : Form, ISearchProvider { ... }

public partial class ChildForm : Form
{
    private readonly ISearchProvider _provider;
    public ChildForm(ISearchProvider provider) { _provider = provider; }
}

常見錯誤與避坑

錯誤後果修正
控制項 Modifiers 設為 Private (預設)子視窗無法直接存取改為 Public/Internal,或暴露屬性/方法封裝
跨執行緒存取 UI 控制項InvalidOperationException用 Invoke/BeginInvoke 封送至 UI 執行緒
子視窗持有父視窗導致記憶體洩漏父視窗無法 GC非模態視窗關閉時置 null;或用弱事件模式
在子視窗建構子中存取父視窗控制項父視窗 InitializeComponent 可能未完成移至 Load 事件或父視窗 Show 後呼叫初始化方法

總結

  • 模態對話框、單向資料流: 首選 建構子注入,清晰、安全、可測試。
  • 非模態視窗、延遲綁定: 使用 屬性注入,注意置 null 釋放參考。
  • 雙向通訊、多子視窗、低耦合: 採用 事件/回調模式,配合介面抽象達到完全解耦。
  • 永遠不要 用 new ParentForm() 或 Application.OpenForms["ParentForm"] 取得父視窗——前者建立新實例,後者依賴字串鍵值且不支援多實例場景。

掌握這三種模式,WinForms 視窗間通訊即可從「湊合能用」進化為「架構清晰、易維護、可測試」的專業級實作。

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