new ParentForm().TextBox.Text 失效?new ParentForm() 建立的是全新實例,而非目前顯示的那個父視窗實例。兩者在記憶體中完全無關,自然讀不到使用者輸入的資料。正確關鍵在於取得現有父視窗實例的參考。
明確宣告依賴,編譯期檢查,單元測試友善,符合「依賴倒置原則」。
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(); // 非模態
}
}
子視窗先建立,後設定父視窗參考。適用於 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 介面) | 中 | 高 |
| 資料流向 | 單向 (父→子) | 單向 (父→子) | 雙向 |
若不想子視窗直接依賴具體 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 後呼叫初始化方法 |
new ParentForm() 或 Application.OpenForms["ParentForm"] 取得父視窗——前者建立新實例,後者依賴字串鍵值且不支援多實例場景。掌握這三種模式,WinForms 視窗間通訊即可從「湊合能用」進化為「架構清晰、易維護、可測試」的專業級實作。