WPF advanced tutorial routing events

Keywords: Attribute

introduce

Like dependency properties, routing events are the upgrade of WPF to traditional. NET events, which makes events have stronger propagation ability.

Definition, registration and packaging

// Let's take an example of a Click event definition
public abstract class ButtonBase : ContentControl
{
    // Define routing events
    public static readonly RoutedEvent ClickEvent;
    
    // Register routing events
    static ButtonBase()
    {
        ButtonBase.ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ButtonBase));
    }
    
    // Normal event wrapper routing event
    public event RoutedEventHandler Click
    {
        add
        {
            // AddHandler and RemoveHandler are both defined in FrameworkElement
            base.AddHandler(ButtonBase.ClickEvent, value);
        }
        remove
        {
            base.RemoveHandler(ButtonBase.ClickEvent, value);
        }
    }
}

Share

From the above definition, we can see that, like dependency properties, routing events are also statically defined and wrapped as ordinary events. Then we can naturally speculate that we can use other types of routing events as our own like dependency properties. Here we need to use RoutedEvent.AddOwner() method.

// The UIElement class adds the MouseUp event of the Mouse class
UIElement.MouseUpEvent = Mouse.MouseUpEvent.AddOwner(typeof(UIElement));

Use

Raising of routing events

Raising a routing event using the RaiseEvent method

RoutedEventArgs e = new RoutedEventArgs(ButtonBase.ClickEvent, this);
base.RaiseEvent(e);

Handling of routing events

Monitoring events

// Direct processing in xaml
<button Click="cmdOK_Click">OK</Button>
// Background code connection event
img.MouseUp += new MouseButtonEventHandler(img_MouseUp);
// It can even simplify the code
img.MouseUp += img_MouseUp;
// Call AddHandler method directly without binding through event wrapper
img.AddHandler(Image.MouseUpEvent, new MouseButtonEventHandler(image_MouseUp));
// As we mentioned before, this is a shared event. MouseUp methods in UIElement Image Mouse are shared, and you can also add handler to UIElement for processing. These two methods are equivalent. The only difference is that it's not easy to see that MouseUp is caused by Image without writing Image
img.AddHandler(UIElement.MouseUpEvent, new MouseButtonEventHandler(image_MouseUp));

Event handlers all have an Args parameter, which is inherited from RoutedEventArgs and contains the following properties

Route break event

Disconnect route event cannot be implemented in xaml, code must be used-=

// Use - = operator
img.MouseUp -= img_MouseUp;
// Using the RemoveHandler method directly
img.RemoveHandler(Image.MouseUpEvent, new MouseButtonEventHandler(image_MouseUp));

Classification of routing events

Classification description

  • Direct route events originate from one element and are not passed to the next
  • Bubbling route events are passed up the element tree (MouseUp event), if not handled, all the way to the top element of the element tree
  • Tunnel route events are passed down the element tree (KeyDown event), which provides an opportunity to handle events in advance, such as PreviewKeyDown event

Set event type

When registering an event using the EventManager.RegistEvent method, you need to pass an enumeration value of RoutingStrategy and set the type of event.

1. Bubbling route event

xaml Code:

<Window x:Class="Charles.WPF.View.TestBubblingEvent"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Charles.WPF.View"
        mc:Ignorable="d"
        Title="TestBubblingEvent" Height="359" Width="329" MouseUp="SomethingClick">
    <Grid>
        <Grid Margin="3" MouseUp="SomethingClick">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="*"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
            </Grid.RowDefinitions>
            <Label Margin="5" Grid.Row="0" HorizontalAlignment="Left"
                   Background="AliceBlue" BorderBrush="Black" BorderThickness="1" MouseUp="SomethingClick">
                <StackPanel MouseUp="SomethingClick">
                    <TextBlock Margin="3" MouseUp="SomethingClick">Image and text label</TextBlock>
                    <Image Source="/Image/1.jpg" Stretch="None" MouseUp="SomethingClick" Width="30" Height="30"></Image>
                    <TextBlock Margin="3" MouseUp="SomethingClick">Courtesy of the StackPanel.</TextBlock>
                </StackPanel>
            </Label>
            <ListBox Grid.Row="1" Margin="5" Name="lstMessage"></ListBox>
            <CheckBox Grid.Row="2" Margin="5" Padding="3" HorizontalAlignment="Right"
                      Name="chk_Handle">Handle first event</CheckBox>
            <Button Grid.Row="3" Margin="5" Padding="3" HorizontalAlignment="Right" Name="cmd_Clear" Click="cmd_Clear_Click">
                Clear List
            </Button>
        </Grid>
    </Grid>
