WPF Routed Events Patterns
Understanding and implementing WPF's routed event system for event propagation through element trees.
1. Routing Strategies Overview
Window (Root)
│
┌──────────────────┼──────────────────┐
│ │ │
Grid Border StackPanel
│ │ │
Button TextBox ListBox
│
ContentPresenter
│
TextBlock (Event Source)
Tunneling (Preview): Window → Grid → Button → ContentPresenter → TextBlock
Bubbling: TextBlock → ContentPresenter → Button → Grid → Window
Direct: Only TextBlock
2. Routing Strategy Types
| Strategy | Direction | Event Name Pattern | Use Case | |----------|-----------|-------------------|----------| | Tunneling | Root → Source (downward) | PreviewXxx | Input validation, cancellation before processing | | Bubbling | Source → Root (upward) | Xxx | Normal event handling | | Direct | Source only | Xxx | Events that don't propagate (MouseEnter, MouseLeave) |
3. Tunneling and Bubbling Example
3.1 XAML Setup
<Window PreviewMouseDown="Window_PreviewMouseDown"
MouseDown="Window_MouseDown">
<Grid PreviewMouseDown="Grid_PreviewMouseDown"
MouseDown="Grid_MouseDown">
<Button PreviewMouseDown="Button_PreviewMouseDown"
MouseDown="Button_MouseDown"
Content="Click Me"/>
</Grid>
</Window>
3.2 Event Handler Order
// Execution order when Button is clicked:
// 1. Window_PreviewMouseDown (Tunneling)
// 2. Grid_PreviewMouseDown (Tunneling)
// 3. Button_PreviewMouseDown (Tunneling)
// 4. Button_MouseDown (Bubbling)
// 5. Grid_MouseDown (Bubbling)
// 6. Window_MouseDown (Bubbling)
private void Window_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("1. Window PreviewMouseDown (Tunneling)");
}
private void Grid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("2. Grid PreviewMouseDown (Tunneling)");
}
private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("3. Button PreviewMouseDown (Tunneling)");
}
private void Button_MouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("4. Button MouseDown (Bubbling)");
}
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("5. Grid MouseDown (Bubbling)");
}
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("6. Window MouseDown (Bubbling)");
}
4. Stopping Event Propagation
4.1 Using Handled Property
private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// Stop further propagation
e.Handled = true;
// Only events 1, 2, 3 will fire
}
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
// Stop bubbling to parent
e.Handled = true;
// Window_MouseDown won't fire
}
4.2 Handling Already-Handled Events
// Register handler that receives even handled events
public MainWindow()
{
InitializeComponent();
// handledEventsToo: true - receives events even if Handled = true
AddHandler(
MouseDownEvent,
new MouseButtonEventHandler(OnMouseDownHandledToo),
handledEventsToo: true);
}
private void OnMouseDownHandledToo(object sender, MouseButtonEventArgs e)
{
// This handler is called even if e.Handled = true elsewhere
Debug.WriteLine($"MouseDown received, Handled: {e.Handled}");
}
5. RoutedEventArgs Properties
private void Element_MouseDown(object sender, MouseButtonEventArgs e)
{
// Source: Element that raised the event (logical tree)
var source = e.Source;
// OriginalSource: Actual element clicked (visual tree)
var originalSource = e.OriginalSource;
// Example: Click on TextBlock inside Button
// Source = Button (logical source)
// OriginalSource = TextBlock (visual source)
// RoutedEvent: The routed event being handled
var routedEvent = e.RoutedEvent;
// Handled: Whether the event has been handled
var handled = e.Handled;
}
6. Creating Custom Routed Events
Advanced: See ADVANCED.md for custom Bubbling/Tunneling event creation, custom EventArgs, class event handlers, and defining attached events.
7. Attached Events
7.1 Using Attached Events
<!-- Handle Button.Click at Grid level (Bubbling) -->
<Grid Button.Click="Grid_ButtonClick">
<StackPanel>
<Button Content="Button 1"/>
<Button Content="Button 2"/>
<Button Content="Button 3"/>
</StackPanel>
</Grid>
private void Grid_ButtonClick(object sender, RoutedEventArgs e)
{
// Handle clicks from any child button
if (e.OriginalSource is Button button)
{
Debug.WriteLine($"Clicked: {button.Content}");
}
}
8. Common Event Handling Patterns
8.1 Event Aggregation
// Handle events from multiple child elements at parent level
private void ParentPanel_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// Find the clicked element type
var clickedElement = e.OriginalSource as FrameworkElement;
switch (clickedElement)
{
case Button button:
HandleButtonClick(button);
break;
case TextBlock textBlock:
HandleTextBlockClick(textBlock);
break;
case Image image:
HandleImageClick(image);
break;
}
}
8.2 Event Suppression
// Suppress events for specific conditions
private void Element_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (IsReadOnly || IsDisabled)
{
// Prevent all mouse handling
e.Handled = true;
}
}
Scan to join WeChat group