在設計複雜表單時,預先在設計器拖放大量控制項不僅畫面擁擠,除錯也較困難。動態建立控制項可依使用者操作、資料筆數、權限等條件即時產生 UI,釋放資源更徹底。本文以建立 Button 與 TextBox 為例,完整說明關鍵技術:Boxing/Unboxing (裝箱/拆箱)、事件綁定與 sender 識別、控制項屬性初始化。
int、bool、struct、enum 等,直接儲存資料於堆疊。object、class、interface、delegate、string、array,堆疊存指標,實體在堆積。事件處理常見簽名 void Handler(object sender, EventArgs e) 中,sender 為參考型別,需拆箱回原控制項型別才能存取專屬屬性。
using System;
using System.Drawing;
using System.Windows.Forms;
public class DynamicControlForm : Form
{
private Button btnAddButton;
private Button btnAddTextBox;
private int _buttonCount = 0;
private int _textBoxCount = 0;
private int _nextButtonTop;
private int _nextTextBoxTop;
public DynamicControlForm()
{
InitializeComponent();
_nextButtonTop = btnAddButton.Bottom + 5;
_nextTextBoxTop = btnAddTextBox.Bottom + 5;
}
private void InitializeComponent()
{
this.Text = "動態建立控制項示範";
this.Size = new Size(400, 500);
this.StartPosition = FormStartPosition.CenterScreen;
btnAddButton = new Button { Text = "新增 Button", Top = 10, Left = 10, Width = 120, Height = 30 };
btnAddButton.Click += BtnAddButton_Click;
this.Controls.Add(btnAddButton);
btnAddTextBox = new Button { Text = "新增 TextBox", Top = 10, Left = 150, Width = 120, Height = 30 };
btnAddTextBox.Click += BtnAddTextBox_Click;
this.Controls.Add(btnAddTextBox);
}
// === 新增 Button ===
private void BtnAddButton_Click(object sender, EventArgs e)
{
_buttonCount++;
Button btn = new Button
{
Name = $"btnDynamic_{_buttonCount}",
Text = $"Button {_buttonCount}",
Left = btnAddButton.Left,
Top = _nextButtonTop,
Width = 120,
Height = 30,
Tag = _buttonCount // 可攜帶自訂資料
};
// 綁定三個事件,共用處理函式
btn.MouseEnter += Control_MouseEnter;
btn.MouseLeave += Control_MouseLeave;
btn.Click += Control_Click;
this.Controls.Add(btn);
_nextButtonTop += btn.Height + 5;
}
// === 新增 TextBox ===
private void BtnAddTextBox_Click(object sender, EventArgs e)
{
_textBoxCount++;
TextBox txt = new TextBox
{
Name = $"txtDynamic_{_textBoxCount}",
Text = $"TextBox {_textBoxCount}",
Left = btnAddTextBox.Left,
Top = _nextTextBoxTop,
Width = 150,
Height = 25
};
txt.Click += Control_Click; // 共用 Click 處理
this.Controls.Add(txt);
_nextTextBoxTop += txt.Height + 5;
}
// === 事件處理:拆箱識別控制項 ===
private void Control_MouseEnter(object sender, EventArgs e)
{
if (sender is Button btn) // 模式比對 (C# 7+),自動拆箱
btn.BackColor = Color.LightBlue;
}
private void Control_MouseLeave(object sender, EventArgs e)
{
if (sender is Button btn)
btn.BackColor = SystemColors.Control;
}
private void Control_Click(object sender, EventArgs e)
{
string msg;
if (sender is Button btn)
msg = $"按鈕 [{btn.Text}] 被點擊";
else if (sender is TextBox txt)
msg = $"文字方塊 [{txt.Text}] 被點擊";
else
msg = "未知控制項";
MessageBox.Show(msg, "動態事件", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
// === 替代寫法:針對 TextBox 獨立事件 ===
private void Txt_Click(object sender, EventArgs e)
{
if (sender is TextBox txt)
MessageBox.Show($"TextBox 專屬事件: {txt.Text}");
}
}
this.Controls.Add(control) 將控制項加入表單視覺樹。移除請用 this.Controls.Remove(control) 並呼叫 control.Dispose() 釋放非受控資源。
Control_Click,透過 sender is Type 模式比對辨識型別,適合邏輯相近的控制項。Txt_Click,針對特定型別客製化行為,程式碼更清晰。btn.Click += (s, e) => MessageBox.Show(((Button)s).Text);,但較難移除事件 (需保存 delegate 參考)。範例使用簡單的 Top 累加。實務上建議使用 FlowLayoutPanel 或 TableLayoutPanel 容器,自動處理換行、對齊、捲軸:
FlowLayoutPanel panel = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.TopDown, AutoScroll = true };
panel.Controls.Add(btn); // 自動排列
每個控制項皆有 object Tag 屬性,可存放 ID、資料實體、狀態物件等,事件中拆箱取用:
btn.Tag = orderId; // 建立時
// 事件中
if (sender is Button b && b.Tag is int id) ProcessOrder(id);
// 清除所有動態控制項
private void ClearDynamicControls()
{
// 反向迴圈避免集合修改例外
for (int i = this.Controls.Count - 1; i >= 0; i--)
{
Control c = this.Controls[i];
if (c.Name.StartsWith("btnDynamic_") || c.Name.StartsWith("txtDynamic_"))
{
this.Controls.Remove(c);
c.Dispose();
}
}
_buttonCount = _textBoxCount = 0;
_nextButtonTop = btnAddButton.Bottom + 5;
_nextTextBoxTop = btnAddTextBox.Bottom + 5;
}
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 事件重複觸發 | 同一控制項重複 += 同一方法 | 建立前先 -=,或使用旗標確保只綁定一次 |
| 記憶體洩漏 | 移除控制項未呼叫 Dispose(),事件未解除參考 | 實作 IDisposable,Remove 後呼叫 Dispose(),大量動態控制項考慮 WeakReference |
| 版面重疊/亂碼 | 手動計算座標錯誤 | 改用 FlowLayoutPanel、TableLayoutPanel、SuspendLayout/ResumeLayout |
| 跨執行緒存取 UI | 背景執行緒直接新增控制項 | 必須 Invoke 回 UI 執行緒:this.Invoke(() => Controls.Add(btn)) |
動態建立控制項是 WinForms 進階開發必備技能。掌握 裝箱/拆箱 概念以正確處理 sender,善用 容器控制項 自動排版,並建立完整的 建立/移除/釋放 生命週期管理,即能打造靈活、高效能的動態介面應用。