Click here to Skip to main content
15,860,859 members
Articles / Programming Languages / Visual Basic
Article

ControlInspector - monitor Windows Forms events as they are fired (like Spy++ for .net)

Rate me:
Please Sign up or sign in to vote.
4.96/5 (86 votes)
30 Apr 2003CPOL6 min read 272.5K   8.3K   176   41
ControlInspector hooks on to all events on a given control, user-control or form and shows when they are fired, along with any eventargs. It even handles custom events and custom event args using dynamically generated assemblies.

Sample Image - controlinspector.gif

Introduction

How often have you coded up a simple Windows Form application to clear up the difference between similar events? Where does TextChanged get fired in the sequence KeyDown, KeyPress, KeyUp. When exactly does Load get fired in relation to other events on a complex control?

ControlInspector is designed to answer these and many other questions by hooking all events on an arbitrary windows form control; in a user control; or in a complete windows form. It will recurse through the controls collection and hook events on every sub control, and has special handling for context menus and main menus on forms to make sure that these don't get excluded.

In summary; Control Inspector is like a native .net version of Spy++ for .net events

This article is intended to accompany ContronInspector and give some insight into the techniques it uses. If you just want to use ControlInspector to diagnose your own applications, or to understand Windows Form events better then just download the compiled version of the software, and don't worry about the source!

Using Control Inspector

When you first open Control Inspector you will be presented with a blank screen. You can use the File/Open option to open an arbitrary assembly (.net exe or dll file); you will then be presented with a list of Windows Forms Controls and Forms that are part of the assembly. The entry you select will be loaded into memory and instantiated either by hosting it in a form, or if it is a form it will be constructed directly. You can also use the File/Windows Forms option to display a list of available controls from the System.Windows.Forms namespace. Note that you won't be able to construct some of these controls (eg ButtonBase) because although they derive from Control they are not directly usable.

If the control is hosted in a form, you will see the ControlHostForm above. It has been written to display a red grid around your control so you can see where the control you are analysing ends.

The first tab of the event viewer, "All Events" shows a complete list of events trapped by ControlInspector in the that they sequence. You can look at individual controls by clicking on their individual tab. When you have a particular control in focus, you are able to use the property editor to make on the fly changes to the control (useful to see what effect events are fired by this, and in what order). For example, if you enable anchoring you then can see the resize events generated by resizing the hosting form.

The events are hooked before the control is displayed, so you will get all the initialisation events; and if you close the hosting form, then you will see the events fired until the control dies.

You are able to uncheck particular events to handle either by focusing on the control and using the checked list box, or by right clicking on a particular event in the event view and selecting "stop tracking this event". This option will only stop tracking events for this individual control. There are options to stop tracking groups of events for all controls (for example, all mouse movement related events) to stop your event list getting over populated.

If you aren't interested in ControlInspector under the covers, I suggest you stop reading now!

Background

Last week I attended a Guerilla .NET course hosted by DevelopMentor. I can highly recommend this training company as the whole week was inspiring, and the instructors highly knowledgeable: You know who you are guys!

The instructors issued a challenge for the the class to come up with the best program that they could using any of the techniques that they had learnt during the week. The challenge would be judged on Thursday, so I had just a few days to get busy.

One of the topics covered on the course was Reflection and I decided to use this to discover information about Windows Forms controls and hook on to their events.

ControlInspector also has to use Reflection.Emit to generate a function and delegate that exactly corresponds to the event type to allow it to hook on to arbitrary events. It can only hook on to events that following the function prototype:

C#
void eventName(object sender, eventargstype x)

where eventargstype derives from the EventArgs type. All the standard WindowsForms events do; and your events should also use this structure so there should be no problems with this approach.

About the code

The main part of the code is split between MainForm.cs which contains the UI and code for hooking on to events, and GenerateEventAssembly.cs which generates the IL for a function which matches a given delegate. Lets talk about the way the events are hooked in the first place:

C#
void HookEvents(object o, string name) {
    Type t = o.GetType();

    ...

Using Reflection, we step trhough all the events on a particular type. The EventHandlerType will be the type of the delegate required to hook on to this event; eg: void EventHandler(object sender, EventArgs e)

To generate a function with this prototype, we need to get the parameters of the function, since all delegates are classes that implement the Invoke function with the required parameters it is this that we find.
C#
foreach(EventInfo ei in t.GetEvents()) 
{
    // Discover type of event handler
    Type eventHandlerType = ei.EventHandlerType;

    // eventHandlerType is the type of the delegate 
    // (eg System.EventHandler)
    // what we need, is to find the type of the second parameter of the
    // delegate, eg System.EventArgs

    MethodInfo mi = eventHandlerType.GetMethod("Invoke");
    ParameterInfo[] pi = mi.GetParameters();

Now comes the magic. The function GetEventConsumerType generates an a class dynamically that has a method "HandleEvent" of exactly the right types. This class is derived from ControlEvent, which contains a function void GenericHandleEvent(object sender, object eventargs) so the generated code is kept to a minimum (I can't write IL for toffee: I wrote a class in C# which did the required type conversion, ran ILDASM on it, then used that as a basis to automatically generate these arbitrary types).

C#
// Get a class derived from ControlEvent which has a HandleEvent method
// taking the appropriate parameters
ControlEvent ce
   = GenerateEventAssembly.Instance.GetEventConsumerType(pi[1].ParameterType);
Now we have a function of the right type, we just need to hook it up. We also hook up the EventFired event on "ControlEvent" to our generic event handler which does all the display work
C#
    // Hook onto this control event to get the details of all events fired
    ce.ControlName = name;
    ce.EventName = ei.Name;
    ce.EventTrackInfo = trackInfo;
    ce.EventFired += new EventHandler(eventFired);

    controlEventList.Add(ce);

    // Wire up the event handler to our new consumer
    Delegate d = Delegate.CreateDelegate(eventHandlerType, ce, "HandleEvent");
    ei.AddEventHandler(o, d);
}

...

Finally, if this is a control type, we recurse through sub-controls

C#
if (o is Control) {
    Control c = (Control) o;
        if (c.Controls != null) {
        foreach(Control subControl in c.Controls) {
            HookEvents(subControl, name + "/" + ControlName(subControl));
        }
    }

    ...
}

The code to do the IL generation is quite well commented, so I won't go into greater detail about that here.

The function
C#
void AddEventsToTreeView(ControlEvent ce, TreeView treeView, 
                         bool includeControlName)
is the routine which adds the events to the tree list. It uses reflection (again!) to display any information contained in the EventArgs (eg key pressed in KeyPressedEventArgs).

Because of the generic way in which events are hooked up, there is a test user control contained in the ControlInspector.exe - UserControlTest. This contains a button which fires off a user-defined event just to prove that everything is working correctly.

Points of Interest

Removing the tab pages from the form when a new control is loaded exhibits a strange bug in the 1.0 framework which I have tried my best to work around. My thanks to instructor Ian Griffiths who helped me with this issue.

I didn't win the Thursday Challenge!

The current project has been tested under VS.NET 2002 and VS.NET 2003, and it works fine on both of them. The downloadable file is a VS.NET 2002 project, but works fine if you upgrade it. I will be releasing a new version with some more changes soon.

History

1.0 Initial release

License

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


Written By
Web Developer
United Kingdom United Kingdom
Director of Adastra Software Ltd, a UK based provider of point of care information systems.

Comments and Discussions

 
GeneralNew version Pin
powerdude10-Jul-03 8:58
professionalpowerdude10-Jul-03 8:58 
GeneralRe: New version Pin
Jabes10-Jul-03 21:54
Jabes10-Jul-03 21:54 

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.