zoukankan      html  css  js  c++  java
  • 将WPF中控件的Event转换成Command执行(转)

    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
    方法。

  • 相关阅读:
    阅读笔记二
    阅读笔记一
    2017年秋季个人阅读计划
    问题账户需求分析
    阅读笔记(一)《软件需求与分析》需要掌握的内容
    我们应该创新吗?
    让你的英文文章看起来高级的一些词汇
    让你的英文文章看起来高级的一些词汇
    Windows的任务管理器怎么显示进程的图标
    如何和不好相处的人一起工作
  • 原文地址:https://www.cnblogs.com/aoldman/p/2781034.html
Copyright © 2011-2022 走看看