In short: I want to declare a custom routed event and at the same time listen to it from the same user control that declared it.
What I want to achieve is to have a user control that serves requests for a certain task, so I imagined this scenario:
Then:
It doesn't seem to work. It's a bit different from the usual scenario where the user control declare and raise the event, I know, so I did some testing.
How to create a custom routed event seems clear, it's not the first time I do it. I have created an example user control, and this it's code behind:
public partial class FuffaControl : UserControl
{
public static readonly RoutedEvent FuffaEvent =
EventManager.RegisterRoutedEvent("Fuffa", RoutingStrategy.Bubble,
typeof(FuffaEventHandler), typeof(FuffaControl));
// Provide CLR accessors for the event
public event FuffaEventHandler Fuffa
{
add { AddHandler(FuffaEvent, value); }
remove { RemoveHandler(FuffaEvent, value); }
}
public FuffaControl()
{
InitializeComponent();
}
}
So far, so good. Then, for test purpose, I've declared a window with the custom control inside and a button. This is the content of the window:
<Grid>
<local:FuffaControl>
<Grid>
<Button Content="Fuffa" Click="Button_Click"/>
</Grid>
</local:FuffaControl>
</Grid>
In the code behind, I use AddHandler to listen to the event and I raise the event on the click of the button:
public MainWindow()
{
InitializeComponent();
this.AddHandler(FuffaControl.FuffaEvent, new FuffaEventHandler(OnFuffaEvent));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(FuffaControl.FuffaEvent);
RaiseEvent(newEventArgs);
}
private void OnFuffaEvent(object sender, RoutedEventArgs e) { }
It works. It makes not much sense (I mean, it's not that useful), but I'm using it just to test that the event itself works correctly. Ok, unless C# is doing something weird and cutting a few corners, but at a first glance it seems to me that the button is raising a custom event, the event travels up through the tree (it's a bubbling event, after all), and the window receives it.
So, next I take the AddHandler(...) call and the handler function and move them to the user control; now it's not the window who listen to the Raisevent(...), but the user control itself. If it works, I have children elements raising an event, and the parent user control managing it:
public partial class FuffaControl : UserControl
{
public static readonly RoutedEvent FuffaEvent = EventManager.RegisterRoutedEvent("Fuffa", RoutingStrategy.Bubble, typeof(FuffaEventHandler), typeof(FuffaControl));
// Provide CLR accessors for the event
public event FuffaEventHandler Fuffa
{
add { AddHandler(FuffaEvent, value); }
remove { RemoveHandler(FuffaEvent, value); }
}
public FuffaControl()
{
InitializeComponent();
this.AddHandler(FuffaControl.FuffaEvent, new FuffaEventHandler(OnFuffaEvent));
}
private void OnFuffaEvent(object sender, RoutedEventArgs e)
{
}
}
Nnnnnope. It doesn't work. Why? What's wrong with this? Why the user control cannot listen to its own event?
A routed event is an event registered with the WPF event system, backed by an instance of the RoutedEvent class, and processed by the WPF event system. The RoutedEvent instance, obtained from registration, is typically stored as a public static readonly member of the class that registered it.
Routed commands give you three main things on top of normal event handling: Routed command source elements (invokers) can be decoupled from command targets (handlers)—they do not need direct references to one another, as they would if they were linked by an event handler.
The difference between a bubbling and a tunneling event is that a tunneling event will always start with a preview. In a WPF application, events are often implemented as a tunneling/bubbling pair. So, you'll have a preview MouseDown and then a MouseDown event.
The reason is in how you raise that event:
private void Button_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(FuffaControl.FuffaEvent);
RaiseEvent(newEventArgs);
}
Routed event (like regular .NET event) has source (sender) and arguments. You only specify arguments, and sender is the control on which you call RaiseEvent
. You do this from MainWindow
class, so source of event will be MainWindow, not button (button does not participate in your event raising code at all as you may notice). WPF will search handlers for routing event starting from sender and then will go up or down hierarchy, depending on event type. In your case event is bubbling so it will search up the tree, starting from MainWindow. Your control is child of window so its handler will not be found.
Instead you should call RaiseEvent
on button. Then button will be sender and it will work as you expect:
private void Button_Click(object sender, RoutedEventArgs e) {
((FrameworkElement) sender).RaiseEvent(new RoutedEventArgs(FuffaControl.FuffaEvent));
}
At first glance it seems you're implementing the UserControl
as a ContentControl
instead. Nevertheless you could get this to work by moving all the logic to the UC and calling a public RaiseFuffaEvent
from the window's Button_Click
handler.
FuffaControl.xaml.cs
public partial class FuffaControl : UserControl
{
public FuffaControl()
{
InitializeComponent();
AddHandler(FuffaEvent, new RoutedEventHandler(OnFuffaEvent));
}
public static readonly RoutedEvent FuffaEvent = EventManager.RegisterRoutedEvent(
"Fuffa", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(FuffaControl));
public event RoutedEventHandler Fuffa
{
add { AddHandler(FuffaEvent, value); }
remove { RemoveHandler(FuffaEvent, value); }
}
public void RaiseFuffaEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(FuffaEvent);
RaiseEvent(newEventArgs);
}
private void OnFuffaEvent(object sender, RoutedEventArgs e)
{
}
}
With this implementation of RaiseFuffaEvent
, other controls can still listen for the event, even though it's raised from the window's code behind.
Window.xaml
<Window
...>
<Grid>
<local:FuffaControl x:Name="fuffa">
<Button Content="Fuffa" HorizontalAlignment="Center" VerticalAlignment="Center" Click="Button_Click"/>
</local:FuffaControl>
</Grid>
</Window>
Window.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
fuffa.RaiseFuffaEvent();
}
}
For completeness, in a "normal" UserControl
you would place the button in FuffaControl.xaml
and set up the Button_Click
handler in FuffaControl.xaml.cs
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With