Adding own event handler in front of other event handlers
Asked Answered
S

5

11

When I utilize AddHandler in VB to add my own method to the Click event :

  AddHandler Button.Click, AddressOf myButton_Click

I see that my code executes last - after other event handlers for the Button_Click event. Is there a way to insert my event handler in front of other events so that it executes first?

I tagged this question as C# as well as VB, please feel free to use eitherlanguage if you have any suggestions.
Thanks!

Sherrillsherrington answered 24/9, 2010 at 17:13 Comment(0)
W
9

The order of execution of handlers of a single event cannot be controlled through the basic behavior of a built-in event itself. MulticastDelegates are "bags" of handlers, and they just grab them one at a time. Keep in mind that this is how most developers expect this to work, and it can be dangerous to allow order-dependent event handlers. Event handlers should normally not know about each other, because if they are dependent on being executed before or after another handler, they first have to know of the existence of the other handler (violating information hiding and several other design principles), and second, if that order changes, the behavior will be broken.

If you understand all this, and still want to control the order of execution of handlers of an event, the following will get you close.

  1. Create an ordered collection of delegates of the event handler type called MyHandlers. This will be a surrogate for the actual event's MulticastDelegate implementation.
  2. Create a "master" handler method that will actually be attached to the built-in event, and will iterate through MyHandlers and call each one.
  3. Define some means to add and remove handlers from the list. Some of this can be accomplished with a custom event "property", but that will define only add and remove behaviors, not insert.

The code might look like the following:

private List<EventHandler> MyHandlers = new List<EventHandler>();

private void MasterClickHandler(object sender, EventArgs e)
{
   foreach(var handler in MyHandlers)
      handler(sender, e); 
}

public event EventHandler MyControlButtonClick
{
   add { MyHandlers.Add(value); }
   remove { MyHandlers.Remove(value); }
}

public void InsertButtonClickHandler(EventHandler handler)
{
   MyHandlers.Insert(handler,0); //calling this to add a handler puts the handler up front
}

...

myForm.MyControl.Click += MasterClickHandler;

Notice that you're no longer attaching handlers other than MasterClickHandler to the actual event; you can't have your cake and eat it too, both overriding and keeping the basic event behavior. There also isn't an "insert" behavior built into the event "property"; you have to define a method that allows this. Lastly, you should never raise the event MyControlButtonClick directly (though as your control is the only one that can, this can be enforced by code inspection).

Now, when you click the button, the button's built-in Click event fires MasterEventHandler, which will execute the delegates in MyHandlers in the same order they were attached to MyControlButtonClick (with any that were inserted executed first, in the reverse order they were inserted). If you placed this code in a custom user control with the Button, you could even name the custom event on your control Click, and the control would look and work much like the Button it contains, except that it would have the extra control over inserting handlers. The beauty of the whole thing is that nothing about this code forces consumers to work with it as anything other than a plain ol' vanilla event.

Westland answered 24/9, 2010 at 17:58 Comment(0)
G
13

Not easily.

That being said, don't do it. Your code shouldn't care about what order it's called in - it should just care that the button in question was clicked. All of the handlers, including yours, will execute. If the order is important, you should rethink your design, and use some other mechanism to control that.

Gabar answered 24/9, 2010 at 17:17 Comment(0)
W
9

The order of execution of handlers of a single event cannot be controlled through the basic behavior of a built-in event itself. MulticastDelegates are "bags" of handlers, and they just grab them one at a time. Keep in mind that this is how most developers expect this to work, and it can be dangerous to allow order-dependent event handlers. Event handlers should normally not know about each other, because if they are dependent on being executed before or after another handler, they first have to know of the existence of the other handler (violating information hiding and several other design principles), and second, if that order changes, the behavior will be broken.

If you understand all this, and still want to control the order of execution of handlers of an event, the following will get you close.

  1. Create an ordered collection of delegates of the event handler type called MyHandlers. This will be a surrogate for the actual event's MulticastDelegate implementation.
  2. Create a "master" handler method that will actually be attached to the built-in event, and will iterate through MyHandlers and call each one.
  3. Define some means to add and remove handlers from the list. Some of this can be accomplished with a custom event "property", but that will define only add and remove behaviors, not insert.

The code might look like the following:

private List<EventHandler> MyHandlers = new List<EventHandler>();

private void MasterClickHandler(object sender, EventArgs e)
{
   foreach(var handler in MyHandlers)
      handler(sender, e); 
}

public event EventHandler MyControlButtonClick
{
   add { MyHandlers.Add(value); }
   remove { MyHandlers.Remove(value); }
}

public void InsertButtonClickHandler(EventHandler handler)
{
   MyHandlers.Insert(handler,0); //calling this to add a handler puts the handler up front
}

...

myForm.MyControl.Click += MasterClickHandler;

