WPF 採用路由事件機制,事件可在視覺樹中跨層級傳遞。XAML 控制項多為組合元素(如 Button 內含 TextBlock),點擊內部元素仍能觸發外層控制項事件。
public abstract class ButtonBase : ContentControl, ICommandSource
{
public static readonly RoutedEvent ClickEvent;
static ButtonBase()
{
ClickEvent = EventManager.RegisterRoutedEvent(
"Click", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(ButtonBase));
}
public event RoutedEventHandler Click
{
add { base.AddHandler(ClickEvent, value); }
remove { base.RemoveHandler(ClickEvent, value); }
}
}
| 策略 | 傳遞方向 | 命名慣例 |
|---|---|---|
| Tunneling (隧道/管道) | 根元素 → 目標元素(向下) | 事件名加 Preview 前綴 |
| Bubbling (冒泡) | 目標元素 → 根元素(向上) | 標準事件名(如 Click) |
| Direct (直接) | 僅在目標元素觸發 | 傳統 CLR 事件模式 |
<Border MouseRightButtonDown="MouseRightButtonDown">
<StackPanel MouseRightButtonDown="MouseRightButtonDown">
<Button MouseRightButtonDown="MouseRightButtonDown">Test</Button>
</StackPanel>
</Border>
private void MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show((sender as FrameworkElement).Name);
}
右鍵點擊 Button,依序彈出:Button → StackPanel → Border,驗證冒泡向上傳遞。
RoutedEventArgs.Handled = true 可標記事件已處理,阻止繼續傳遞(但實際仍繼續路由,只是不再觸發處理器)。
private void MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show(sender.GetType().Name);
if (sender.GetType().Name == "StackPanel") e.Handled = true;
}
StackPanel 標記 Handled 後,Border 不再觸發處理器。
public Window1()
{
InitializeComponent();
this.border1.AddHandler(
Border.MouseRightButtonDownEvent,
new MouseButtonEventHandler(MouseRightButtonDown),
true); // handledEventsToo = true
}
即使事件被標記 Handled,仍會觸發此處理器。
<Border PreviewMouseRightButtonDown="MouseRightButtonDown">
<StackPanel PreviewMouseRightButtonDown="MouseRightButtonDown">
<Button PreviewMouseRightButtonDown="MouseRightButtonDown">Test</Button>
</StackPanel>
</Border>
輸出順序反轉:Border → StackPanel → Button。隧道先於冒泡執行,若隧道階段標記 Handled,冒泡階段不再觸發。
類似附加屬性,父元素可訂閱子元素的路由事件:
<StackPanel Button.Click="Click">
<Button>Test</Button>
<CheckBox>Check</CheckBox>
</StackPanel>
private void Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(e.Source.ToString()); // 輸出: System.Windows.Controls.Button:Test
}
StackPanel 雖無 Click 事件,仍能捕獲 Button 的點擊。**注意:** 任何冒泡經過的 ButtonBase 衍生類點擊都會觸發,CheckBox 同樣觸發。