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





5.00/5 (1投票)
使用附加属性和 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);
}
如何发布事件
在实现上述代码后,您就可以发布事件并订阅它们了。您需要做两件事:
- 创建一个复合事件。
- 在 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
”!
结论
目前就这些。它奏效了,但有一个主要缺点,即每个控件只能有一个附加属性。因此,如果您有任何关于如何解决此问题的方法,请随时与我们联系。
另外,我们还没有对它进行任何性能测试。它似乎运行良好,但不能保证。另请注意,本文中的代码已删除了异常处理。
再次,请向我们提供关于我们处理此问题的方式的反馈、想法、批评等……我们非常希望尽可能地改进它。