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.
public delegate void SomethingHappenedEventHandler( object sender, SomethingHappenedEventArgs e );
public class SomethingHappenedEventArgs : System.EventArgs
{
}
public event SomethingHappenedEventHandler SomethingHappened;
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.
private SomethingHappenedEventHandler onSomethingHappened;
public event SomethingHappened
{
{ add { this.onSomethingHappened += value; } }
{ remove { this.onSomethingHappened -= value; } }
}
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.
private static readonly object EventOnSomethingHappened = new object();
public event SomethingHappened
{
{ add { this.Events.AddHandler( EventOnSomethingHappened, value ); } }
{ remove { this.Events.RemoveHandler( EventOnSomethingHappened, value ); } }
}
void RaiseSomethingHappened()
{
SomethingHappenedEventHandler eh = ( SomethingHappenedEventHandler )this.Events[ EventOnSomethingHappened ];
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;
- in a delegate field, or
- 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;
- Create a Windows form control instance.
- 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. - 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
Dictionary<Delegate, EventInfo> delegateToEventInfoDict = new Dictionary<Delegate, EventInfo>();
object dynamicInstance = this.ControlType.Assembly.CreateInstance( this.ControlType.FullName );
foreach( EventInfo ei in this.ControlType.GetEvents( BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static ) )
{
MethodInfo createdHandlerMethodInfo = ...;
Delegate dlg = Delegate.CreateDelegate( ei.EventHandlerInfo, createdHandlerMethodInfo );
ei.AddEventHandler( dynamicInstance, dlg );
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.
PropertyInfo eventsPropertyInfo = this.ControlType.GetProperty( "Events", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetProperty );
EventHandlerList eventHandlerList = eventsPropertyInfo.GetValue( dynamicInstance, null ) as EventHandlerList;
foreach( FieldInfo fi in this.GetFieldInfos( this.ControlType ) )
{
object fieldValue = fi.GetValue( dynamicInstance );
if( fieldValue is Delegate )
{
Delegate eventHandler = fieldValue as Delegate;
EventInfo ei;
bool exist = delegateToEventInfoDict.TryGetValue( eventHandler, out ei );
if( exist )
{
}
continue;
}
if( eventHandlerList[ fieldValue ] != null )
{
Delegate eventHandler = eventHandlerList[ fieldValue ];
EventInfo ei;
bool exist = delegateToEventInfoDict.TryGetValue( eventHandler, out ei );
if( exist )
{
}
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.
- 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. - Duplicate a control with event handlers.
When duplicating a Windows form control dynamically, event handlers added to the existing controls can be copied together. - 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.
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;
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.