Click here to Skip to main content
15,881,715 members
Articles / Programming Languages / C#
Tip/Trick

Extract and manipulate event handler delegates of Windows form controls

Rate me:
Please Sign up or sign in to vote.
4.17/5 (4 votes)
14 Jan 2020CPOL6 min read 7.9K   103   2   11
Get, move, copy and delete event handler delegates of Windows form controls.

Introduction

This article describes:

  •  How events are implemented in Windows form controls
  •  Way to extract event handler delegates
  •  Practical example

and a few more.
Despite the title of this article, what written here is not restricted to Windows form controls.
I chose this title because a few important features exclusively applicable to Windows form controls are also explained.

How events are implemented in Windows form controls

You may have written your own custom event like this.

C#
// PseudoCode 1
//
// Declare custom event handler method signature
public delegate void SomethingHappenedEventHandler( object sender, SomethingHappenedEventArgs e );

// Declare custom event argument
public class SomethingHappenedEventArgs : System.EventArgs
{
    // omitted.
}

// Declare custom event
public event SomethingHappenedEventHandler SomethingHappened;

// Raise an event
void RaiseSomethingHappened()
{
    this.SomethingHappened?.Invoke( this, new SomethingHappenedEventArgs() );
}

In actual Windows form controls, an event handler delegate is declared as a private field like below.

C#
// PseudoCode 1a
//
// Event delegate is declared as a private field
private SomethingHappenedEventHandler onSomethingHappened;

// Declare public event
public event SomethingHappened
{
    { add    { this.onSomethingHappened += value; } }
    { remove { this.onSomethingHappened -= value; } }
}

// Raise an event
void RaiseSomethingHappened()
{
    this.onSomethingHappened?.Invoke( this, new SomethingHappenedEventArgs() );
}

However, such coded events are very few in Windows form controls.
Almost all events in Windows form controls use EventHandlerList for event handling.

EventHandlerList

EventHandlerList is a class that can contain multiple event handler delegates.
EventHandlerList class is in System.CompnentModel namespace.

  • Every Windows form control has 'Events' property, type of EventHandlerList.
    This 'Events' is a protected property of System.ComponentModel.Component class, the base class of System.Windows.Forms.Control.
    Therefore. some UI components not inherited from System.Windows.Forms.Control but from System.ComponentModel.Component ( e.g. Menu component ) also have 'Events' property.
  • EventHandlerList behaves like Dictionary<object, Delegate>.
  • Key to the dictionary is an instance of System.Object.
    One key object is related to one event.
    In other words, one record in 'Events' property is related to one event.
  • Value of the dictionary is a wrapper class instance of System.Delegate.
    This delegate is the event handler delegate invoked when the record's event is raised.

This is a pseudocode used in Windows form control, explaining how EventHandlerList is used.

C#
// Pseudocode 2
//
// Declare Key to the EventHandlerList
private static readonly object EventOnSomethingHappened = new object();

// Declare event property
public event SomethingHappened
{
    { add    { this.Events.AddHandler( EventOnSomethingHappened, value ); } }
    { remove { this.Events.RemoveHandler( EventOnSomethingHappened, value ); } }
}

// Raise an event
void RaiseSomethingHappened()
{
    // Get event handler delegate from 'Events' property.
    SomethingHappenedEventHandler eh = ( SomethingHappenedEventHandler )this.Events[ EventOnSomethingHappened ];

    // Invoke the delegate if any added.
    if( eh != null )
    {
        eh( this, new SomethingHappenedEventArgs() );
    }
}

Way to extract event handler delegates

To extract event handler delegates, we have to obtain every event and delegate related to each event from a Windows form control.
Yes, it is possible, and to see the information, compile and run 'Dump' console project in downloaded solution.

As written in previous section, an event callback delegate exists either;

  1. in a delegate field, or
  2. in one record of 'Events' property.

