Help us improve
Share bugs, ideas, or general feedback.
From wpf-dev-pack
Explains WPF routed events: Bubbling, Tunneling, Direct strategies with XAML examples, handler order, and propagation control for custom events.
npx claudepluginhub christian289/dotnet-with-claudecode --plugin wpf-dev-packHow this skill is triggered — by the user, by Claude, or both
Slash command
/wpf-dev-pack:routing-wpf-eventssonnetThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Understanding and implementing WPF's routed event system for event propagation through element trees.
Navigates WPF Visual and Logical Trees using VisualTreeHelper and LogicalTreeHelper. Useful for traversing elements, accessing template internals, and understanding event routing.
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Share bugs, ideas, or general feedback.
Understanding and implementing WPF's routed event system for event propagation through element trees.
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
| 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) |
<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>
// 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)");
}
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
}
// 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}");
}
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;
}
Advanced: See ADVANCED.md for custom Bubbling/Tunneling event creation, custom EventArgs, class event handlers, and defining 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}");
}
}
// 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;
}
}
// Suppress events for specific conditions
private void Element_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (IsReadOnly || IsDisabled)
{
// Prevent all mouse handling
e.Handled = true;
}
}