繼承 UserControl 建立複合控件時,若需對外暴露專屬事件 (如「使用者按下 Shift+Click」),需完整實作 **委派 → EventArgs → 事件** 三部曲。本文以「偵測 Shift 鍵點擊」為例,逐步示範自訂事件的標準模式、命名慣例、執行緒安全與設計原則。
// 1. 自訂 EventArgs:攜帶事件資料
public sealed class CustomClickEventArgs : EventArgs
{
public bool IsShiftPressed { get; }
public Point ClickLocation { get; }
public CustomClickEventArgs(bool isShiftPressed, Point location)
{
IsShiftPressed = isShiftPressed;
ClickLocation = location;
}
}
// 2. 委派定義:符合 .NET 事件模式 (sender, e)
public delegate void CustomClickEventHandler(object sender, CustomClickEventArgs e);
// 3. 事件宣告:在控件類別中
public event CustomClickEventHandler CustomClick;
// 4. 觸發方法:保護性虛擬方法,供衍生類別覆寫
protected virtual void OnCustomClick(CustomClickEventArgs e)
{
CustomClick?.Invoke(this, e); // C# 6+ null 條件運算子,執行緒安全
}
// 5. 實際觸發點 (例如滑鼠事件處理)
private void MyControl_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
bool shift = (Control.ModifierKeys & Keys.Shift) == Keys.Shift;
OnCustomClick(new CustomClickEventArgs(shift, e.Location));
}
}
<uc:MyCustomControl ID="MyCtrl" runat="server" OnCustomClick="MyCtrl_CustomClick" />
// Code-behind
protected void MyCtrl_CustomClick(object sender, CustomClickEventArgs e)
{
if (e.IsShiftPressed)
lblMsg.Text = $"Shift+Click at {e.ClickLocation}";
else
lblMsg.Text = $"Normal Click at {e.ClickLocation}";
}
若控件事件極多 (如 Control 基類 70+ 事件),使用 EventHandlerList 儲存委派,避免每個事件佔用欄位:
private static readonly object EventCustomClick = new object();
public event CustomClickEventHandler CustomClick
{
add { Events.AddHandler(EventCustomClick, value); }
remove { Events.RemoveHandler(EventCustomClick, value); }
}
protected virtual void OnCustomClick(CustomClickEventArgs e)
{
var handler = (CustomClickEventHandler)Events[EventCustomClick];
handler?.Invoke(this, e);
}
此為 Component/Control 基類內建模式,Events 屬性為 EventHandlerList。
| 原則 | 做法 |
|---|---|
| 命名慣例 | EventArgs 類別以 EventArgs 結尾;委派以 EventHandler 結尾;事件名稱用動詞片語 (如 CustomClick、DataLoaded)。 |
| EventArgs 不可變 | 屬性唯讀,透過建構函式注入,確保事件引數在多訂閱者間一致。 |
| sender 傳 this | 讓訂閱者能轉型存取控件其他公開成員。 |
| 虛擬 OnXxx 方法 | 允許衍生類別在觸發前/後插入邏輯,或完全取代觸發行為。 |
| 執行緒安全觸發 | 使用 handler?.Invoke(this, e) 或 EventHandlerList 內建同步。 |
| 文件化 | XML 註解標註 <event>、<param name="e">,IntelliSense 可見。 |
public EventHandler CustomClick; (欄位而非事件) → 外部可直接 = null 清除訂閱者,破壞封裝。?.Invoke → 無訂閱者時拋 NullReferenceException。Click 屬性 vs Click 事件) → 命名加前綴區分。自訂事件是控件與外部世界溝通的契約。遵循 **EventArgs + Delegate + Event + Virtual OnXxx** 標準模式,配合 EventHandlerList 優化大量事件記憶體,能打造專業、可擴展、符合 .NET 設計規範的自訂控件。下次需要「通知外部發生了什麼」時,請優雅地定義一個事件。