Now let's consider to write our program. We want to let our program detect where the event handler delegate is stored to an event.
More precisely,

  • In above case 1, we want to extract the delegate field, with the event that the delegate field is related to.
    This corresponds to the relationship between 'onSomethingHappened' delegate field and 'SomethingHappened' event in previous section's Pseudocode 1a.
  • In above case 2, we want to extract the 'Events' property's key object field, with the event that the key object is related to.
    This corresponds to the relationship between 'EventOnSomethingHappened' object field and 'SomethingHappened' event in previous section's Pseudocode 2.

Our strategy is;

  1. Create a Windows form control instance.
  2. To each event of the control, create a method which will be invoked when the event is raised, create a delegate to the method, and add the delegate to the event.
    At the same time, record the relationship between the event and created delegate.
  3. Analyze every field of the control. By looking up the relationship that we recorded, we can see what event the field is related to when;
    • the field is delegate, and also the delegate is the one which we added.
    • the field is key object to EventHandlerList, and also its related delegate in the EventHandlerList is the one which we added.

Below code is excerpted from FillFieldInfoList method in ControlInitializer.EventRelatedFieldInfoContainer class

C#
// Declare a dictinary to store delegate and related EventInfo
Dictionary<Delegate, EventInfo> delegateToEventInfoDict = new Dictionary<Delegate, EventInfo>();

// Create control instance dynamically.
object dynamicInstance = this.ControlType.Assembly.CreateInstance( this.ControlType.FullName );

// Add event handlers to all available events.
foreach( EventInfo ei in this.ControlType.GetEvents( BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static ) )
{
    // Dynamically create a method to be invoked when the event is raised.
    // As is beyond this article, detailed explanation is omitted. Please refer to the code.
    MethodInfo createdHandlerMethodInfo = ...;

    // Create an event handler delegate.
    Delegate dlg = Delegate.CreateDelegate( ei.EventHandlerInfo, createdHandlerMethodInfo );

    // Add created event handler delegate to the event.
    ei.AddEventHandler( dynamicInstance, dlg );

    // Store created event handler delegate with relevant EventInfo to dictionary.
    delegateToEventInfoDict.Add( dlg, ei );
}

Now event handler delegates are added to every public event of dynamically created Windows form control instance.
We also have a dictionary to look up what event a given delegate is added to.
By using this dictionary, we can finally find out which fields are related to which events, as well as how these fields are used.

C#
// Get EventHandlerList from dynamically created control instance.
PropertyInfo eventsPropertyInfo = this.ControlType.GetProperty( "Events", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetProperty );
EventHandlerList eventHandlerList = eventsPropertyInfo.GetValue( dynamicInstance, null ) as EventHandlerList;

// Iterate each field and check if it is relevant to an event.
foreach( FieldInfo fi in this.GetFieldInfos( this.ControlType ) )
{
    object fieldValue = fi.GetValue( dynamicInstance );
    if( fieldValue is Delegate )
    {
        // This field may be a event hander delegate which we added.
        Delegate eventHandler = fieldValue as Delegate;
        EventInfo ei;

        // Check if this delegate is recorded in our Delegate-to-EventInfo dictionary.
        bool exist = delegateToEventInfoDict.TryGetValue( eventHandler, out ei );
        if( exist )
        {
            // This field is an event handler delegate which we added to an event.
        }

        continue;
    }

    if( eventHandlerList[ fieldValue ] != null )
    {
        // This field is a key to EventHandlerList, but the event handler delegate may not be the one which we added.
        Delegate eventHandler = eventHandlerList[ fieldValue ];
        EventInfo ei;

        // Check if this delegate is recorded in our Delegate-to-EventInfo dictionary.
        bool exist = delegateToEventInfoDict.TryGetValue( eventHandler, out ei );
        if( exist )
        {
            // This field is an object used as a key to 'Events' property, and the related delegate is the one which we added.
        }

        continue;
    }
}

Now we obtained each event's information (1. what field is related to the event, and (2. how the field is used.
This means that when a Windows form control instance is given and an event of the control is specified, we can get the delegate called when the instance's event is raised.
We can also remove the delegate from the instance, and add the clone of the delegate to another control instance of same or derived type.
Refer to 'GetDelegate', 'MoveDelegate', 'CopyDelegate', and 'RemoveDelegate' method in ControlInitializer.EventRelatedFieldInfo class and derived ones.

