Written March 03, 2009 at 15:37 MST Tagged .net 3.0, c sharp and programming
I am currently working on a large winforms application (most likely the last winforms app before I make the transition to WPF). I am writing the application in a top down fashion, which means I drive out the tests for the presenters, and then move down through the corresponding layers to get a good vertical slice of functionality. When it comes to the development of the user interface I am using a command based UI to allow me to easily drive the testing of the screens easily from a unit test. One thing that I often need to do, is cause a UI component to trigger one of its own events to see if the command in question will be triggered. In order to allow for a bit of “guidance” I wrote a class that allows me to quickly trigger events on UI components. The following code snippet shows a usage:
EventTrigger.trigger_event<Events.ControlEvents>(x => x.OnKeyPress(new KeyPressEventArgs('A')), target);
I am making use of an expression tree to determine which event I want the target control to raise. This is easily done because most (almost all) UI components in both the .Net framework and 3rd party Winforms libraries follow a convention of having a method named “OnEventName” which is usually a protected method that the control itself can call when it wants to raise its event. The EventTrigger class lives in my test utilities and for my current application I have a set of Events classes which correspond to some of the third party, and raw .net, control I want to raise events on, here is a trimmed down section of one of the events class:
public class Events
{
public interface ControlEvents : IEventTarget
{
void OnEnter(EventArgs args);
void OnKeyPress(KeyPressEventArgs args);
}
public interface FormEvents : ControlEvents
{
void OnActivated(EventArgs e);
void OnDeactivate(EventArgs e);
}
}
static public void trigger_event
Notice I am making use of the IEventTarget interface to basically simply constrain the incoming expression to be one based on one of the interfaces that lives in the “Events” class. The first argument “Expression<Action static public void trigger_event { var method_call_expression = expression_representing_event_to_raise.Body.downcast_to<MethodCallExpression>(); var method_args = get_parameters_from(method_call_expression.Arguments); var method_name = method_call_expression.Method.Name; var method = target.GetType().GetMethod(method_name, binding_flags); Debug.Assert(target != null,"The target to raise the event on cannot be null"); Debug.Assert(method != null,"There is no method called {0}, on a {1}".format_using(method_name,target.GetType().proper_name())); method.Invoke(target, method_args.ToArray()); } var method_name = method_call_expression.Method.Name; var method = target.GetType().GetMethod(method_name, binding_flags); method.Invoke(target, method_args.ToArray()); var method_args = get_parameters_from(method_call_expression.Arguments);
This is test utility code, so I am able to make a lot of assumptions about how I am going to be consuming and using it. For starters I know that the ExpressionTree coming is is going to represent a MethodCallExpression. This post is only a small intro into the amazing concept of expression trees, if they are not something you are comfortable with, there is no time like the present to start playing around with them.
With a MethodCallExpression I have access to the MethodInfo object that contains information about the target method to call (this allows me to reflectively match upto the protected method on the target I want to raise the event on:
Once I have the actual method to invoke, it still needs to be invoked with the correct arguments:
The act of getting the arguments is the interesting part to this code (and a little gentle introduction to the world of ExpressionTrees for those of you who are not using them yet). The following line:
method_call_expression.Arguments is actually a collection of Expressions also. If you look back at the line of code that is using this the original trigger method, we’ll decompose it so that you can get a better idea of the expression tree created to represent the call to the event:
EventTrigger.trigger_event<Events.ControlEvents>(x => x.OnKeyPress(new KeyPressEventArgs('A')), target);
static IEnumerable<object> get_parameters_from(IEnumerable<Expression> parameter_expressions)
{
foreach (var expression in parameter_expressions)
{
if (can_handle(expression)) yield return get_value_from_evaluating(expression);
else cannot_handle(expression);
}
}
static EventTrigger()
{
expression_handlers = new Dictionary<ExpressionType, Func<Expression, object>>();
expression_handlers[ExpressionType.New] = instantiate_value;
expression_handlers[ExpressionType.MemberAccess] = get_value_from_member_access;
expression_handlers[ExpressionType.Constant] = get_constant_value;
}
static object get_value_from_evaluating(Expression expression)
{
return expression_handlersexpression.NodeType;
}
static public object get_constant_value(Expression expression)
{
return expression.downcast_to<ConstantExpression>().Value;
}
static object instantiate_value(Expression expression)
{
var new_expression = expression.downcast_to<NewExpression>();
var args = new_expression.Arguments.Select(constructor_argument_expression => get_value_from_evaluating(constructor_argument_expression));
return new_expression.Constructor.Invoke(args.ToArray());
}
In the second line we are actually evaluating each of the constructor arguments expressions one at a time and having them also evaluated by the get_value_from_evaluating method. Because of the nested nature of expression trees, depending on the type of Expression that was passed in, this handles recursive traversal of the expression.
Once all of the arguments to the constructor have been evaluated, we can then invoke the expressions Constructor (which is a ConstructorInfo) to create the item. In our example here, it will cause the creation of a new KeyPressEventsArgs with the character of ‘A’ (the character was evaluated using the ConstantExpression we discussed earlier).
When it comes to testing a new control I can just create a new nested interface in my Events class, add public versions of the protected methods that I want to invoke (along with the correct arguments) and then I’m off to the races!!
Develop With Passion
Newer: Code is headed your way
Older: Getting In Contact With Me