samedi 20 février 2016

UWP Subscribe to an event dynamically using generics and reflection

I'm trying to subscribe to a runtime event dynamically using generics and reflection. Purpose is to be able to subscribe to e.g. the PointerReleased event of a Grid using XAML and execute a command when the event is raised.

<Grid namespace:CommandExtension.Event="PointerReleased"
      ...>
</Grid>

So far i have the following code:

var eInfo = d.GetType().GetRuntimeEvent(e.NewValue.ToString());

if (eInfo != null)
{
    // Get the type of the event handler
    var handlerType = eInfo.EventHandlerType;

    // Create the add handler using CreateAddFunction and reflection
    var addFunc = typeof(CommandExtension).GetMethod(nameof(CreateAddFunction), BindingFlags.Instance | BindingFlags.NonPublic)
                                          .MakeGenericMethod(handlerType);
    dynamic add = addFunc.Invoke(new CommandExtension(), new object[] { eInfo, d });

    // Create the remove handler that is stored for removing the handler if the event changes
    Action<EventRegistrationToken> remove = (a) => { eInfo.RemoveMethod.Invoke(eInfo, new object[] { a }); };

    // Create the handler
    var action = new Action<object, RoutedEventArgs>(CommandInvoke);
    var invoke = action.GetType().GetMethod("Invoke");
    var expressionParams = handlerType.GetMethod("Invoke").GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
    var handler = Expression.Lambda(handlerType,
                                    Expression.Call(Expression.Constant(action),
                                                    invoke,
                                                    expressionParams[0], expressionParams[1]),
                                    expressionParams)
                            .Compile();

    // Add the handler
    var runtimeMarshalAdd = typeof(WindowsRuntimeMarshal).GetMethod("AddEventHandler", BindingFlags.Static | BindingFlags.Public)
                                                         .MakeGenericMethod(handlerType);
    runtimeMarshalAdd.Invoke(null, new object[] { add, remove, handler });
}

// T is the type of the event handler e.g. RoutedEventHandler
private Func<T, EventRegistrationToken> CreateAddFunction<T>(EventInfo eInfo, DependencyObject d)
{
    return new Func<T, EventRegistrationToken>((a) => { return (EventRegistrationToken)eInfo.AddMethod.Invoke(d, new object[] { a }); });
}

Now whenever runtimeMarshalAdd.Invoke is invoked an exception stating that the operation would not be available:
Exception thrown: 'System.InvalidOperationException' in mscorlib.ni.dll ("The API 'System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeMarshal.AddEventHandler[PointerEventHandler]
(System.Func'2[Windows.UI.Xaml.Input.PointerEventHandler,System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken], System.Action'1[System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken], Windows.UI.Xaml.Input.PointerEventHandler)' cannot be used on the current platform. See http://ift.tt/1mu4Xh2 for more information.")

However doing the same stuff non-generic works.

Button b = new Button();
var eInfo = b.GetType().GetRuntimeEvent("PointerReleased");

Func<PointerEventHandler, EventRegistrationToken> add = (a) => { return (EventRegistrationToken)eInfo.AddMethod.Invoke(b, new object[] { a }); };
Action<EventRegistrationToken> remove = (a) => { eInfo.RemoveMethod.Invoke(eInfo, new object[] { a }); };
PointerEventHandler handler = CommandInvoke;

WindowsRuntimeMarshal.AddEventHandler(add, remove, handler);

Am i missing something in my attempt of adding the handler?





Aucun commentaire:

Enregistrer un commentaire