在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应用程序中实现事件的动态绑定和发布,从而避免视图和视图模型之间的代码后端依赖。然而,当前的实现有一个限制,即每个控件只能发布一个事件。正在寻找解决这个问题的方法,如果有任何建议,请随时联系。