事件聚合器与附加属性的实现

在WPF应用程序开发中,经常需要在视图(View)和视图模型(ViewModel)之间进行事件的发布和订阅。为了实现这一目的,通常会使用事件聚合器(EventAggregator)来解耦视图和视图模型之间的依赖关系。本文将介绍如何通过附加属性(Attached Property)和事件聚合器来实现这一功能。

首先,定义了一个名为AttachedPropertyEvent的类,这个类位于基础设施项目中,以便其他项目可以引用。在这个类中,定义了三个附加属性:

第一个附加属性是CompositeEventNameProperty,它是一个字符串类型的属性,用于表示需要发布的复合事件的名称。这个属性不需要回调方法。

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

第二个附加属性是ListenForEventProperty,通过这个属性,可以指定需要监听的事件名称来发布事件。

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

第三个附加属性是ListenForPropertyProperty,通过这个属性,可以在某个属性发生变化时发布事件。

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

接下来,实现了一个名为HandleEvent的方法,这个方法使用反射(Reflection)来获取当前的事件聚合器,并发布事件。由于只有复合事件的字符串表示,而没有类型引用,因此需要使用反射的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(); Type eventAggregatorType = eventAggregator.GetType(); MethodInfo getEvent = eventAggregatorType.GetMethod("GetEvent"); MethodInfo method = getEvent.MakeGenericMethod(new[] { compositeEventType }); object compositeEventAggregator = method.Invoke(eventAggregator, null); MethodInfo publishMethod = compositeEventAggregator.GetType().GetMethod("Publish"); publishMethod.Invoke(compositeEventAggregator, new[] { eventArgs }); }

RaiseListenForEventCallback回调方法中,动态创建了一个代理(Delegate),以便在事件触发时调用HandleEvent方法。

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)); 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) }); il.Emit(OpCodes.Ret); Type finished = tb.CreateType(); MethodInfo eventHandler = finished.GetMethod("DynamicHandler"); Delegate del = Delegate.CreateDelegate(eventHandlerType, eventHandler); eventInfo.AddEventHandler(d, del); }

要发布事件,需要做两件事:创建一个复合事件,并在XAML代码中添加两行代码。例如,如果想在按钮按下时发布一个事件,可以这样写:

总结:通过使用附加属性和事件聚合器,可以在WPF应用程序中实现事件的动态绑定和发布,从而避免视图和视图模型之间的代码后端依赖。然而,当前的实现有一个限制,即每个控件只能发布一个事件。正在寻找解决这个问题的方法,如果有任何建议,请随时联系。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485