WPF入门(3)——命令

命令是ICommand类型的属性,binding到控件上,用于代替事件,个人认为事件也很好,命令只是轻度解耦前后端。
闲话少说,上代码,示例是ScreenToGif的源代码中的一个命令:

public ICommand OpenOptions
        {
            get
            {
                return new RelayCommand
                {
                    CanExecutePredicate = a => true, //TODO: Always let this window opens or check if there's any other recorder active?
                    ExecuteAction = a =>
                    {
                        var options = Application.Current.Windows.OfType<Options>().FirstOrDefault();
                        var tab = a as int? ?? 0; //Parameter that selects which tab to be displayed.

                        if (options == null)
                        {
                            options = new Options(tab);
                            options.Closed += (sender, args) =>
                            {
                                CloseOrNot();
                            };

                            //TODO: Open as dialog or not? Block other windows?
                            options.Show();
                        }
                        else
                        {
                            if (options.WindowState == WindowState.Minimized)
                                options.WindowState = WindowState.Normal;

                            options.SelectTab(tab);
                            options.Activate();
                        }
                    }
                };
            }
        }

这样看有些麻烦,我们省略一些暂时对理解不重要的东西:

public ICommand OpenOptions
        {
            get
            {
                return new RelayCommand
                {
                    CanExecutePredicate = a => true, 
                    ExecuteAction = a =>
                    {
                        //这是个lambda表达式
                    }
                };
            }
        }

实际上就是返回了一个new RelayCommand,这个RelayCommand是作者自定义的一个路由命令的类,代码如下:

    /// <summary>
    /// 路由命令
    /// </summary>
    internal class RelayCommand : ICommand
    {
        /// <summary>
        /// 作者自定义的字段 
        /// </summary>
        public Predicate<object> CanExecutePredicate { get; set; }
        /// <summary>
        /// 作者自定义的字段 
        /// </summary>
        public Action<object> ExecuteAction { get; set; }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="canExecute"></param>
        /// <param name="execute"></param>
        public RelayCommand(Predicate<object> canExecute, Action<object> execute)
        {
            CanExecutePredicate = canExecute;
            ExecuteAction = execute;
        }
        /// <summary>
        /// ICommand字段
        /// </summary>
        public RelayCommand()
        { }
        /// <summary>
        /// ICommand字段
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add => CommandManager.RequerySuggested += value;
            remove => CommandManager.RequerySuggested -= value;
        }
        /// <summary>
        /// ICommad字段
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(object parameter)
        {
            return CanExecutePredicate == null || CanExecutePredicate(parameter);
        }
        /// <summary>
        /// ICommand字段
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        {
            ExecuteAction(parameter);
        }
    }

使用路由的好处就是不管啥命令返回的都是RelayCommand类的实例,只要给该实例绑定上相应的方法就好了,方法可以随意写,上面作者就是为路由实例赋值了一个lambda表达式。

下面使用实例操作一下,下面的例子按下按钮之后修改button的context,为了方便我就直接使用上面ScreenToGif的RelayCommand。为了不与之前的代码冲突,我新加一个button:

新加的button的context同样是binding到Name属性,Command则是binding到了名为“ChangeName”命令上,该命令如下:

此时,MainViewModel.cs的所有代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace WpfApp.ViewModel
{
    class MainViewModel : INotifyPropertyChanged
    {
        #region INPC
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        #endregion


        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                OnPropertyChanged("Name");
            }
        }

        public ICommand ChangeName
        {
            get
            {
                return new RelayCommand() {
                    CanExecutePredicate = a => true,
                    ExecuteAction = a =>
                    {
                        Name = "ChangeName Command";
                    }
                };
            }
        }
    }

    /// <summary>
    /// 路由命令
    /// </summary>
    internal class RelayCommand : ICommand
    {
        public Predicate<object> CanExecutePredicate { get; set; }
        public Action<object> ExecuteAction { get; set; }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="canExecute"></param>
        /// <param name="execute"></param>
        public RelayCommand(Predicate<object> canExecute, Action<object> execute)
        {
            CanExecutePredicate = canExecute;
            ExecuteAction = execute;
        }
        /// <summary>
        /// ICommand字段
        /// </summary>
        public RelayCommand()
        { }
        /// <summary>
        /// ICommand字段
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add => CommandManager.RequerySuggested += value;
            remove => CommandManager.RequerySuggested -= value;
        }
        /// <summary>
        /// ICommad字段
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(object parameter)
        {
            return CanExecutePredicate == null || CanExecutePredicate(parameter);
        }
        /// <summary>
        /// ICommand字段
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        {
            ExecuteAction(parameter);
        }
    }
}

按照我的设定,按下第一个按钮会触发Button_Click方法(该方法在view的后台代码中,也就是我的工程中的MainWindow.xaml.cs文件)这是事件的方法,按下后会更改Name属性的内容,所有binding到该属性的控件内容都会跟随更改。
按下第二个按钮,则是触发ChangeName命令,该命令则是view的DataContext(也就是MainViewModel类)的ChangeName属性。按下后同样会修改Name属性的内容,所有所有binding到该属性的控件内容都会跟随更改。
下面debug一下:

喜大普奔,没有啥bug,哈哈哈哈哈哈

工程源代码上传在GitHub上了:https://github.com/feipeng8848/WPF-Demo

参考:https://www.codeproject.com/Articles/1052346/ICommand-Interface-in-WPF

posted @ 2019-10-09 10:40  feipeng8848  阅读(1468)  评论(2编辑  收藏  举报