6

我想在 WinRT (Windows 8) 应用程序中使用 MVVM,我的要求之一是能够将事件与命令 ( ICommand) 挂钩。这意味着我必须动态地将处理程序添加到 WinRT 事件。这里有一个很好的解释如何做到这一点,但我的问题是处理程序类型在编译时是未知的(即它并不总是RoutedEventHandler像那个例子中那样)。

我开始编写该代码的通用实现,在其中使用表达式树构建委托。那部分有效。我的问题是WindowsRuntimeMarshal.AddEventHandler动态调用失败:

var rtMarshalType = typeof (WindowsRuntimeMarshal);
var eventHandlerMethod = rtMarshalType.GetRuntimeMethods().Single(x => x.IsStatic && x.Name == "AddEventHandler");
MethodInfo closedAddMethod = eventHandlerMethod.MakeGenericMethod(handlerType);
closedAddMethod.Invoke(null, new object[] {add, remove, handler});

这在Invoke调用中失败并引发 InvalidOperationException 并显示以下消息:

API 'System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeMarshal.AddEventHandler[ItemClickEventHandler](System.Func 2[Windows.UI.Xaml.Controls.ItemClickEventHandler,System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken], System.Action1[System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken], Windows.UI.Xaml.Controls.ItemClickEventHandler)'当前平台。有关详细信息,请参阅http://go.microsoft.com/fwlink/?LinkId=248273

我知道我有正确的类型,因为当我用这个替换上面的 4 行代码时(硬编码事件处理程序类型,这不是我想要的),然后代码工作并且事件按预期附加:

WindowsRuntimeMarshal.AddEventHandler<ItemClickEventHandler>(add, remove, handler);

作为参考,三个参数定义如下(可以肯定的是,这不是我的表达式树构建代码的问题,我目前正在使用这些委托的显式定义,即使尝试动态调用AddEventHandler,仍然失败):

Func<ItemClickEventHandler, EventRegistrationToken> add =
    a => (EventRegistrationToken) eventInfo.AddMethod.Invoke(instance, new object[] {a});
Action<EventRegistrationToken> remove = a => eventInfo.RemoveMethod.Invoke(instance, new object[] {a});
ItemClickEventHandler handler = (s, args) => command.Execute(args);

为什么通过反射调用而不是直接调用时调用失败?

当编译时事件处理程序的类型未知时,是否有用于动态附加 WinRT 事件的替代解决方案?

4

3 回答 3

5

为什么通过反射调用而不是直接调用时调用失败?

答:WindowsRuntimeMarshal.AddEventHandler是用[SecurityCritical]属性标记的

来自反射的安全注意事项:http : //msdn.microsoft.com/en-us/library/stfy7tfc.aspx

根据必要的权限,代码可以使用反射来执行以下类型的访问: 访问非安全关键的公共成员

AddEventHandler因此,即使它们是公开的,您也不能使用对安全至关重要的反射方法。您可以在不使用反射的情况下自由调用此类方法,并且可以自由地反射自己的方法。这就是您的解决方案有效的原因。

于 2012-10-16T02:31:58.253 回答
0

经过一些修补后,我找到了一种方法:

首先,我声明了这个方法:

    private static void AddEventHandler<T>(Func<T,EventRegistrationToken> add, Action<EventRegistrationToken> remove, T handler)
    {
        WindowsRuntimeMarshal.AddEventHandler(add, remove, handler);            
    }

然后我可以通过反射调用它:

    var closedAddMethod = GetAddEventHandlerMethodInfo(handlerType);
    closedAddMethod.Invoke(null, new[] {add, remove, actualDelegate});

在哪里GetAddEventHandlerMethodInfo

    private static MethodInfo GetAddEventHandlerMethodInfo(Type handlerType)
    {
        var rtMarshalType = typeof (AttachedCommand);
        var eventHandlerMethod = rtMarshalType.GetRuntimeMethods().Single(x => x.IsStatic && x.Name == "AddEventHandler");
        MethodInfo closedAddMethod = eventHandlerMethod.MakeGenericMethod(handlerType);
        return closedAddMethod;
    }

这行得通。正如问题中提到的,如果没有我自己的类的中间方法(即 WindowsRunTimeMarshal.AddEventHandler 作为 的直接目标),这将不起作用MethodInfo。我无法解释为什么会这样,但这是一个可行的解决方法,所以如果其他人有同样的问题,我会把它留在这里。

我不知道这是否会发生,因为微软试图防止一些意外使用或其他事情 - 如果有人真的知道为什么原始问题中描述的前一种方法不起作用;留下答案。

于 2012-10-10T17:04:48.513 回答
0

编辑:由于平台安全性,反射方法不起作用,就像 Silverlight 是为了确保应用程序在沙箱中运行并且不影响 .net 核心框架或利用该框架来克服已实施的安全措施。不幸的是,这会影响其他事情,例如功能的真正使用。Microsoft 不会让您反映可能被利用的平台类和方法。

不幸的是,像 Silverlight 一样,WinRT 的某些部分被锁定以将应用程序保留在沙盒中,但这并不是全部原因,但仍有一些 API 尚未开发。

Driis 你是对的,你最好的选择是创建一个封装 AddEventHandler 的包装器方法。

您可以在下面看到一个工作示例,创建对 AssignEvent 的写入反射调用会很简单。此示例假设您在屏幕上有一个名为“hellow”的文本块和一个名为“button”的按钮。

public MainPage()
{
    this.InitializeComponent();

    RoutedEventHandler handler = (a, b) => this.Hellow.Text = "MUAHAHAH";
    // you can quite easily reflect the method below.
    this.AssignEvent<RoutedEventHandler>(this.button, "Click", handler);
}

private void AssignEvent<T1>(object instance, string eventName, T1 handler)
{
    var runtimeEvent = instance.GetType().GetRuntimeEvent(eventName);
    var handlerType = runtimeEvent.EventHandlerType;
    Func<T1, EventRegistrationToken> add = (a) => { return (EventRegistrationToken)runtimeEvent.AddMethod.Invoke(instance, new object[] { a }); };
    Action<EventRegistrationToken> remove = (a) => { runtimeEvent.RemoveMethod.Invoke(runtimeEvent, new object[] { a }); };

    WindowsRuntimeMarshal.AddEventHandler<T1>(add, remove, handler);
}

方舟君的回答是正确的,因为该方法带有Security Critical Attribute修饰,它无法通过正常方式反映。这又是为了保护平台。

正如他所说,反射的安全考虑解释了一切。

于 2012-10-11T15:14:51.653 回答