Custom events

Custom event handler delegate can be detected as a delegate field, and this corresponds to case 1 in previous section, 'Way to extract event handler delegates'.
So the custom events in your class derived from Windows form control also can be detected and can be handled.
Moreover, you can apply this article not only to Windows form controls but also to other .Net Framework classes.
Just remember that non-control classes do not have EventHandlerList, and therefore all events will be implemented as delegate fields.

Practical example

This article enables things listed below, for example.

  1. Replace a control and reassign event handlers.
    Don't you have any experience that you had to change a Windows form control to a derived one, after you had placed on a form and had added event handlers?
    By using the technique in this article, you can programatically replace existing control to a derived one, reassigning all event handlers simultaneously.
  2. Duplicate a control with event handlers.
    When duplicating a Windows form control dynamically, event handlers added to the existing controls can be copied together.
  3. Remove all event handlers added to a control.
    It is known fact that even after you dispose a class instance, it will not be gabage-collected as long as any event handlers to the instance remain.
    Now we can remove all event handlers added to a disposing Windows form control instance, and let the garbage collector take over disposing process.

All above case examples are implemented in downloaded solution's 'Demo' project. Try and run it.

Small tip to avoid unwanted event handling.

Usually, it is very unlikely to happen that event handler delegates are moved or copied.
Therefore, with no intention you may write an event handler vulnerable to such things.
Below is an example.

C#
    myControl.MouseClick += ( s, e ) => { myControl.Text = "Clicked"; };

If this event handler delegate is copied to another control, clicking the control changes myControl's Text property, not the text on the control you actually clicked.
To prevent such behaviour, this code should be something like;

C#
    myControl.MouseClick += ( s, e ) => { ( ( Control )s ).Text = "Clicked"; };

Conclusion

This article explains how events are implemented in Windows form controls.
EventHandlerList, a class containing most event delegates used in Windows form control is also explained.
For practical use, a utility project to manipulate event handler delegates is given with two other projects,
one for make a list of every event's information and another for a demo.

In finally, this is my first Code Project article. I hope this would be any help of somebody.

This article was originally posted at https://github.com/yutaraa/ControlInitializer

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Japan Japan
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHow to retrieve the real name(s) of method(s) attacheed to an event? Pin
Member 127414727-May-23 23:09
Member 127414727-May-23 23:09 
AnswerRe: How to retrieve the real name(s) of method(s) attacheed to an event? Pin
yutaraa8-May-23 21:50
yutaraa8-May-23 21:50 
GeneralRe: How to retrieve the real name(s) of method(s) attacheed to an event? Pin
Member 1274147215-May-23 22:56
Member 1274147215-May-23 22:56 
GeneralRe: How to retrieve the real name(s) of method(s) attacheed to an event? Pin
yutaraa16-May-23 1:26
yutaraa16-May-23 1:26 
GeneralRe: How to retrieve the real name(s) of method(s) attacheed to an event? Pin
Member 1274147219-May-23 1:38
Member 1274147219-May-23 1:38 
GeneralRe: How to retrieve the real name(s) of method(s) attacheed to an event? Pin
yutaraa19-May-23 15:20
yutaraa19-May-23 15:20 
GeneralRe: How to retrieve the real name(s) of method(s) attacheed to an event? Pin
Member 1274147221-May-23 0:48
Member 1274147221-May-23 0:48 
GeneralRe: How to retrieve the real name(s) of method(s) attacheed to an event? Pin
yutaraa21-May-23 2:27
yutaraa21-May-23 2:27 
QuestionDon't quite understand? Pin
asiwel15-Jan-20 17:21
professionalasiwel15-Jan-20 17:21 
AnswerRe: Don't quite understand? Pin
yutaraa16-Jan-20 2:17
yutaraa16-Jan-20 2:17 
GeneralRe: Don't quite understand? Pin
asiwel16-Jan-20 6:56
professionalasiwel16-Jan-20 6:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.