zoukankan      html  css  js  c++  java
  • MVVM中的命令绑定及命令参数

    继续重构上一篇《Prism安装、MVVM基础概念及一个简单的样例》中的事例,在这一篇里我们将让命令绑定支持带方法参数。这是非常重要的一个编码需求。

    为了让本例支持更复杂的应用场景,我们这次要针对一个列表进行操作。

    1:建立领域模型

    public class StudentTeam: NotificationObject
        {
            string teamName;
            public string TeamName 
            {
                get 
                {
                    return teamName;
                }
                set
                {
                    teamName = value;
                    this.RaisePropertyChanged(() => this.TeamName);
                }
            }
    
            //获取模拟数据
            internal List<Student> MockGetStudents()
            {
                List<Student> students = new List<Student>(); 
                students.Add(new Student() { FirstName = "f1", LastName = "l1" });
                students.Add(new Student() { FirstName = "f2", LastName = "l2" });
                students.Add(new Student() { FirstName = "f3", LastName = "l3" });
                students.Add(new Student() { FirstName = "f4", LastName = "l4" });
                return students;
            }
    
        }

    领域模型完成的工作很简单,就是为了获取到一个详细的列表。同样,我们不关心数据来自与何方,这里只是进行了模拟。

    2:建立ViewModel

        public class StudentListViewModel : NotificationObject
        {
            public StudentListViewModel()
            {
                students = (new StudentTeam()).MockGetStudents();
            }
    
            List<Student> students;
    
            public List<Student> Students
            {
                get
                {
                    return students;
                }
                set
                {
                    students = value;
                    this.RaisePropertyChanged(() => this.Students);
                }
            }
    
            string someState = string.Empty;
    
            public string SomeState
            {
                get { return someState; }
                set { someState = value; this.RaisePropertyChanged(() => this.SomeState); }
            }
    
    
            public void Select()
            {
                SomeState = "a";
            }
    
            public void UnSelect()
            {
                SomeState = "b";
            }
        }

    vm中方法Select用于绑定某条记录被选择时触发,UnSelect则用于取消选择时触发。

    Students用于列表绑定,SomeState用于显示选定状态。

    3:建立View

    image

    注意图中红线部分。

    有一个细节要注意到,每一条记录都有一个CheckBox,由于采用了数据模版,默认CheckBox的绑定源对应的是ListBox的绑定源,而实际上,我们需要CheckBox对应的绑定源是VM对象,因为VM对象中才有Select和UnSelect方法,所以CheckBox的绑定源我们对应到了Grid上面。

    现在,可以运行了。界面如下:

    image

    4:问题来了,如何让绑定方法带参数

    由于Select和UnSelect都无法带参数,所以在VM中我们根本不知道是哪条记录被选中了。这个问题在这里特别突出,默认的命令绑定中Button的Command就是可以带参数的,但是这里的CallMethodAction就不允许带参数。

    有人觉得在VM中新增一个属性用来绑定当前记录就可以的,但是个人认为这是一种非常丑陋的实现。

    查看Prism和MVVM Light的源码,都创建了一个类似的类型,在Prism中是DelegateCommand,在MVVM Light中是RelayCommand,它们最终都继承自ICommand接口。使用这类类型,可以支持绑定带参数方法。

    不过,即便我们不使用这两个框架,有几类方法仍然能支持使用参数。如:

    1:在前台使用System.Windows.Interactivity命名空间下的InvokeCommandAction,在后台则使用Microsoft.Expression.Interactivity.Core下的ActionCommand也可以解决这个问题。当前SL子集中(4.0及以下)我没有直接实现ICommand接口的类型。

    2:或者,干脆我们自己实现一个Command类型。

    首先,我们来看第一种方法。

    5:使用InvokeCommandAction和ActionCommand解决方法带参数

    前台代码需修改的部分:

    image

    VM部分代码为:

        public class StudentListViewModel : NotificationObject
        {
            public StudentListViewModel()
            {
                students = (new StudentTeam()).MockGetStudents();
                Selected = new ActionCommand(this.Select);
                UnSelected = new ActionCommand(this.UnSelect);
            }
    
            List<Student> students;
    
            public List<Student> Students
            {
                get
                {
                    return students;
                }
                set
                {
                    students = value;
                    this.RaisePropertyChanged(() => this.Students);
                }
            }
    
            string someState = string.Empty;
    
            public string SomeState
            {
                get { return someState; }
                set { someState = value; this.RaisePropertyChanged(() => this.SomeState); }
            }
    
            public ICommand Selected { get; private set; }
    
            void Select(object obj)
            {
                SomeState = (obj as Student).FirstName;
            }
    
            public ICommand UnSelected { get; private set; }
    
            void UnSelect(object obj)
            {
                SomeState = (obj as Student).LastName;
            }
        }

    运行代码,你会发现一切已经如我们所愿。这里不妨停下来提供一下源代码:https://files.cnblogs.com/luminji/SilverlightApplication3.rar

    6:实现自己的ICommand类型

    所谓实现自己的ICommand,其实就是参考两大框架的代码,取而用之,在这里使用的MVVM Light的RelayCommand,并稍稍改之(泛型,其实你会注意到这里的泛型实现没有任何意义,但是我懒得再去修改了)。

       public class RelayCommand<T> : ICommand where T : class
        {
            private readonly Action<T> _execute;
    
            private readonly Func<bool> _canExecute;
    
            public RelayCommand(Action<T> execute)
                : this(execute, null)
            {
            }
    
            public RelayCommand(Action<T> execute, Func<bool> canExecute)
            {
                if (execute == null)
                {
                    throw new ArgumentNullException("execute");
                }
    
                _execute = execute;
                _canExecute = canExecute;
            }
    
            public event EventHandler CanExecuteChanged;
    
            public void RaiseCanExecuteChanged()
            {
                var handler = CanExecuteChanged;
                if (handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }
    
            public bool CanExecute(object parameter)
            {
                return _canExecute == null ? true : _canExecute();
            }
    
            public void Execute(object parameter)
            {
                _execute(parameter as T);
            }
        }

    相应的,因为使用自己的ICommand类型,所以,VM的代码也稍稍进行了修改:

        public class StudentListViewModel : NotificationObject
        {
            public StudentListViewModel()
            {
                students = (new StudentTeam()).MockGetStudents();
                Selected = new RelayCommand<Student>(this.Select);
                UnSelected = new RelayCommand<Student>(this.UnSelect);
            }
    
            List<Student> students;
    
            public List<Student> Students
            {
                get
                {
                    return students;
                }
                set
                {
                    students = value;
                    this.RaisePropertyChanged(() => this.Students);
                }
            }
    
            string someState = string.Empty;
    
            public string SomeState
            {
                get { return someState; }
                set { someState = value; this.RaisePropertyChanged(() => this.SomeState); }
            }
    
            public ICommand Selected { get; private set; }
    
            void Select(Student obj)
            {
                SomeState = obj.FirstName;
            }
    
            public ICommand UnSelected { get; private set; }
    
            void UnSelect(Student obj)
            {
                SomeState = obj.LastName;
            }

    这部分的代码可以直接重构上部分提供的源码,故不再提供下载了。

    7:题外话

    既然已经完全实现了自己创建类型的MVVM简单框架,想要把这部分功能使用Prism或Light来实现,就是轻而易举的事情了,只要将相应的类型用两个框架中对应的类型就可以了。

  • 相关阅读:
    Java判断一个实体类对象实例的所有成员变量是否为空
    正则表达式 整数
    将定时任务cron 解析成中文
    如何使用html定义一个红色小圆点
    Oracle获取当前日期前一个月的全部日期
    京东系统架构师如何让笨重的架构变得灵巧
    POI使用详解
    Java Excel 列号数字与字母互相转换
    使用exe4j将java项目打成exe执行程序
    Address already in use: JVM_Bind错误的解决
  • 原文地址:https://www.cnblogs.com/luminji/p/2062977.html
Copyright © 2011-2022 走看看