這個做法在討論處理系統事件時該用 override 還是該 Attach 到既有事件。

在官方的event 文檔中有提到 基底類別通常會提供一個 On 開頭的 protectedvirtual 的方法,例如 MouseDown 事件就提供 OnMouseDown 方法並接收一個 MouseButtonEventArgs 參數。

我們可以透過覆寫這個方法來達到自訂邏輯的效果,只要確保在衍生類別呼叫基底類別就能正確通知註冊的訂閱者,例如下面這樣 覆寫 OnMouseDown 方法並穿插一段 DoMouseThings 方法,最後在呼叫基底類別的 OnMouseDown 方法。

public partial class MainWindow : Window
{
   public MainWindow()
   {
       InitializeComponent();
   }
   protected override void OnMouseDown(MouseButtonEventArgs e)
   {
       DoMouseThings(e);
       base.OnMouseDown(e);
   }
}

在 WPF 中還有另一種附加的寫法,但需要搭配 C# 與 XAML,例如下面這段程式碼在 MouseDown 事件附加了 OnMouseDownHandler,之後呼叫自訂的 DoMouseThings 方法,達到跟上一段程式碼一樣的結果。

<!-- XAML Description -->
<Window x:Class="WpfApp1.MainWindow"
       xmlns:local="clr-namespace:WpfApp1"
       mc:Ignorable="d"
       Title="MainWindow" Height="350" Width="525"
       MouseDown="OnMouseDownHandler">
   <Grid >
   </Grid>
</Window>
 
// C# File
public partial class MainWindow : Window
{
   public MainWindow()
   {
       InitializeComponent();
   }
   private void OnMouseDownHandler(object sender, MouseButtonEventArgs)
   {
       DoMouseThings(e);
   }
}

這兩個方式最大的差異是,第一個是透過覆寫虛擬方法來達到新增邏輯的效果,第二個則是透過新增額外的 EventHandler 來達到新增邏輯的效果, 所以第二種方法就有一個很大的缺點,就是拋出錯誤時會影響到之後運行的 EventHandler,導致它們不會被運行。

第一種方式可以避免這個問題,因為負責呼叫其它 EventHandler 的邏輯是包含在基底類別的方法裡面,所以就代表即使你的 DoMouseThings 報錯, 只要正確處理並呼叫 base.OnMouseDown 就能確保剩下的 EventHandler 能正確運行。

並且第一種方式只要維護你自己寫的 DoMouseThings 方法就好,是第二種方式則需要維護新的 EventHandler 與榜定事件的程式碼(XAML 中的 MouseDown="OnMouseDownHandler")兩個地方。


但這並不代表第二種新增 EventHandler 的方式就沒有任何用處,設計它的初衷是 UI 設計師可能想要定義一些事件邏輯, 例如按下按鈕後觸發某種效果,這種需求透過新增事件就可以達到了,所以不需要覆寫 protected virtual 方法, 可以理解成 UI 工程師只需要透過 IDE 提供的內建工具就能完成設計的工作,所以它們不需要理解執行的理論。

另一個需要新增 EventHandler 的場合就是事件的處理方式需要根據不同的情境進行切換,例如 OnMouseDown 事件可能有開始畫線或是選取物件這兩種效果, 這種需求就可以透過 EventHandler 在應用程式運行中即時切換,覆寫則是偏向固定的行為。

最後是能夠將多個處理方法綁訂到同一個事件上,例如 OnMouseDown 事件可能要同時執行多個操作,這種複雜的場景就適合使用 EventHandler, 覆寫則無法這樣靈活綁定多個事件處理行為。


Summary

這個做法是在開發 WinForm 才會碰到的問題,如果事件比較單一而且簡單的話應該用覆寫的方式更好而且比較容易維護,其它複雜的場合則用 EventHandler 並 Attach 到既有事件。