WinForms 開發常遇到子視窗需操作父視窗控制項、或父視窗操作子視窗控制項的需求。本文介紹兩種核心模式讓子視窗取得父視窗參考,並強調以公用屬性/方法封裝控制項操作,而非直接暴露控制項公開欄位。
適用於應用程式僅有一個主視窗實例的情境(典型 MDI 或單視窗應用)。
public partial class MainForm : Form
{
// 靜態屬性保存當前實例
public static MainForm CurrentInstance { get; private set; }
public MainForm()
{
InitializeComponent();
CurrentInstance = this; // 建構子中指派
}
// 封裝控制項操作為公用方法/屬性
public string StatusText
{
get => lblStatus.Text;
set => lblStatus.Text = value;
}
public void RefreshDataGrid()
{
dataGridView1.DataSource = GetLatestData();
}
}
public partial class ChildForm : Form
{
public ChildForm()
{
InitializeComponent();
}
private void btnUpdateParent_Click(object sender, EventArgs e)
{
// 直接透過靜態屬性存取父視窗
if (MainForm.CurrentInstance != null)
{
MainForm.CurrentInstance.StatusText = "子視窗更新於 " + DateTime.Now;
MainForm.CurrentInstance.RefreshDataGrid();
}
}
}
適用於多主視窗、多子視窗、或需明確父子關係的情境。更符合依賴注入原則,易於單元測試。
public partial class ChildForm : Form
{
private readonly MainForm _parentForm;
// 建構子要求傳入父視窗
public ChildForm(MainForm parentForm)
{
InitializeComponent();
_parentForm = parentForm ?? throw new ArgumentNullException(nameof(parentForm));
}
private void btnNotifyParent_Click(object sender, EventArgs e)
{
_parentForm.StatusText = $"子視窗 {this.Text} 通知於 {DateTime.Now}";
_parentForm.RefreshDataGrid();
}
}
public partial class MainForm : Form
{
private void btnOpenChild_Click(object sender, EventArgs e)
{
// 傳入 this 給子視窗建構子
var child = new ChildForm(this);
child.Show(); // 或 ShowDialog()
}
}
// 父視窗
public Button btnSave; // 破壞封裝,子視窗可任意修改任何屬性
// 子視窗
parent.btnSave.Visible = false;
parent.btnSave.Text = "Hacked!";
parent.btnSave.Click += MaliciousHandler();
// 父視窗
public bool CanSave
{
get => btnSave.Enabled;
set => btnSave.Enabled = value;
}
public void TriggerSave() => btnSave.PerformClick();
// 子視窗
parent.CanSave = false; // 語意清晰
parent.TriggerSave(); // 行為明確
| 維度 | 靜態單例 | 建構子注入 |
|---|---|---|
| 適用場景 | 單一主視窗、全域存取點 | 多視窗、明確父子關係、可測試性 |
| 耦合度 | 高(子視窗依賴具體父視窗類別) | 中(可抽象介面降低耦合) |
| 生命週期 | 父視窗存活期間全域可用 | 隨子視窗實例建立/銷毀 |
| 執行緒安全 | 需注意跨執行緒存取 | 同樣需注意 |
| 單元測試 | 難以模擬 | 易於注入 Mock 父視窗 |
// 定義父視窗契約
public interface IParentForm
{
string StatusText { get; set; }
void RefreshDataGrid();
event Action DataRefreshed;
}
// 父視窗實作介面
public partial class MainForm : Form, IParentForm { ... }
// 子視窗依賴介面而非具體類別
public partial class ChildForm : Form
{
private readonly IParentForm _parent;
public ChildForm(IParentForm parent) { _parent = parent; }
}
父子視窗互操作核心在於如何取得參考(靜態單例 vs 建構子注入)與如何封裝操作(公開語意化屬性/方法 vs 暴露控制項)。建議優先採用建構子注入 + 介面契約 + 封裝操作,兼顧架構清晰、可測試性與封裝性。