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 :)

2008-10-22

BadImageFormatException when loading Microsoft.TeamFoundation.Client.dll

I tried to make a small winforms application on my 64bit Vista that talks with our TeamFoundationServer today. I use a plugin based application that loads the assemblies using Assembly.Load (utilizing the AppDomain.AssemblyResolve event).

The problem surfaced when I tried to activate a function in one of my loaded plugins that used the TeamFoundation client dll, this was not located in the same folder as the exe.
Using the AppDomain.CurrentDomain.AssemblyResolve event, I was easy to find the assembly dll and using Assembly.LoadFrom(filename) I could return the needed assembly.

The problem was that when I tried to do this, I got a BadImageFormatException stating that

Could not load file or assembly 'file:///C:\MyPath\MyProject\bin\Debug\Plugins\MyPlugin\Microsoft.TeamFoundation.Client.dll' or one of its dependencies. An attempt was made to load a program with an incorrect format.
at System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
at System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
at System.Reflection.Assembly.LoadFrom(String assemblyFile)
at Meridium.Plugins.PluginLoader.CurrentDomain_AssemblyResolve(Object sender, ResolveEventArgs args) in C:\VSS\TFS\Meridium\Source\Meridium.Plugins\PluginLoader.cs:line 606

This seemed not quite correct, since the assembly at the location is ok and can be opened for instance in Reflector.

After a bit of searching I found more clues pointing to the fact that a 64bit application cannot load 32bit assemblies if they are in "Mixed mode" since they contain native code and thus are platform specific. The forum post http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=665884&SiteID=1 stated the problem and provided the solution to set the project output to be built for the x86 platform.

After changing the settings in the project, it worked like a charm.