2008-10-27

Clicking a winforms button from code

Today I was writing a unit test for a winforms application (or rather a small Mvc framework for winforms) and I would like to simulate a button click on the form to se that all events had been wired correctly.
Since the event is a delegate, I thought it would be easy to just call the Invoke/DynamicInvoke methods of the Click delegate instance to get the events fiering...
It wasn't quite that easy though. When I tried to use the code
button.Click.Invoke(this,EventArgs.Empty);
I got a compiler error

The event 'System.Windows.Forms.Control.Click' can only appear on the left hand side of += or -=

After a search I found an article by Partha S that summarized the issue quite well, namely the difference between an event and a delegate is that the event can control the chaining and the event can only be invoked from within the declaring class. Partha also provided a solution for declaring an event that can be called from external code but that don't help me since I cannot modify the Button class.

A workaround is then to use reflection for retrieving the delegate field from the button class and when we have direct access to the delegate (not through the event wrapper construct) we will be able to Invoke the event.
The code below contains a method "GetField" that is included in our base library that I can post if anyone is interested, but for now it returns the value of a field.
//All events in a Component is listed in an EventHandlerList 
//called events in the Component class.
//b is the button instance.
EventHandlerList list = ReflectionUtil.GetField(
typeof(Component), "events", b) as EventHandlerList;
Assert.IsNotNull(list,"Cannot get eventlist from component");

//in the EventHandlerList, each delegate is identified by a
//static object instance in the control class
object eventClick = ReflectionUtil.GetField(typeof(Control),
"EventClick",null);

//now we get the click event delegate
Delegate click = list[eventClick];

//finally we can invoke the listeners of the click event.
click.DynamicInvoke(this, EventArgs.Empty);
So now I can click da button in my unittest :)

6 comments:

  1. Hi! Great!

    Would you mind telling me what's in the "GetField" method? ;-) I tried to write my own but it returns always an empty EventHandlerList.

    Regards,

    Mario M.

    ReplyDelete
  2. Hi, well of course. This is no standard method and I have added a new post regarding the GetField method.
    Hope this will help you!

    Good luck.
    /Dan

    ReplyDelete
  3. Thanks! I hoped that I could use it for a "WebControls.Button" too, but unfortunately it didn't work... thanks anyway...

    ReplyDelete
  4. Well, not in that sense. It all depends on your purpose. Is it for unit testing your webform or what?
    In that case, I recommend the ASP.NET MVC framework that eases testing a lot (but you need a different setup than an ordinary asp.net web project).

    ReplyDelete
  5. Hi,

    thanks, but actually it wasn't for testing. I tried to implement something like a "PreviewClickEvent":

    I need an event that gets called before all other events get called. Furthermore I have no access to the code of the original EventHandler and therefore I thought I could extract the original EventHandlers and replace them with my "PreviewEventHanlder".

    In this PreviewEventHanlder I would do some stuff and then call the original EventHandler afterwards.

    The only problem is that I'm not able to get the original EventHandlers. If I call "get_Events" via Refelction I only receive an empty EventHandlerList...

    Never mind, I'll think of something else

    Cheers,

    Mario M.

    ReplyDelete
  6. If you are trying to do this on a webform, the Click event handler won't be set from start when the webrequest begins. See the aspnet life cycle events chart for getting an idea when the event handler is available.

    ReplyDelete