WPF Routed Events are a powerful feature of Windows Presentation Foundation that allows events to travel through the WPF element tree rather than being handled only by the control that originally raised them.
In a traditional .NET event model, an event is generally handled by the object that raises it. WPF extends this model by introducing routed events, allowing an event to travel between controls according to a defined routing strategy.
WPF provides three main routing strategies:
- Bubbling
- Tunneling
- Direct
Understanding WPF Routed Events is important when working with mouse input, keyboard input, custom controls, reusable UI components, and advanced WPF applications.
In this tutorial, you’ll learn:
- What WPF Routed Events are
- How event routing works
- What bubbling events are
- What tunneling events are
- What direct events are
- How
SourceandOriginalSourcework - How the
Handledproperty affects routing - How to create custom routed events
- When to use each routing strategy
- The difference between bubbling, tunneling, and direct events
What Are WPF Routed Events? #
A WPF Routed Event is an event that can travel through the WPF element tree.
For example, consider this simple hierarchy:
Window
|
+-- Grid
|
+-- StackPanel
|
+-- Button
If the user interacts with the Button, WPF can route the event through its parent elements.
Depending on the routing strategy, the event can travel:
Button → StackPanel → Grid → Window
or:
Window → Grid → StackPanel → Button
This is the fundamental idea behind WPF Routed Events.
Why Use WPF Routed Events? #
Routed events solve several common problems in UI development.
Handle Events from Multiple Controls #
Suppose a window contains 50 buttons.
Without routed events, you might attach separate handlers to every button.
With a bubbling routed event, a parent element can handle events generated by its children.
Window
|
Grid
|
StackPanel
|
+-- Button
+-- Button
+-- Button
+-- Button
A single handler can potentially handle events from all the buttons.
Support Reusable Controls #
Routed events are particularly useful when creating custom WPF controls.
A custom control can raise an event internally while allowing its parent controls to handle that event.
Intercept Input #
Tunneling events allow parent elements to inspect an event before it reaches the source control.
This is useful for:
- Keyboard shortcuts
- Input validation
- Mouse handling
- Event logging
- Custom control behavior
- Preventing specific interactions
How Event Routing Works in WPF #
To understand WPF Routed Events, you need to understand the WPF element tree.
Consider:
<Window>
<Grid>
<StackPanel>
<Button Content="Save" />
</StackPanel>
</Grid>
</Window>
The hierarchy is approximately:
Window
|
└── Grid
|
└── StackPanel
|
└── Button
When the button generates an input event, WPF determines an event route through this hierarchy.
The element where the event originates is called the source.
For example:
Button = Event Source
Depending on the event’s routing strategy, WPF can then route the event upward, downward, or keep it at the source.
WPF Routed Events Routing Strategies #
WPF supports three routing strategies:
- Bubbling
- Tunneling
- Direct
Each strategy determines how the event moves through the WPF element tree.
1. WPF Routed Events: Bubbling #
A bubbling event starts at the element where the event occurs and travels upward through its parent elements.
The direction is:
Child → Parent → Parent → Parent
For example:
Button
↓
StackPanel
↓
Grid
↓
Window
The event starts at the Button and moves toward its ancestors.
This behavior is called bubbling because the event moves upward through the element hierarchy.
Bubbling Event Example in WPF #
Consider this XAML:
<Window x:Class="WpfDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF Routed Events">
<Grid Button.Click="Element_Click">
<StackPanel>
<Button Content="Save"
Width="120"
Height="40"
Margin="10"/>
<Button Content="Delete"
Width="120"
Height="40"
Margin="10"/>
</StackPanel>
</Grid>
</Window>
Notice that the Click handler is attached to the Grid:
<Grid Button.Click="Element_Click">
The buttons don’t have individual handlers.
When a button is clicked, the event can bubble upward:
Button
↓
StackPanel
↓
Grid
The Grid can therefore handle the event.
The handler can be:
private void Element_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Button clicked");
}
This is one of the most practical uses of WPF Routed Events.
Understanding sender and Source in Routed Events #
When handling WPF Routed Events, it is important to understand the difference between:
sender
and:
e.Source
Suppose the handler is attached to the Grid:
<Grid Button.Click="Element_Click">
When a button is clicked:
private void Element_Click(object sender, RoutedEventArgs e)
{
}
The values may be:
sender = Grid
e.Source = Button
sender represents the object whose event handler is currently executing.
e.Source identifies the source of the routed event.
You can therefore write:
private void Element_Click(object sender, RoutedEventArgs e)
{
if (e.Source is Button button)
{
MessageBox.Show(button.Content?.ToString());
}
}
Now the same handler can determine which button generated the event.
Bubbling Events with Multiple Buttons #
A common use case for WPF Routed Events is handling events from multiple controls.
<StackPanel Button.Click="Button_Click">
<Button Content="Save"
Margin="5"/>
<Button Content="Update"
Margin="5"/>
<Button Content="Delete"
Margin="5"/>
</StackPanel>
One handler can process all three buttons:
private void Button_Click(object sender, RoutedEventArgs e)
{
if (e.Source is Button button)
{
MessageBox.Show($"Clicked: {button.Content}");
}
}
Instead of creating three separate handlers, the parent can handle the bubbling event.
Common Bubbling Events in WPF #
Several WPF input and UI events use the bubbling routing strategy.
Common examples include:
MouseDown
MouseUp
MouseMove
MouseLeftButtonDown
KeyDown
KeyUp
GotFocus
LostFocus
The exact routing strategy depends on the particular event, so you should always verify the event documentation when implementing advanced event handling.
2. WPF Routed Events: Tunneling #
A tunneling event travels from the root of the element tree toward the element where the event originated.
The direction is:
Parent → Child
For example:
Window
↓
Grid
↓
StackPanel
↓
Button
Tunneling events are commonly called Preview Events in WPF.
Examples include:
PreviewMouseDown
PreviewKeyDown
PreviewMouseMove
Tunneling events allow parent elements to inspect an event before the source element receives it.
Why Tunneling Events Are Called Preview Events #
A tunneling event gives parent elements an opportunity to inspect or handle an event before the child receives it.
Suppose the element hierarchy is:
Window
|
Grid
|
StackPanel
|
Button
A tunneling event travels:
Window → Grid → StackPanel → Button
This makes tunneling events useful for:
- Input validation
- Keyboard handling
- Mouse handling
- Event interception
- Application-wide input behavior
- Custom controls
Tunneling Event Example in WPF #
Consider this example:
<Window
PreviewMouseDown="Window_PreviewMouseDown">
<Grid>
<Button Content="Click Me"
Width="150"
Height="50"/>
</Grid>
</Window>
The window receives the preview mouse event before the button.
private void Window_PreviewMouseDown(
object sender,
MouseButtonEventArgs e)
{
MessageBox.Show(
"Mouse event is traveling toward the button.");
}
The routing path is approximately:
Window
↓
Grid
↓
Button
This is the tunneling phase of WPF Routed Events.
WPF Tunneling and Bubbling Events Together #
One of the most important concepts in WPF is that many input events have both tunneling and bubbling versions.
For example:
PreviewMouseDown
MouseDown
The event processing can be visualized as:
Tunneling:
Window
↓
Grid
↓
Button
followed by:
Bubbling:
Button
↑
Grid
↑
Window
Conceptually, the complete route looks like:
Window → Grid → Button
↓
Window ← Grid ← Button
This gives parent controls an opportunity to participate before and after the source element processes the event.
Tunneling and Bubbling Event Example #
<Window
PreviewMouseDown="Window_PreviewMouseDown"
MouseDown="Window_MouseDown">
<Grid
PreviewMouseDown="Grid_PreviewMouseDown"
MouseDown="Grid_MouseDown">
<Button
Content="Click Me"
PreviewMouseDown="Button_PreviewMouseDown"
MouseDown="Button_MouseDown"/>
</Grid>
</Window>
The handlers can execute approximately in this order:
1. Window PreviewMouseDown
2. Grid PreviewMouseDown
3. Button PreviewMouseDown
4. Button MouseDown
5. Grid MouseDown
6. Window MouseDown
This two-phase model is an important part of understanding WPF Routed Events.
3. WPF Routed Events: Direct Events #
The third routing strategy is Direct.
A direct routed event does not travel through the element tree in the same way as bubbling or tunneling events.
Instead, it is handled by the element that raises it.
Conceptually:
Button
rather than:
Window
↓
Grid
↓
Button
↑
Grid
↑
Window
A direct event stays associated with its source element.
Direct Events Are Still Routed Events #
A common misconception is that direct events are not routed events.
They are.
WPF supports three routing strategies:
RoutingStrategy.Bubble
RoutingStrategy.Tunnel
RoutingStrategy.Direct
A direct routed event simply uses:
RoutingStrategy.Direct
instead of traveling through parent elements.
Example of a Direct Event #
MouseEnter and MouseLeave are commonly encountered examples of direct routed events.
For example:
<Button Content="Move Mouse Here"
MouseEnter="Button_MouseEnter"/>
The handler can be:
private void Button_MouseEnter(
object sender,
MouseEventArgs e)
{
MessageBox.Show("Mouse entered the button.");
}
The event is associated directly with the element where the mouse entered.
Creating Custom WPF Routed Events #
When developing custom WPF controls, you can define your own routed events.
For example:
public static readonly RoutedEvent SaveRequestedEvent =
EventManager.RegisterRoutedEvent(
"SaveRequested",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MyControl));
The important part is:
RoutingStrategy.Bubble
This determines how the custom event will travel.
You can expose the event using:
public event RoutedEventHandler SaveRequested
{
add => AddHandler(SaveRequestedEvent, value);
remove => RemoveHandler(SaveRequestedEvent, value);
}
Then raise it using:
RaiseEvent(
new RoutedEventArgs(SaveRequestedEvent));
The event can now participate in the WPF event routing system.
Creating a Tunneling Routed Event #
To create a custom tunneling event, use:
public static readonly RoutedEvent PreviewSaveRequestedEvent =
EventManager.RegisterRoutedEvent(
"PreviewSaveRequested",
RoutingStrategy.Tunnel,
typeof(RoutedEventHandler),
typeof(MyControl));
The important difference is:
RoutingStrategy.Tunnel
This causes the event to travel from the parent toward the source.
Creating a Direct Routed Event #
A direct custom event can be registered using:
public static readonly RoutedEvent SaveCompletedEvent =
EventManager.RegisterRoutedEvent(
"SaveCompleted",
RoutingStrategy.Direct,
typeof(RoutedEventHandler),
typeof(MyControl));
Because the routing strategy is:
RoutingStrategy.Direct
the event is not routed through the parent hierarchy like a bubbling or tunneling event.
The Handled Property in WPF Routed Events #
Another important feature of WPF Routed Events is the:
e.Handled
property.
It allows a handler to indicate that an event has already been processed.
For example:
private void Button_Click(
object sender,
RoutedEventArgs e)
{
e.Handled = true;
}
Setting:
e.Handled = true;
indicates that the event has been handled.
This can affect whether other handlers in the event route receive the event through normal event-handler invocation.
Why Handled Is Useful #
Consider:
Window
|
Grid
|
Button
The button handles an event:
private void Button_Click(
object sender,
RoutedEventArgs e)
{
e.Handled = true;
}
The event has now been marked as handled.
This is useful when a child control should consume an input event rather than allowing normal handlers further along the route to process it.
Handling an Already-Handled Routed Event #
Sometimes you still want to receive an event even after another control has marked it as handled.
You can use:
AddHandler(
Button.ClickEvent,
new RoutedEventHandler(Button_Click),
true);
The third parameter:
true
requests that the handler receive the event even when it has already been marked as handled.
This technique can be useful for:
- Global event monitoring
- Custom controls
- Application-level input handling
- Logging
- UI frameworks and behaviors
Source vs OriginalSource in WPF Routed Events #
Routed event arguments provide:
e.Source
and:
e.OriginalSource
These properties can be different when controls contain templates or other internal elements.
For example:
<Button>
<TextBlock Text="Save"/>
</Button>
The original source may be an internal element such as the TextBlock, while Source may represent the element that WPF considers the routed-event source.
In simple controls, you will often find:
Source == OriginalSource
But the difference becomes important when working with:
- Control templates
- Data templates
- Custom controls
- Complex visual trees
WPF Routed Events vs Normal .NET Events #
A normal .NET event typically follows a simple publisher/subscriber model.
For example:
button.Click += Button_Click;
The handler is attached directly to the button.
WPF Routed Events add another dimension: the event can participate in the WPF element tree.
| Feature | Normal .NET Event | WPF Routed Event |
|---|---|---|
| Direct handler | Yes | Yes |
| Event tree routing | No | Yes |
| Bubbling | No | Yes |
| Tunneling | No | Yes |
| Direct routing | N/A | Yes |
Handled support | Not generally | Yes |
| Parent event handling | Not automatically | Yes |
This makes routed events especially useful for hierarchical user interfaces.
Bubbling vs Tunneling vs Direct Events #
The three routing strategies can be summarized as follows:
| Routing Strategy | Direction | Typical Example |
|---|---|---|
| Bubbling | Child → Parent | MouseDown |
| Tunneling | Parent → Child | PreviewMouseDown |
| Direct | Source only | MouseEnter |
Bubbling #
Button
↓
StackPanel
↓
Grid
↓
Window
Tunneling #
Window
↓
Grid
↓
StackPanel
↓
Button
Direct #
Button
A useful way to remember them is:
Bubbling = Child → Parent
Tunneling = Parent → Child
Direct = Source only
Practical Example: Handling Multiple Button Clicks #
Suppose a WPF application has several buttons:
<Grid Button.Click="Grid_ButtonClick">
<StackPanel>
<Button Content="New"
Margin="5"/>
<Button Content="Save"
Margin="5"/>
<Button Content="Delete"
Margin="5"/>
</StackPanel>
</Grid>
The parent can handle the events:
private void Grid_ButtonClick(
object sender,
RoutedEventArgs e)
{
if (e.Source is Button button)
{
MessageBox.Show(
$"Clicked: {button.Content}");
}
}
This demonstrates how bubbling makes it possible to centralize event handling.
For larger MVVM applications, however, commands are often a better choice for application actions such as Save, Delete, and Update.
Practical Example: Intercepting Keyboard Input #
Tunneling routed events are useful when a parent needs to inspect keyboard input before a child control processes it.
For example:
<Window PreviewKeyDown="Window_PreviewKeyDown">
The handler can be:
private void Window_PreviewKeyDown(
object sender,
KeyEventArgs e)
{
if (e.Key == Key.F1)
{
MessageBox.Show("F1 pressed");
e.Handled = true;
}
}
The window gets the opportunity to inspect the keyboard event before the focused child control processes it.
This pattern can be useful for application-wide keyboard shortcuts.
WPF Routed Events and MVVM #
Routed Events are primarily part of WPF’s UI and input system.
In an MVVM application, you generally don’t want to place large amounts of business logic inside code-behind event handlers.
For actions such as:
Save
Delete
Update
Submit
Cancel
WPF commands are often more appropriate.
However, Routed Events remain useful for:
- Custom controls
- Mouse interaction
- Keyboard input
- UI behaviors
- Event interception
- Reusable controls
- Preview input processing
Therefore, WPF Routed Events and commands are complementary concepts rather than competing technologies.
When Should You Use Bubbling Routed Events? #
Use bubbling when:
- A parent needs to respond to child events.
- Multiple child controls should share an event handler.
- You are building reusable UI components.
- You want event delegation.
- An event should travel from the source toward its ancestors.
Example:
Button → StackPanel → Grid → Window
When Should You Use Tunneling Routed Events? #
Use tunneling when:
- A parent needs to inspect input before a child.
- You need to intercept keyboard or mouse input.
- You need to prevent a child from processing an event.
- You are implementing custom input behavior.
- You need preview or validation logic.
Example:
Window → Grid → StackPanel → Button
When Should You Use Direct Routed Events? #
Use direct events when:
- The event is meaningful only to the source element.
- Event propagation is not required.
- The event represents interaction specific to a particular control.
Examples include events such as:
MouseEnter
MouseLeave
Common Mistakes When Working with WPF Routed Events #
Mistake 1: Assuming Every WPF Event Bubbles #
Not every WPF event uses the bubbling strategy.
An event can be:
Bubble
Tunnel
Direct
Always check the event’s routing strategy when behavior is unexpected.
Mistake 2: Confusing sender with e.Source #
When handling an event on a parent, sender may refer to the parent while e.Source refers to the element that originated the event.
Mistake 3: Ignoring e.Handled #
If an event is not reaching a handler, check whether another handler has already set:
e.Handled = true;
Mistake 4: Using Routed Events for Everything #
Routed Events are excellent for UI event routing, but they are not a replacement for WPF commands, data binding, or MVVM patterns.
Key Takeaways #
WPF Routed Events allow events to travel through the WPF element tree.
There are three main routing strategies.
Bubbling #
Events travel from the source toward its ancestors:
Button
↓
StackPanel
↓
Grid
↓
Window
Tunneling #
Events travel from the root toward the source:
Window
↓
Grid
↓
StackPanel
↓
Button
Tunneling events are commonly known as Preview Events.
Direct #
The event is handled directly by its source:
Button
The most important concept to remember is:
Bubbling = Child → Parent
Tunneling = Parent → Child
Direct = Source only
Once you understand WPF Routed Events, you’ll have a much stronger foundation for working with WPF input handling, custom controls, event interception, the visual tree, and advanced UI behaviors.
The next concepts to learn after WPF Routed Events are WPF Commands, CommandBindings, InputBindings, and MVVM, because these concepts work together to create maintainable WPF applications.
