返回 Skill 列表
extension
分类: 开发与工程无需 API Key

routing-wpf-events

实现WPF路由事件,包括冒泡、隧道和直接策略。在创建自定义路由事件、处理事件传播或理解预览事件时使用。

person作者: jakexiaohubgithub

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

9. References