EDIT: I guess I asked a bit of a XY Problem. I don't really care about getting tunneled events working, what I care about is getting a event raised from the code behind of the parent window to be picked up and reacted to by a control that is a child of that window without explicitly needing to tell the child who its parent is and manually subscribing to the event.
I am trying to raise a event in a parent control and having the child controls listen for that event and react to it. From my research I thought I just needed to do a RoutedEvent
but I am doing something incorrect.
Here is a MCVE showing what I have tried, it is a simple program with a window and a UserControl inside of it.
<Window x:Class="RoutedEventsTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RoutedEventsTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Name="button" Click="ButtonBase_OnClick" HorizontalAlignment="Left"
VerticalAlignment="Top">Unhandled in parent</Button>
<local:ChildControl Grid.Row="1"/>
</Grid>
</Window>
using System.Windows;
namespace RoutedEventsTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TestEventHandler += MainWindow_TestEventHandler;
}
void MainWindow_TestEventHandler(object sender, RoutedEventArgs e)
{
button.Content = "Handeled in parent";
e.Handled = false;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(TestEvent));
}
public static readonly RoutedEvent TestEvent = EventManager.RegisterRoutedEvent("TestEvent", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(MainWindow));
public event RoutedEventHandler TestEventHandler
{
add { AddHandler(TestEvent, value); }
remove { RemoveHandler(TestEvent, value); }
}
}
}
<UserControl x:Class="RoutedEventsTest.ChildControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBlock Name="textBlock">Unhandeled in child</TextBlock>
</Grid>
</UserControl>
using System.Windows;
using System.Windows.Controls;
namespace RoutedEventsTest
{
public partial class ChildControl : UserControl
{
public ChildControl()
{
InitializeComponent();
AddHandler(MainWindow.TestEvent, new RoutedEventHandler(TestEventHandler));
}
private void TestEventHandler(object sender, RoutedEventArgs routedEventArgs)
{
textBlock.Text = "Handled in child";
routedEventArgs.Handled = false;
}
}
}
When I run the program the parent window reacts like I expect, but the child UserControl never runs its delegate that I passed in to AddHandler
.
Changing the child control to be
public partial class ChildControl : UserControl
{
public ChildControl()
{
InitializeComponent();
AddHandler(TestEvent, new RoutedEventHandler(TestEventHandler));
}
public static readonly RoutedEvent TestEvent = EventManager.RegisterRoutedEvent("TestEvent", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(ChildControl));
private void TestEventHandler(object sender, RoutedEventArgs routedEventArgs)
{
textBlock.Text = "Handled in child";
routedEventArgs.Handled = false;
}
}
did not fix the issue either. I searched a lot and found many examples of how to do a bubbled event going from a child to the parent, but I could not find a single full example showing how to do a tunneled event from a parent to a child.
If you check out the MSDN article on routed events in WPF (archived) more closely, you will see that it says:
It's indeed counter intuitive, but a tunneled event propagates towards the source element. In your case, root element is
MainWindow
, but the source element is actually theChildControl
. When you raised the event inside theMainWindow
, that happened to be both the source and the root.Source element is the element on which the
RaiseEvent
method is invoked, even if theRoutedEvent
is not a member of that element. Also, sinceRaiseEvent
is a public method, other elements can make another element become the source element for a tunneled event.In other words, you would need something like (added the
Preview
prefix cause that's the convention for tunneled events):And in the
MainWindow
:Things are simpler if you use existing tunneled events, but note that they are still defined on the
Button
as the source, not the root element:This would also output the following to the console (on mouse up):
And of course, if you set the
Handled
property totrue
inside the parent handler, child handler will not be invoked.[Update]
If you want to raise the event from the parent control, yet make the child control the source of the event, you can simply invoke the child control's public
RaiseEvent
method from outside:Depending on your actual use case, it almost looks like you want the parent window to notify the child control without actual tunneling. In that case, I am not sure if you even need events? I.e. what's wrong with simply this: