您的位置:首页 > 其它

将WPF中控件的Event转换成Command执行

2010-12-05 22:13 465 查看
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

方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: