65.9K
CodeProject 正在变化。 阅读更多。
Home

附加属性事件模式:第二部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2009年7月13日

CPOL

4分钟阅读

viewsIcon

17686

使用附加属性和 IEventAggregator 发布事件的模式。这次是通用的。

引言

在“附加属性事件模式”文章的第 1 部分中,我们描述了如何创建附加属性并从该属性发布 IEventAggregator 事件。这样做的原因是避免在视图中使用任何代码隐藏,并确保视图和视图模型之间没有依赖关系。

第 1 部分中描述的实现的缺点是,您需要为项目中的每个事件实现一个附加属性。我们希望以通用方式完成此操作,这就是我们将在本文中描述的内容。

通过我们的实现,有两种发布事件的方式。一种是发布事件(例如 KeyDown),另一种是在某个属性更改时发布事件。

当前限制

使用当前实现,每个控件只能发布一个事件。这是因为在控件中只能设置一次附加属性的值。我们正在研究一种解决此问题的方法。如果您有建议,请提出来!

实现

首先,我们实现了一个名为 AttachedPropertyEvent 的类。该类位于我们的 Infrastructure 项目中,以便任何人都可以引用它。在这里,我们有三个附加属性。

第一个附加属性名为“CompositeEventName”。这是必须附加到要发布的每个事件的复合事件的字符串表示形式。为了发布正确的事件,我们必须拥有此属性。此附加属性不需要回调方法。

public static DependencyProperty CompositeEventNameProperty = 
       DependencyProperty.RegisterAttached("CompositeEventName",
                                           typeof(string),
                                           typeof(AttachedPropertyEvent));

下一个附加属性名为“ListenForEvent”。使用此属性,您可以通过指定事件名称来发布事件。

public static DependencyProperty ListenForEventProperty = 
   DependencyProperty.RegisterAttached("ListenForEvent",
                                       typeof (string),
                                       typeof (AttachedPropertyEvent),
                                       new PropertyMetadata(RaiseListenForEventCallback));

此事件的回调方法使用中间语言 (Intermediate Language) 会有些复杂,但请参阅 MSDN 以获得更详细的解释:http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx

我们创建了一个名为“HandleEvent”的方法,并从 IL 调用它,而不是在 IL 中编写所有代码。HandleEvent 接收复合事件名称和一个对象。然后,它获取当前的事件聚合器,并使用反射来发布事件。我们在这里需要使用反射来实现“MakeGenericMethod”,因为我们只有复合事件的字符串表示形式,而没有对该类型的引用。

public static void HandleEvent(string compositeEventName, object eventArgs)
{
    string compositeEventString = 
      string.Format("_10100.Infrastructure.Events." + compositeEventName);
    Type compositeEventType = Type.GetType(compositeEventString);

    IEventAggregator eventAggregator = 
      ServiceLocator.Current.GetInstance<IEventAggregator>();

    Type eventAggregatorType = eventAggregator.GetType();
    MethodInfo getEvent = eventAggregatorType.GetMethod("GetEvent");
    MethodInfo method = getEvent.MakeGenericMethod(new[] { compositeEventType });
    object compositeEventAggregator = method.Invoke(eventAggregator, null);

    foreach (MethodInfo methodInfo in compositeEventAggregator.GetType().GetMethods())
    {
        Console.WriteLine(methodInfo.Name);
    }

    MethodInfo publishMethod = compositeEventAggregator.GetType().GetMethod("Publish");
    publishMethod.Invoke(compositeEventAggregator, new[] {eventArgs});
}

回到 ListenForEvent 附加属性的回调方法。如上所述,请查看 MSDN 链接以完全理解这里发生的事情,但我们添加了对“HandleEvent”的调用。

在运行时创建程序集的原因是我们 G需要调用 EventInfo.AddEventHandler。此方法接受一个委托,要创建此委托,我们需要了解有关事件的一些信息。因此,通过这样做,委托是动态创建的,我们无需担心它是哪种类型的事件、它具有哪种 EventArg,以及有多少参数。

