WPF_05_ Routing events

Routing events

WPF replaces normal. NET events with more advanced routing events. Routing events have stronger propagation ability, can bubble up and tunnel down in the element tree, and are processed by the event handler along the propagation path. Like dependent properties, routing events are represented by read-only static fields, registered in static constructors, and encapsulated through standard. NET event definitions.

public abstract class ButtonBase : ContentControl
{
    // definition
    public static readonly RoutedEvent ClickEvent;

    // register
    static ButtonBase()
    {
        // Event name routing type defines the delegate owning event class of event handler syntax
        ButtonBase.ClickEvent = EventManager.RegisterRoutedEvent("Click",RoutingStategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase));
    }

    // Traditional packaging
    public event RoutedEventHandler Click
    {
        add
        {
            base.AddHandler(ButtonBase.ClickEvent,value);
        }
        remove
        {
            base.RemoveHandler(ButtonBase.ClickEvent,value);
        }
    }
}

Shared routing events

As with dependent properties, definitions of routing events can be shared between classes.

UIElement.MouseUpEvent = Mouse.MouseUpEvent.AddOwner(typeof(UIElement));

Raise routing event

Routing events are not raised through the traditional. NET event wrapper, but are raised using the RaiseEvent() method, which is inherited by all elements from the UIElement class.
The first parameter (sender) of each event handler provides a reference to the object that raised the event. The second parameter is the EventArgs object, which is bound with all other additional details that may be important. If you do not need to pass additional details, you can use RoutedEventArgs

Handling routing events

There are several ways to associate event handlers.

<Image Source="hello.jpg" Name = "img" MouseUp="img_MouseUp"/>
// Define a delegate object and point the delegate to img_MouseUp() method
// Then add the delegate to the list of registered event handlers for the img.MouseUp event
img.MouseUp += new MouseButtonEventHandler(img_MouseUp);

// C # also allows you to implicitly create appropriate delegate objects using a leaner syntax
img.MouseUp += img_MouseUp;

The above code method depends on the event wrapper, which calls the UIElement.AddHandler() method. You can also call the UIElement.AddHanler() method to connect events directly.

// This method needs to create an appropriate delegate type (MouseButtonEventHandler), and the delegate object cannot be created implicitly
img.AddHandler(Image.MouseUpEvent, new MouseButtonEventHandler(img_MouseUp));

// You can also use the name of the class that defines the event instead of the name of the class that raises the event
img.AddHandler(UIElement.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));

If you want to disconnect an event handler, you can only use code, not XAML.

img.MouseUp -= img_MouseUp;
img.RemoveHandler(Image.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));

Connecting the same event handler multiple times for the same event is usually the result of an error, in which case the event handler will be triggered multiple times. If you try to delete an event handler that has been connected twice, the event will still trigger the event handler, but only once.

Event routing

<Label BorderThickness="1">
    <StackPanel>
        <TextBlock Margin="3">
            Image and text label
        </TextBlock>
        <Image Source="hello.jpg"/>
        <TextBlock Margin="3">
            Courtesy of the StackPanel
        </TextBlock>
    </StackPanel>
</Label>

The label above contains a panel, which contains two pieces of text and an image. Clicking on the image part will raise the Image.MouseDown event, but what if you want to process all click events on the label in the same way? Obviously, associating the same handler for the MouseDown event of each element will make the code messy and difficult to maintain.
Routing events occur in the following three ways:

  • Direct events similar to ordinary. NET events. They originate from the same element and are not passed to other elements. For example, the MouseEnter event is a direct routing event.
  • Bubbling events passed upward in the containment hierarchy. For example, MouseDown is a bubbling routing event. This event is raised first by the clicked element, then by the parent element of the modified element, then by the parent element of the parent element, and so on until WPF reaches the top of the element tree.
  • Tunneling events passed down in the containment hierarchy. Tunnel routing events provide an opportunity to not preview events before they reach the right control. For example, you can intercept whether a key is pressed through PreviewKeyDown. First at the window level, then the more specific container, until you reach the element that has focus when you press the key.

When registering a routing event using the EventManager.RegisterEvent() method, you need to pass a RoutingStrategy enumeration value that indicates the event behavior you want to apply to the event.

MouseUp and MouseDown are both bubble events. When clicking the image part on the label:

  1. Image.MouseDown
  2. StackPanel.MouseDown
  3. Label.MouseDown

Pass up to the window in nested order.

RoutedEventArgs class

When handling bubbling routing events, the sender parameter is a reference to the last link. If the event bubbles up from the image to the label before processing, the sender parameter references the label object.

name explain
Source Indicates the object that raised the event. Keyboard events - controls with focus; Mouse event - the uppermost of all the elements below the mouse
OriginalSource A reference to the object that originally raised the event. Usually the same as Source
RoutedEvent Provide a RoutedEvent object for the triggered event through the event handler. This information is useful if the same handler handles different events
Handled This property allows the bubbling or tunneling of an event to be terminated. If set to true, the event will not continue to be passed and will not be raised for other elements

Handling pending events

The button suspends the MouseUp event and raises a more advanced Click event. At the same time, the Handled flag is set to true to prevent the MouseUp event from continuing to pass.
Interestingly, there is a way to receive events marked as processed:

// If the last parameter is true, the event will be received even if the Handled flag is set
cmdClear.AddHander(UIElement.MouseUpEvent, new MouseButtonEventHandler(cmdClear_MouseUp),true);

Additional events

<!--StackPanel did not Click event-->
<StackPanel Button.Click="DoSomething" Margin="5">
    <Button>Command 1</Button>
    <Button>Command 2</Button>
