• WPF命令


    WPF的命令是经常使用的,在MVVM中,RelayCommand更是用得非常多,但是命令的本质究竟是什么,有了事件为什么还要命令,命令与事件的区别是什么呢?MVVM里面是如何包装命令的呢?命令为什么能够触发呢?带着这些疑问,我们深入讲解下命令:

    首先看看命令系统的几个基本元素:

    1) 命令(Command):实现了ICommand接口的类,用得最多的是RoutedCommand.

      ICommand的成员:

      event EventHandler CanExecuteChanged;

      bool CanExecute(object parameter);确定此命令能否执行的方法

      void Execute(object parameter);执行命令调用的方法

    2) 命令源(Command Source):即命令的发送者,是实现了ICommandSource接口的类,很多界面元素都实现了这个接口,其中包括Button, MenuItem, ListBoxItem等。

      ICommandSource成员:

             ICommand Command{get;}   获取将在调用命令源时执行的命令

        object CommandParameter{get; } 命令参数。

                IInputElement CommandTarget{get;} 将在其上执行命令的对象。

    3)命令目标(Command Target):即命令讲发送给谁,或者说命令将作用在谁身上。命令目标必须是实现了IInputElement接口的类。

    4)命令关联(Command Binding):负责把一些外围逻辑与命令关联起来,比如执行之前对命令是否可以执行进行判断、命令执行之后还有哪些后续工作。

      CommandBinding的成员:

        public ICommand Command{get; set;} 与这个CommandBinding关联的ICommand。

       public event CanExecuteRoutedEventHandler CanExecute;

           public event ExecutedRoutedEventHandler Executed;

           public event CanExecuteRoutedEventHandler PreviewCanExecute;

           public event ExecuteRoutedEventHandler PreviewExecuted;

     下面先看看一个命令方面的例子:

    代码:

     1   private RoutedCommand clearCmd = new RoutedCommand("Clear", typeof(MainWindow));
     2         private void InitializeCommand()
     3         {
     4             this.button1.Command = clearCmd;
     5             this.clearCmd.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt));
     6             this.button1.CommandTarget = this.textBoxA;
     7 
     8             CommandBinding cb = new CommandBinding();
     9             cb.Command = this.clearCmd;
    10             cb.CanExecute += new CanExecuteRoutedEventHandler(cb_CanExcecute);
    11             cb.Executed += new ExecutedRoutedEventHandler(cb_Executed);
    12 
    13             this.stackPanel.CommandBindings.Add(cb);
    14 
    15         }
    16 
    17         void cb_CanExcecute(object sender, CanExecuteRoutedEventArgs e)
    18         {
    19             if (string.IsNullOrEmpty(this.textBoxA.Text))
    20             {
    21                 e.CanExecute = false;
    22             }
    23             else
    24             {
    25                 e.CanExecute = true;
    26             }
    27             e.Handled = true;
    28         }
    29 
    30         void cb_Executed(object sender, ExecutedRoutedEventArgs e)
    31         {
    32             this.textBoxA.Clear();
    33             e.Handled = true;
    34         }
    View Code

    UI上一个按钮,一个文本框,上级UI是StackPanel。
    实现的功能是当文本框里面没有内容的时候,按钮是不是能的,当有内容的时候,按钮使能。

    我们来对照着命令的几大要素,来分析下这个例子:

    首先一个clearCmd的路由命令,RoutedCommand,实现了ICommand接口。

    然后就是命令源,这里是Button,它实现了ICommandSource接口,在这里把clearCmd路由命令赋值给了其成员Command,把文本框赋值给了命令的目标。

    然后命令目标就是文本框,实现了IInputElement接口

    最后就是CommandBinding,这里给StackPanel的CommandBindings赋值,其中CommandBinding的Command赋值为clearCmd,并且定义两个事件驱动程序,用来处理CanExecute,Execute。

    另外还有快捷键的设置方式,这不是重点,我们重点看看命令的执行模式到底如何?工作原理是什么?

    通过调查,我发现是这样的:

    首先命令源会一直查询其Command,如果有就执行命令,然后就连接到了命令目标,命令目标激发路由事件,然后在外围的控件的CommandBinding监控下捕捉相关的路由事件,然后就会调用相关的事件处理程序。

    对应这个例子,是这样的,button作为命令源,赋值了clearCmd命令,然后通过查询并执行这个命令,命令连接到命令目标文本框,然后文本框激发出路由事件clearCmd,然后安装在StackPanel的CommandBinding监控下,如果是找到的命令的能否执行命令,就执行能否执行命令的事件处理函数,这里的能否执行返回的值直接决定了命令源是否可用。如果找到的命令式执行命令,就执行执行命令的处理函数,这里执行就把文本框清空。为了提高效率,一般都要e.Handled = True.

    我们可以看出,真正起作用的是CommandBinding,命令源的目的是告诉命令目标发了命令,还有让命令目标激光路由事件,命令目标的目的就是发生路由事件,CommandBinding赋值监听命令,执行命令。

    用通俗的话说,命令源就相当于火炮,命令相当于炮弹,命令目标相当于跑到要打的目标,命令关联就详单与侦察兵,在打炮弹之前的观察敌情,以及打扫战场等事情。

    命令源会不断的像命令目标投石问路,命令目标就会不断的发送路由事件PreviewCanExcecute和CanExcecute附加事件,命令被发送出来并达到命令目标,命令目标就会发送PreviewExecuted和Executed附加事件。命令关联捕捉到后,就会执行一些任务了。

    我们再来看看另外一个例子,我们自定义命令的例子:

     1   /// <summary>
     2     /// CustomRoutedCommand.xaml 的交互逻辑
     3     /// </summary>
     4     public partial class CustomRoutedCommand : UserControl,IView
     5     {
     6         public CustomRoutedCommand()
     7         {
     8             InitializeComponent();
     9         }
    10 
    11         public bool IsChanged { get; set; }
    12         public void SetBinding() { }
    13         public void Refresh(){}
    14         public void Save() { }
    15         public void Clear()
    16         {
    17             this.textBox1.Clear();
    18             this.textBox2.Clear();
    19             this.textBox3.Clear();
    20             this.textBox4.Clear();
    21         }
    22     }
    23 
    24     public interface IView
    25     {
    26         bool IsChanged { get; set; }
    27         void SetBinding();
    28         void Refresh();
    29         void Clear();
    30         void Save();
    31     }
    32 
    33     public class ClearCommand : ICommand
    34     {
    35         public bool CanExecute(object parameter)
    36         {
    37             throw new NotImplementedException();
    38         }
    39 
    40         public event EventHandler CanExecuteChanged;
    41 
    42         public void Execute(object parameter)
    43         {
    44             IView view = parameter as IView;
    45             if (view != null)
    46             {
    47                 view.Clear();
    48             }           
    49             //throw new NotImplementedException();
    50         }
    51     }
    52 
    53     public class MyCommandSource : UserControl, ICommandSource
    54     {
    55         public ICommand Command { get; set; }
    56         public object CommandParameter { get; set; }
    57         public IInputElement CommandTarget { get; set; }
    58 
    59         protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    60         {
    61             base.OnMouseLeftButtonDown(e);
    62             if (CommandTarget != null)
    63             {
    64                 this.Command.Execute(CommandTarget);
    65             }
    66         }
    67       
    68     }
    View Code

    也是点击一个按钮把4个文本框全部清空。
    同样的我们来分析下各个要素:

    命令:ClearCommand, 实现了ICommand接口,这里的命令执行方法,是执行命令参数的清除方法。

    命令源:MyCommandSource,这是一个用户控件,实现了ICommandSource接口,左键按钮里面如果有命令目标,就执行命令方法。

    命令目标是一个窗体,他实现了IView接口,接口里面有个清除方法。

    这里没有CommandBinding。实际执行的代码就是命令目标的IView接口的方法。

    当我们点击按钮的时候,触发了命令的执行,命令执行调用IView接口的方法。

    这个自定义的过程要比上面那个例子要好理解一点。

    上面的那个例子存在命令源,路由命令和CommandBinding三者之间的关系,命令并不真正执行逻辑代码,是靠CommandBinding来实现逻辑的,当我们真正自定义的命令的时候,如果想简单的使用命令,我们可以把逻辑放到命令里面去,这样便于管理,以上的自定义命令的例子就是这样。

    至于MVVM里面的RelayCommand命令,一样也是实现的ICommand接口。当我们把命令通过binding赋值给命令源的Command后,当命令源的命令触发的时候,就执行RelayCommand的方法,这个方法是一个委托方法,这样我们就可以通过Binding来把联系了控件和控件要执行的行为。

    命令跟事件可能否是在一起被触发的,比如在ButtonBase的OnClick方法里面是这样的:

    protected virtual void OnClick()
    {
            RoutedEventArgs e = new RoutedEventArgs(ButtonBase.ClickEvent, this);
           base.RaiseEvent(e);
           CommandHelpers.ExecuteCommandSource(this);
    }

    可以看出在Click里面,先激发路由事件,再执行命令。

    总结:对于命令而言,我们说的几大要素,命令,命令源,命令目标,命令关联,在路由命令中,一般都是存在的,命令在命令源的激发下,到命令目标,有可能没有命令目标,通过命令关联来监控并执行逻辑方面的事情。但是我们一般使用比如MVVM里面的RelayCommand,一般逻辑代码都是放在命令里面的,一般没有命令目标及命令关联。

    到目前为止,虽然大致对命令有了个了解,但是对于WPF预定义的一些命令没有完全理解,以及对于命令的好处也没有完全理解,以后随着使用越来越多,再去总结吧。

    http://files.cnblogs.com/files/monkeyZhong/RoutedCommandEg.zip

  • 相关阅读:
    牛客网 二叉树的镜像 JAVA
    牛客网 反转链表 JAVA
    牛客网 调整数组顺序使奇数位于偶数前面 JAVA
    Integer to Roman LeetCode Java
    Valid Number leetcode java
    Longest Common Prefix
    Wildcard Matching leetcode java
    Regular Expression Matching
    Longest Palindromic Substring
    Add Binary LeetCode Java
  • 原文地址:https://www.cnblogs.com/monkeyZhong/p/4600293.html
Copyright © 2020-2023  润新知