Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF - Declaring a custom routed event and listening to it

Tags:

c#

events

wpf

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:

  • the user control class declare a custom routed event
  • the user control class listen to its own custom routed event through AddHandler(...)

Then:

  • some random item in the visual tree use RaiseEvent(...) to...well, raise the event.
  • an instance of the user control in the tree serves the request.

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?

like image 690
motoDrizzt Avatar asked Jun 18 '17 12:06

motoDrizzt


People also ask

What is a routed event WPF?

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.

Why should we use routed commands instead of events?

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.

What is bubbling and tunneling in WPF?

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.


2 Answers

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));
}
like image 199
Evk Avatar answered Oct 23 '22 05:10

Evk


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.

like image 33
Funk Avatar answered Oct 23 '22 05:10

Funk