private static void RaiseListenForEventCallback(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
{
    if (e.NewValue == null) return;

    string listenForEvent = (string) GetListenForEvent(d);
    string compositeEventName = (string) GetCompositeEventName(d);

    Type control = d.GetType();
    EventInfo eventInfo = control.GetEvent(listenForEvent);
    Type eventHandlerType = eventInfo.EventHandlerType;

    MethodInfo invokeMethod = eventHandlerType.GetMethod("Invoke");
    ParameterInfo[] parms = invokeMethod.GetParameters();
    Type[] parmTypes = new Type[parms.Length];
    for (int i = 0; i < parms.Length; i++)
    {
        parmTypes[i] = parms[i].ParameterType;
    }

    AssemblyName aName = new AssemblyName { Name = "DynamicTypes" };
    AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, 
                         AssemblyBuilderAccess.Run);
    ModuleBuilder mb = ab.DefineDynamicModule(aName.Name);
    TypeBuilder tb = mb.DefineType("Handler", 
                     TypeAttributes.Class | TypeAttributes.Public);

    MethodBuilder handler = 
      tb.DefineMethod("DynamicHandler",
                      MethodAttributes.Public | MethodAttributes.Static,
                      invokeMethod.ReturnType, parmTypes);

    ILGenerator il = handler.GetILGenerator();
    il.EmitWriteLine(string.Format("{0} event was raised!", 
                     eventHandlerType.Name));

    // Here's our call to the HandleEvent method. 
    Type apeMethodsType = typeof(AttachedPropertyEventMethods);
    MethodInfo handleEventMethodInfo = apeMethodsType.GetMethod("HandleEvent");
    il.DeclareLocal(typeof (string));
    il.Emit(OpCodes.Ldstr, compositeEventName);
    il.Emit(OpCodes.Stloc_0);
    il.Emit(OpCodes.Ldloc_0);
    il.Emit(OpCodes.Ldarg_1);
    il.EmitCall(OpCodes.Call, handleEventMethodInfo, 
                new[] { typeof(string), typeof(object) });
    // End of code for calling HandleEvent

    il.Emit(OpCodes.Ret);

    Type finished = tb.CreateType();
    MethodInfo eventHandler = finished.GetMethod("DynamicHandler");

    Delegate del = Delegate.CreateDelegate(eventHandlerType, eventHandler);
    eventInfo.AddEventHandler(d, del);
}

最后一个附加属性是“ListenForProperty”。使用此附加属性,您可以在属性更改时发布事件。

public static DependencyProperty ListenForPropertyProperty = 
   DependencyProperty.RegisterAttached("ListenForProperty",
                                       typeof (object),
                                       typeof (AttachedPropertyEvent),
                                       new PropertyMetadata(
                                           RaiseListenForPropertyCallback));

ListenForProperty”的回调方法使用上面描述的 HandleEvent 方法,将复合事件名称和您正在监听的控件属性的新值传递给它。

private static void RaiseListenForPropertyCallback(DependencyObject d, 
                    DependencyPropertyChangedEventArgs e)
{
    if (e.NewValue == null) return;

    AttachedPropertyEventMethods.HandleEvent(
      (string) GetCompositeEventName(d), e.NewValue);
}

如何发布事件

在实现上述代码后,您就可以发布事件并订阅它们了。您需要做两件事:

  1. 创建一个复合事件。
  2. 在 XAML 代码中添加两行。

我们在命名空间 _10100.Infrastructure.Events 中创建复合事件。目前,它已硬编码在“HandleEvent”方法中;我们的复合事件就在那里。要创建复合事件,您只需要:

public class MyCompositeEvent : CompositePresentationEvent<KeyEventArgs> {}

现在,要从 XAML 发布此事件,请在控件中执行以下操作:

<Button x:Name="btnSomeButton"
    Content="My Button"
    Events:AttachedPropertyEvent.CompositeEventName="MyCompositeEvent"
    Events:AttachedPropertyEvent.ListenForEvent="KeyDown"
    />

当您想监听某个属性时,执行相同操作,但不要使用附加属性“ListenForEvent”,而是使用“ListenForProperty”。

请注意,您必须始终在其他两个属性之前拥有“CompositeEventName”!

结论

目前就这些。它奏效了,但有一个主要缺点,即每个控件只能有一个附加属性。因此,如果您有任何关于如何解决此问题的方法,请随时与我们联系。

另外,我们还没有对它进行任何性能测试。它似乎运行良好,但不能保证。另请注意,本文中的代码已删除了异常处理。

再次,请向我们提供关于我们处理此问题的方式的反馈、想法、批评等……我们非常希望尽可能地改进它。

© . All rights reserved.