I'm working on an Extension method and I'm having some problems with a part of it.
This is the idea: I want to create a method that takes an object and the name of an event, awaits for the event to be raised and then returns its event args. I'd use it to avoid having to manually create delegates and stuff every time I do something like this in my code.
NOTE: the code is in a PCL that targets Windows8.1 and Windows Phone 8.1, so some reflection methods are not available here.
This is what I have so far:
/// <summary>
/// Waits for an event to be raised and returns its arguments
/// </summary>
/// <typeparam name="Targs">The type of the event args to return</typeparam>
/// <param name="target">The target object that will raise the event</param>
/// <param name="eventName">The name of the event to wait</param>
/// <param name="timeout">The time limit (in millisecond) to wait for the desired event to be raised</param>
public static async Task<Targs> WaitEventAsync<Targs>(this object target, String eventName, int timeout)
where Targs : class
{
// Arguments check
if (target == null) throw new ArgumentNullException("The target object can't be null");
if (eventName == null) throw new ArgumentNullException("The event name can't be null");
if (timeout <= 0) throw new ArgumentOutOfRangeException("The timeout must be greater than 0");
// Get the target event
EventInfo eventInfo = target.GetType().GetRuntimeEvent(eventName);
if (eventInfo == null) throw new ArgumentException(String.Format("The target object doesn't contain the {0} event", eventName));
// Prepare the TaskCompletionSource, the return variable and the right handler
TaskCompletionSource<Targs> tcs = new TaskCompletionSource<Targs>();
Delegate handler;
if (eventInfo.EventHandlerType.Equals(typeof(EventHandler<Targs>)))
{
handler = new EventHandler<Targs>((sender, args) =>
{
tcs.SetResult(args);
});
}
else
{
// PROBLEM: when this line is executed, the AddEventHandler method crashes
handler = new TypedEventHandler<object, Targs>((sender, args) =>
{
tcs.SetResult(args);
});
}
// Add the handler and wait for the event
eventInfo.AddEventHandler(target, handler);
CancellationTokenSource cts = new CancellationTokenSource(timeout);
try
{
// If the event was triggered before the timout expired, return its args
return await tcs.Task.GetWatchedTask(cts);
}
catch (OperationCanceledException)
{
// If the timout expired, just return null
return null;
}
finally
{
// Remove the handler from the target object
eventInfo.RemoveEventHandler(target, handler);
}
}
Now, everything works just fine with standard events, so no problems here.
When I have RoutedEvents though, I get an exception.
I ended up trying to use the TypedEventHandler class as I didn't find another way to get the right delegate for those events (like all the pointer events).
But when I try to add the handler, I get an InvalidOperationException.
Is it possible to create handlers at runtime for RoutedEvents as well, or it is just not possible on WinRT?
Thanks for your help!
Sergio
You get that exception because WinRT won't allow you to add an event handler using the AddHandler method. The only way i know that can do this is by using this code:
private void AddHandler(FrameworkElement element, object parameter, EventInfo eventInf)
{
var addMethod = eventInf.AddMethod;
var removeMethod = eventInf.RemoveMethod;
var addParameters = addMethod.GetParameters();
var delegateType = addParameters[0].ParameterType;
Action<object, object> handler = (s, e) => ExecuteCommand();
var handlerInvoke = typeof(Action<object, object>).GetRuntimeMethod("Invoke", new[] { typeof(object), typeof(object) });
var @delegate = handlerInvoke.CreateDelegate(delegateType, handler);
Func<object, EventRegistrationToken> add = a => (EventRegistrationToken)addMethod.Invoke(element, new object[] { @delegate });
Action<EventRegistrationToken> remove = t => removeMethod.Invoke(element, new object[] { t });
WindowsRuntimeMarshal.AddEventHandler(add, remove, handler);
}
To remove the handler, use this:
WindowsRuntimeMarshal.RemoveEventHandler(remove, handler);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With