Wpf的DataBinding为我们将View与logic的分离提供了便利。特别是利用MVP模式,可以将大部分的UI交互逻辑通过绑定Command
来转移到Presenter中来,使我们可以专注于操作业务数据。但是微软提供的控件中好像还有很多的控件根本没有Command属性,
只有普通的Event。这样的话就没有办法利用绑定将逻辑转移到Presenter中。看到xaml.cs(每一个xaml文件的codebehind文件)文件
还是有很多的事件处理代码感觉非常不爽,于是想了个办法来将Event的处理转换成执行Command,这样就可以在Presenter里来处理
EventHandler的逻辑了。xaml.cs文件只有一个构造函数的感觉就是好,感觉视图和逻辑是真正的分离了。
首先来看一下用我的这个方法在xaml中怎么写。
<Button CommandBindingBehavior.BindingEventName="Click"
CommandBindingBehavior.BindingCommand="{Binding CloseMainWindowCommand}" Content="EventToCommand"/>
在Button中使用这两个AttachProperty,当Click事件触发的时候你所绑定的Command也会被执行,而你自己不用为Click事件写
任何的处理程序。
下面来看一下这两个AttachProperty具体是做了些什么事情。
其实思路很简单:在BindingEventName附加属性的PropertyChanged事件中通过反射在使用该附加属性的DependencyObject中
找到BindingEventName所指定Event,然后给它注册一个处理函数。Event处理函数的函数体是去执行BindingCommand属性所绑定
Command。这样就能达到Event被执行的时候所绑定的Command也被执行,而从表面上看好像是将Event转换成了Command来执行。
(看到这里你可能会觉得太简单了。其实不然,具体的实现过程还是没有那么的容易。)
第一步:定义两个AttachProperty(BindingEventNameProperty和BindingCommandProperty);
BindingEventNameProperty用来保存要转换成Command执行的Event名称;
BindingCommandProperty用来绑定要执行的Command.
public static class CommandBindingBehavior
{
public static DependencyProperty BindingEventNameProperty =
DependencyProperty.RegisterAttached("BindingEventName", typeof(string),
typeof(CommandBindingBehavior), new PropertyMetadata(null, OnBindingEventChanged));
public static DependencyProperty BindingCommandProperty =
DependencyProperty.RegisterAttached("BindingCommand", typeof(ICommand), typeof(CommandBindingBehavior), new PropertyMetadata(null));
}
第二步:写一个通用的事件处理方法。为什么说通用呢,因为他要被注册给所有类型的Event作为处理函数。
private static void OnEventRaised<T>(object sender, T arg) where T : EventArgs
{
DependencyObject dependencyObject = sender as DependencyObject;
if (dependencyObject != null)
{
ICommand command = GetBindingCommand(dependencyObject);
if (command.CanExecute(null))
{
command.Execute(null);
}
}
}
据我不完全观察,UI控件的事件处理程序的签名几乎都是void EventHandlerMethodName(object sender, T arg) where T:EventArgs;
为什么要定义这样一个函数呢,这个先不管,做完第三步就会明白。
第三步:实现OnBindingEventChanged事件处理程序(最关键的步骤)
private static void OnBindingEventChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg)
{
Type senderType = sender.GetType();
EventInfo eventInfo = senderType.GetEvent(arg.NewValue.ToString());
eventInfo.AddEventHandler(sender, GenerateDelegateForEventHandler(eventInfo, true));
}
方法说明:
从sender中通过反射拿到BindingEventName所指定的Event,然后通过调用AddEventHandler为其添加一个处理程序。
AddEventHandler的第二个参数是传递一个Delegate,这时就遇到了一个问题:不同的事件处理程序的签名不一样,
如果写一个普通的方法(比如已经实现的OnEventRaised),即使定义为泛型,也不能成功的转换为特定的Delegate.
貌似只能根据Event的类型动态的去获取其处理程序的签名,然后动态的创建一个方法出来才行。
看看GenerateDelegateForEventHandler方法的实现:
private static Delegate GenerateDelegateForEventHandler(EventInfo eventInfo)
{
Delegate result = null;
MethodInfo methodInfo = eventInfo.EventHandlerType.GetMethod("Invoke");
ParameterInfo[] parameters = methodInfo.GetParameters();
if (parameters.Length == 2)
{
Type currentType = typeof(CommandBindingBehavior);
Type argType = parameters[1].ParameterType;
MethodInfo eventRaisedMethod =
currentType.GetMethod("OnEventRaised", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(argType);
result = Delegate.CreateDelegate(eventInfo.EventHandlerType, eventRaisedMethod);
}
return result;
}
方法说明:
这个方法结束一个EventInfo对象,EventInfo对象包含了一个EventHandlerType的属性(即Event处理程序的delegate类型)。
然后通过反射拿到delegate的Invoke方法。(每个delegate的Invoke方法的签名是和该delegate的签名一样的,因此我们可以通过
这个方法的参数来判定该delegate的签名是怎样的)
我们通过反射拿到Invoke方法的参数类型(第一个参数是object类型,第二个参数的类型才是有用的);
Delegate类有一个CreateDelegate方法,他的原型如下:
CreateDelegate(Type, MethodInfo);
他通过接受一个Delegate的Type和一个MethodInfo描述的方法,可以创建一个Type所指定的Delegate对象.
所以现在关键就是如何去生成这个MethodInfo对象了。
我们之前创建的OnEventRaised泛型方法现在可以发挥作用了,我们通过反射可以拿到这个方法的MethodInfo。
但是这个对象不是我们想要的,因为他的第二参数的类型不一定和目标Delegate的签名一致。
MethodInfo的MakeGenericMethod可以帮助我们将OnEventRaised中的泛型参数替换成具体的参数,这样我们就可以用我们之前
拿到的Delegate的Invoke方法的第二个参数的类型来生成一个有用的MethodInfo了。
注:其实这里可能会有一个疑问:Delegate和MulticastDelegate类均没有Invoke这个方法,我们为什么能通过反射拿到一个
Delegate类型的Invoke方法呢?其实这是编译器在作怪,编译器会为每个delegate对象创建Invoke,BeginInvoke和EndInvoke
方法。