2

I am trying to understand the RoutedEventArgs.Source property in a simple WPF application. Here is the XAML Code

    <Window x:Class="BubbleDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel x:Name="stackPanel1" Button.Click="OnOuterButtonClick">
        <Button x:Name="button1" Content="Button 1" Margin="5" />
        <Button x:Name="button2" Margin="5" Click="OnButton2">
            <ListBox x:Name="listBox1">
                <Button x:Name="innerButton1" Content="Inner Button 1" Margin="4" Padding="4" Click="OnInner1" />
                <Button x:Name="innerButton2" Content="Inner Button 2" Margin="4" Padding="4" Click="OnInner2" />
            </ListBox>
        </Button>
        <ListBox ItemsSource="{Binding}" />        
    </StackPanel>
</Window>

And here is the code behind

using System;
using System.Collections.ObjectModel;
using System.Windows;

namespace BubbleDemo
{
    public partial class MainWindow : Window
    {
        private ObservableCollection<string> messages = new ObservableCollection<string>();
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = messages;
        }

        private void AddMessage(string message, object sender, RoutedEventArgs e)
        {
            messages.Add(String.Format("{0}, sender: {1}; source: {2}; original source: {3}",
                message, (sender as FrameworkElement).Name,
                (e.Source as FrameworkElement).Name,
                (e.OriginalSource as FrameworkElement).Name));  
        }

        private void OnOuterButtonClick(object sender, RoutedEventArgs e)
        {
            AddMessage("outer event", sender, e);   
        }

        private void OnInner1(object sender, RoutedEventArgs e)
        {
            AddMessage("inner1", sender, e);
        }

        private void OnInner2(object sender, RoutedEventArgs e)
        {
            AddMessage("inner2", sender, e);

            e.Handled = true;
        }

        private void OnButton2(object sender, RoutedEventArgs e)
        {
            AddMessage("button2", sender, e);
            e.Source = sender;
        }
    }
}

When I click on InnerButton1 the click event is raised and then is executed the OnInner1 handler. After is executed the OnButton2 Handler which sets the RoutedEventArgs.Source property with the sender parameter. If you build and execute this code, you can see the output results. When the event arrives on the OnOuterButtonClick handler, the output in the bottom ListBox should be:

inner1, sender: innerButton1; source: innerButton1; original source: innerButton1
button2, sender: button2; source: innerButton1; original source: innerButton1
outer event, sender: stackPanel1; source: button2; original source: innerButton1

but the output is this

inner1, sender: innerButton1; source: innerButton1; original source: innerButton1
button2, sender: button2; source: innerButton1; original source: innerButton1
outer event, sender: stackPanel1; source: innerButton1; original source: innerButton1

The RoutedEventArgs.Source property reassigned in the OnButton2 hander is changed but returns to reference innerButton1 within the OnOuterButtonClick handler.

Why this happens? Thanks

Lucas
  • 3,376
  • 6
  • 31
  • 46
Anloboz
  • 173
  • 1
  • 1
  • 9

2 Answers2

2

This is a really good question, and i had to look into the Source of .net to figure out why it is like this:

The Source Property looks like this:

public object Source
{
  get {return _source;}
  set
  {
    if (UserInitiated && InvokingHandler)
      throw new InvalidOperationException(SR.Get(SRID.RoutedEventCannotChangeWhileRouting));

    ...
  }
}

This execption s thrown, whenever the User tries to SET the source, while the event is Bubbling or tunneling.

I'm assuming, that the part of the .net Framework, taking care for this behavior is also catching the Exception, so you don't get aware of the problem. In fact, when trying to set the Source Property, while the event is bubbling, the Debugger shows, that is not changed right after setting it.

Unfortunately the source code just shows that Microsoft does not allow to change the Source-Property while the Event is bubbling (or tunneling), but not why.

If you - for whatever Reason - need to get information about the Prior handler that processed an event, you could create your own Extension of RoutedEventArgs and add another property, containing this information.

finally you can extend the button class, and raise your own Event, that contains the appropriate RoutedEventArgsWithHandlerHistory Object :)

dognose
  • 20,360
  • 9
  • 61
  • 107
1

This is an interesting question and demanded to reflect the .net Routing engine. So what I found is each UIElement uses RaiseEvent() method to initiate the RoutedEvent. While doing so it first builds the EventRoute. While build EventRoute, it creates the list of Invoke handlers depending on the RoutingStrategy i.e for Bubble and Tunnel it goes up and down the VisualTree to which UIElement belongs and finds out how many Handlers are attached to the given RoutedEvent. As apparent, in your case for innerButton1 and innerButton1 there are three handlers.

Now UIElement got the EventRoute for its RoutedEvent, next it calls InvokeHandlers() on EventRoute. While calling the handlers in the loop, InvokeHandler reset the args.Source to the original value it has like below where it is doing it for the Bubble strategy.

    for (int index = 0; index < this._routeItemList.Count; ++index)
    {
      if (index >= endIndex)
      {
        object bubbleSource = this.GetBubbleSource(index, out endIndex);
        if (!reRaised)
          args.Source = bubbleSource ?? source;
      }

Hence before each handler call, the Source is reset to its original value therefore changing it inside any handler will not be passed to the next handler.

Nitin
  • 18,344
  • 2
  • 36
  • 53