</Window>

Background code:

public partial class TestBubblingEvent : Window
{
    protected int eventCount = 0;
    public TestBubblingEvent()
    {
        InitializeComponent();
    }

    private void SomethingClick(object sender, MouseButtonEventArgs e)
    {
        eventCount++;
        string message = "#" + eventCount.ToString() + ":\r\n" +
            " Sender: " + sender.ToString() + ":\r\n" +
            " Source: " + e.Source + ":\r\n" +
            " Original Source: " + e.OriginalSource;
        lstMessage.Items.Add(message);
        e.Handled = (bool)chk_Handle.IsChecked;
    }

    private void cmd_Clear_Click(object sender, RoutedEventArgs e)
    {
        lstMessage.Items.Clear();
    }
}

This example shows the sequence of event transfer of bubble event. When clicking the picture, the event is triggered by the Image and transferred up layer by layer. If e.Handled = True is not encountered, the transfer will not be terminated.

Some tips for bubbling events:

  • Sender is the control triggered by the current event, and Source is the trigger Source
  • Both MouseButtonEventArgs and RoutedEventArgs can be used for event processing
  • We also monitored the MouseUp event of the window, which makes us trigger the MouseUp event when we Click in any blank position of the window. However, we found that when we Click the Button, we do not trigger the MouseUp event of the window, but the Click event of the Button. This is because the MouseUp event is suspended in the Button's source code, and a more advanced Click event is triggered
  • In WinForm, most controls have Click events. In WPF, only a few controls have Click events
  • There is a way to listen to the event with Handled as true. Although this is not recommended, it can be implemented. By passing the last parameter true, you can receive the suspended event (not recommended)
    cmdClear.AddHandler(UIElement.MouseUpEvent, new MouseButtonEventHandler(cmdClear_MouseUp), true);
    
  • As we mentioned above, most controls have no Click event, so how does the Click event bubble? In fact, Click events support bubbling, but handling Click events requires some special techniques
    // StatckPanel doesn't have a Click, but it needs to monitor the Click of the Button in it
    // It's not possible to use the following methods
    <StackPanel Click="DoSomething">
        <Button/>
        <Button/>
        <Button/>
    </StackPanel>
    
    An error will be reported when writing this way, because there is no Click event in the StackPanel. At this time, if we want to listen to all Button Click events, we need to use the skill of an attachment event
    <StackPanel Button.Click="DoSomething">
        <Button/>
        <Button/>
        <Button/>
    </StackPanel>
    
    Use the class name plus the event to get the Click event of the Button in the StackPanel, which is the use of additional properties. In the code, you need to pay attention that you cannot use the + = method to bind the event handling method, because + = is bound to the StackPanel by default, and you need to use the AndHandler method
    // The name of StackPanel is pnlButtons
    pnlButtons.AddHandler(Button.Click, new RoutedEventHandler(DoSomething));
    
    To determine which button in the StackPanel raised the event, use the following method
    // Method 1. Use the x:Name property of the control
    private void DoSomething(object sender, RoutedEventArgs args)
    {
        // cmd1 is the Name property of button control 1
        if(sender == cmd1)
        {
            // The first button is triggered
        }
        else if(sender == cmd1)
        ...
    }
    // Method 2. Add Tag to the button
    <Button Tag="first button"/>
    
    object tag = ((FrameworkElement)sender).Tag;
    

2. Tunnel routing events

  • Tunnel routing events work the same way as bubble routing events, but in the opposite direction
  • Tunnel routing events are all events starting with Preview
  • The tunnel route event and the bubbling route event share the same RoutedEventArgs, so if the tunnel route event is marked as handled, the bubbling route event will not be triggered. This attribute is very suitable for preprocessing
  • Tunnel events are always triggered before bubbling events
  • As can be seen from the figure below, the event will be triggered first and then up, so if you let e.Handler=true in any intermediate element, the bubbling event will not be triggered
27 original articles published, 13 praised, 7685 visited
Private letter follow

Posted by Dani34 on Tue, 25 Feb 2020 18:49:50 -0800