</StackPanel>

The Click event is actually defined in the ButtonBase class, and the Button class inherits this event. If an event handler is associated with a ButtonBase.Click event, it is called when you Click any control that inherits from ButtonBase, including the Button class, RadioButton class, and CheckBox class. If a handler is associated with a Button.Click event, it can only be used by the Button object.

You can also associate additional events in your code, but you need to use the UIElement.AddHandler() method instead of the + = operator syntax.

stackPanel.AddHandler(Button.Click, new RoutedEventHandler(DoSomething));

In this case, how to distinguish which button triggers the event? You can set the Tag property through the text of button or Name.

<StackPanel Button.Click="DoSomething" Margin="5">
    <Button Tag="first button">Command 1</Button>
    <Button Tag="second button">Command 2</Button>
</StackPanel>
private void DoSomething(object sender, RoutedEventArgs e)
{
    object tag = ((FrameworkElement)sender).Tag;
    MessageBox.Show(tag.toString());
}

Tunnel routing events

Tunnel routing events begin with the word Preview. WPF usually defines bubble routing events and tunnel routing events in pairs. Tunnel routing events are always triggered before bubbling routing events. If the tunnel routing event is marked as handled, the bubbling routing event will no longer be triggered because the two events share the same instance of the RoutedEventArgs class.

Tunnel routing events are useful if you need to perform some preprocessing (perform actions according to specific keys on the keyboard or filter out specific mouse actions). Tunnel routing events work in the same way as bubble routing events, but in the opposite direction. It is triggered in the window first, and then passed down in the whole hierarchy. If it is marked as processed at any time, the corresponding bubbling event will not occur.

WPF events

WPF events generally include the following five categories:

  • Lifecycle events: these events occur when an element is initialized, loaded, or unloaded
  • Mouse event
  • Keyboard events
  • Stylus event: replace mouse with stylus on tablet
  • Multi touch event: the result of one or more fingers touching on the multi touch screen

Claim cycle events

Events are raised when all elements are first created and released, which are defined in the FrameworkElement class.

name explain
Initialized Occurs when the element is instantiated and the attributes of the element are set according to the XAML tag. At this point, the element has been initialized, but the rest of the window may not have been initialized. In addition, styles and data bindings have not been applied. Is a normal. NET event
Loaded This event occurs when the entire window has been initialized and style and data binding has been applied. This is the last stop before the element is rendered. In this case, IsLoaded is true
Unloaded This event occurs when an element is released because the window containing the element is closed or a specific element is deleted from the window

The FrameworkElement class implements the method used by the ISupportInitialize interface to control the initialization process.

  • The first method is BeginInit(), which is called immediately after the element is instantiated.
  • The XAML parser then sets the properties of all elements and adds content.
  • The second method is EndInit(), which will be called after initialization. The Initialized event is raised

When a window is created, each element branch is initialized from bottom to top. After each element is initialized, you also need to layout, apply styles and bind to the data source in the container. When the initialization process is completed, the Loaded event will be raised, which is a top-down process. When all elements raise the Loaded event, the window is visible.
You can add your own code to the window constructor, but the Loaded event is a better choice. Because if an exception occurs in the constructor, it will be thrown when the XAML parser parses the page. The exception is encapsulated in a useless XamlParseException object along with the original exception in the InnerException attribute.

Keyboard events

name Routing type explain
PreviewKeyDown Tunnel Occurs when a key is pressed
KeyDown Bubbling Occurs when a key is pressed
PreviewTextInput Tunnel Occurs when the key is completed and the element is receiving text input
TextInput Bubbling Occurs when the keyboard is complete and the element is receiving text input
PreviewKeyUp Tunnel Occurs when the key is released
KeyUp Bubbling Occurs when the key is released

For example, you can verify the input of TextBox:

private void textBox_PreviewTextInput(object sender,TextCompositionEventArgs e)
{
    short val;
    // Keyconverter. Convertotostring() method, both Key.D9 and Key.NumPad9 return the string "9"
    if(!Int16.TryParse(e.Text,out val))
    {
        // Only numbers are allowed
        e.Handled = true;
    }
}

private void textBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
    if(e.Key == Key.Space)
    {
        // Some keys, such as spaces, bypass PreviewTextInput
        e.Handled = true;
    }
}

Mouse drag and drop

Drag and drop has two aspects: source and target. You need to call the DragDrop.DoDragDrop() method at a certain time to initialize the drag and drop operation. At this time, determine the source of the drag operation, shelve the content you want to move, and indicate what drag and drop effects (copy, move, etc.) are allowed.

private void lb_MouseDown(object sender, MouseButtonEventArgs e)
{
    Label lb1 = (Label)sender;
    DragDrop.DoDragDrop(lb1, lb1.Content, DragDropEffects.Copy);
}

The element receiving data needs to set its AllowDrop attribute to true.

<Label Grid.Row="1" AllowDrop="True" Drop="lbTarget_Drop">To Here</Label>

If you want to have selective reception, you can handle the DragEnter event.

private void lb2_DragEnter(object sender, DragEventArgs e)
{
    if(e.Data.GetDataPresent(DataFromats.Text))
        e.Effects = DragDropEffects.Copy;
    else
        e.Effects = DragDropEffects.None;
}

Finally, you can retrieve and process the data.

private void lb2_Drop(object sender, DragEventArgs e)
{
    ((Label)sender).Content = e.Data.GetData(DataFromats.Text);
}

My official account

Tags: WPF

Posted on Wed, 03 Nov 2021 20:44:53 -0400 by kevinkorb