routing-wpf-events

WPF Routed Events Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "routing-wpf-events" with this command: npx skills add christian289/dotnet-with-claudecode/christian289-dotnet-with-claudecode-routing-wpf-events

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

  1. 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)

  1. 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)"); }

  1. 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}"); }

  1. 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;

}

  1. Creating Custom Routed Events

6.1 Bubbling Event

namespace MyApp.Controls;

using System.Windows;

public class CustomSlider : Control { // Register routed event public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent( name: "ValueChanged", routingStrategy: RoutingStrategy.Bubble, handlerType: typeof(RoutedPropertyChangedEventHandler<double>), ownerType: typeof(CustomSlider));

// CLR event wrapper
public event RoutedPropertyChangedEventHandler&#x3C;double> ValueChanged
{
    add => AddHandler(ValueChangedEvent, value);
    remove => RemoveHandler(ValueChangedEvent, value);
}

// Raise the event
protected virtual void OnValueChanged(double oldValue, double newValue)
{
    var args = new RoutedPropertyChangedEventArgs&#x3C;double>(oldValue, newValue, ValueChangedEvent);
    RaiseEvent(args);
}

}

6.2 Tunneling Event (with Preview)

namespace MyApp.Controls;

using System.Windows;

public class CustomButton : Button { // Tunneling (Preview) event public static readonly RoutedEvent PreviewClickedEvent = EventManager.RegisterRoutedEvent( name: "PreviewClicked", routingStrategy: RoutingStrategy.Tunnel, handlerType: typeof(RoutedEventHandler), ownerType: typeof(CustomButton));

// Bubbling event
public static readonly RoutedEvent ClickedEvent = EventManager.RegisterRoutedEvent(
    name: "Clicked",
    routingStrategy: RoutingStrategy.Bubble,
    handlerType: typeof(RoutedEventHandler),
    ownerType: typeof(CustomButton));

public event RoutedEventHandler PreviewClicked
{
    add => AddHandler(PreviewClickedEvent, value);
    remove => RemoveHandler(PreviewClickedEvent, value);
}

public event RoutedEventHandler Clicked
{
    add => AddHandler(ClickedEvent, value);
    remove => RemoveHandler(ClickedEvent, value);
}

protected override void OnClick()
{
    // Raise Preview (Tunneling) first
    var previewArgs = new RoutedEventArgs(PreviewClickedEvent, this);
    RaiseEvent(previewArgs);

    // If not handled, raise Bubbling event
    if (!previewArgs.Handled)
    {
        var args = new RoutedEventArgs(ClickedEvent, this);
        RaiseEvent(args);
    }

    base.OnClick();
}

}

6.3 Custom EventArgs

namespace MyApp.Events;

using System.Windows;

public class ItemSelectedEventArgs : RoutedEventArgs { public object SelectedItem { get; } public int SelectedIndex { get; }

public ItemSelectedEventArgs(RoutedEvent routedEvent, object source, object selectedItem, int selectedIndex)
    : base(routedEvent, source)
{
    SelectedItem = selectedItem;
    SelectedIndex = selectedIndex;
}

}

public delegate void ItemSelectedEventHandler(object sender, ItemSelectedEventArgs e);

  1. Class Event Handlers

7.1 Registering Class Handler

namespace MyApp.Controls;

using System.Windows; using System.Windows.Controls; using System.Windows.Input;

public class EnhancedTextBox : TextBox { static EnhancedTextBox() { // Class handler - called before instance handlers EventManager.RegisterClassHandler( typeof(EnhancedTextBox), PreviewKeyDownEvent, new KeyEventHandler(OnPreviewKeyDownClass));

    EventManager.RegisterClassHandler(
        typeof(EnhancedTextBox),
        GotFocusEvent,
        new RoutedEventHandler(OnGotFocusClass));
}

private static void OnPreviewKeyDownClass(object sender, KeyEventArgs e)
{
    // Called for all EnhancedTextBox instances
    if (sender is EnhancedTextBox textBox)
    {
        // Common keyboard handling logic
    }
}

private static void OnGotFocusClass(object sender, RoutedEventArgs e)
{
    // Auto-select all text on focus
    if (sender is EnhancedTextBox textBox)
    {
        textBox.SelectAll();
    }
}

}

  1. Attached Events

8.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.2 Defining Attached Events

namespace MyApp.Behaviors;

using System.Windows;

public static class ValidationBehavior { // Attached routed event public static readonly RoutedEvent ValidationErrorEvent = EventManager.RegisterRoutedEvent( name: "ValidationError", routingStrategy: RoutingStrategy.Bubble, handlerType: typeof(RoutedEventHandler), ownerType: typeof(ValidationBehavior));

public static void AddValidationErrorHandler(DependencyObject d, RoutedEventHandler handler)
{
    if (d is UIElement element)
    {
        element.AddHandler(ValidationErrorEvent, handler);
    }
}

public static void RemoveValidationErrorHandler(DependencyObject d, RoutedEventHandler handler)
{
    if (d is UIElement element)
    {
        element.RemoveHandler(ValidationErrorEvent, handler);
    }
}

// Raise the attached event
public static void RaiseValidationError(UIElement element)
{
    element.RaiseEvent(new RoutedEventArgs(ValidationErrorEvent, element));
}

}

  1. Common Event Handling Patterns

9.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;
}

}

9.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; } }

  1. References
  • Routed Events Overview - Microsoft Docs

  • How to: Create a Custom Routed Event - Microsoft Docs

  • Marking Routed Events as Handled - Microsoft Docs

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

converting-html-css-to-wpf-xaml

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

publishing-wpf-apps

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

using-xaml-property-element-syntax

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

managing-styles-resourcedictionary

No summary provided by upstream source.

Repository SourceNeeds Review