Notice that you're no longer attaching handlers other than MasterClickHandler to the actual event; you can't have your cake and eat it too, both overriding and keeping the basic event behavior. There also isn't an "insert" behavior built into the event "property"; you have to define a method that allows this. Lastly, you should never raise the event MyControlButtonClick directly (though as your control is the only one that can, this can be enforced by code inspection).

Now, when you click the button, the button's built-in Click event fires MasterEventHandler, which will execute the delegates in MyHandlers in the same order they were attached to MyControlButtonClick (with any that were inserted executed first, in the reverse order they were inserted). If you placed this code in a custom user control with the Button, you could even name the custom event on your control Click, and the control would look and work much like the Button it contains, except that it would have the extra control over inserting handlers. The beauty of the whole thing is that nothing about this code forces consumers to work with it as anything other than a plain ol' vanilla event.

Westland answered 24/9, 2010 at 17:58 Comment(0)
B
5

It is more of an implementation detail of VB.NET, it has an alternate way of dealing with events using the WithEvents and Handles keywords. An event handler that uses Handles gets subscribed by auto-generated code in the form constructor. This code will run before any of your code, including InitializeComponent or your custom AddHandler statement. And will thus always run first.

Getting your code to guarantee to run first is possible. Derive your own class from Button and override the OnClick method:

Public Class MyButton
    Inherits Button

    Protected Overrides Sub OnClick(ByVal e As System.EventArgs)
        '' Do your stuff here
        ''....

        '' Other event handlers will run now:
        MyBase.OnClick(e)
    End Sub
End Class
Banana answered 24/9, 2010 at 17:37 Comment(0)
D
2

I had a similar problem.

I had an event: Closed that two different objects listen to. And I had to be sure that the 1st object's event will be called before the 2nd one.

Here his my solution (in C#):

public class Screen
{
    public event EventHandler Closed;
    public event EventHandler ScreenRemoved;


    protected virtual void OnClosed()
    {
        if (Closed != null)
        {
            Closed.Invoke(this, EventArgs.Empty);
        }

        OnScreenRemoved();
    }

    protected virtual void OnScreenRemoved()
    {
        if (ScreenRemoved != null)
        {
            ScreenRemoved.Invoke(this, EventArgs.Empty);
        }
    }
}

This way for all the events I want to be called first:

m_Screen.Closed += Screen_Closed;

And for all the events I want to be called last:

m_Screen.ScreenRemoved += new EventHandler(Screen_Closed);

*both way adding an event are the same, with and without "new EventHandler()"

Hope I been helpful.

Derk answered 27/12, 2013 at 15:3 Comment(0)
F
0

If you came here searching for adding your callback for eventhandler you cannot alter (as it is inside of component you are using and cannot change), this is the way:

Helper method: (change type ExecutedRoutedEventHandler for your EventHandler type if needed)

/// <summary>
/// Method, that can add delegate callback on the start of invocation list. So when the event is fired, this callback is called first.
/// </summary>
/// <param name="instance">instance of object with desired event</param>
/// <param name="eventName">name of the event on <see cref="instance"/></param>
/// <param name="addHandler">delegate for adding event callback</param>
/// <param name="removeHandler">delegate for removing event callback</param>
/// <param name="handler">desired callback, that will be called first</param>
private static void PrependExecutedRoutedCallbackTo<T>(
    T instance,
    string eventName,
    Action<T, ExecutedRoutedEventHandler> addHandler,
    Action<T, ExecutedRoutedEventHandler> removeHandler,
    ExecutedRoutedEventHandler handler
)
    where T : class
{
    var classType = typeof(T);

    // compiler automaticly creates backing field with event name - searching for it
    FieldInfo eventField = classType.GetField(eventName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
    ExecutedRoutedEventHandler eventDelegate = (ExecutedRoutedEventHandler)eventField.GetValue(instance);

    // if the field is null, it means no callbacks are registered - handler can be easily added
    if (eventDelegate == null)
    {
        addHandler(instance, handler);
        return;
    }

    // Here is some magic - removing all callbacks, adding desired callback on the begining and adding callbacks back

    // getting current registered callbacks
    var delegates = eventDelegate.GetInvocationList().OfType<ExecutedRoutedEventHandler>().ToList();
    // removing all of them
    foreach (var del in delegates)
    {
        removeHandler(instance, del);
    }
    // adding my special callback
    addHandler(instance, handler);
    // returning back all original callbacks
    foreach (var del in delegates)
    {
        addHandler(instance, del);
    }
}

Example of ussage:

//binding.Executed += BeforeOperationExecute;

// same as this ^^^^^^, but the callback is added as first to invocation list
PrependExecutedRoutedCallbackTo(
    binding,
    nameof(CommandBinding.Executed),
    (b, c) => { b.Executed += c; },
    (b, c) => { b.Executed -= c; },
    BeforeOperationExecute
);
Fiction answered 19/11, 2020 at 10:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.