MVVM中的命令绑定及命令参数

简介: 继续重构上一篇《Prism安装、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;
        }
    }

运行代码,你会发现一切已经如我们所愿。这里不妨停下来提供一下源代码:http://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来实现,就是轻而易举的事情了,只要将相应的类型用两个框架中对应的类型就可以了。

Creative Commons License本文基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 http://www.cnblogs.com/luminji(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。
目录
相关文章
WPF疑难问题之Treeview中HierarchicalDataTemplate多级样式
WPF疑难问题之Treeview中HierarchicalDataTemplate多级样式
726 0
|
存储 编译器 开发工具
LabVIEW源程序安全性保护综合方案
LabVIEW源程序安全性保护综合方案
199 3
LabVIEW源程序安全性保护综合方案
|
C# 容器
C#中的命名空间与程序集管理
在C#编程中,`命名空间`和`程序集`是组织代码的关键概念,有助于提高代码的可维护性和复用性。本文从基础入手,详细解释了命名空间的逻辑组织方式及其基本语法,展示了如何使用`using`指令访问其他命名空间中的类型,并提供了常见问题的解决方案。接着介绍了程序集这一.NET框架的基本单位,包括其创建、引用及高级特性如强名称和延迟加载等。通过具体示例,展示了如何创建和使用自定义程序集,并提出了针对版本不匹配和性能问题的有效策略。理解并善用这些概念,能显著提升开发效率和代码质量。
461 4
|
消息中间件 开发框架 .NET
.NET 8 强大功能 IHostedService 与 BackgroundService 实战
【11月更文挑战第7天】本文介绍了 ASP.NET Core 中的 `IHostedService` 和 `BackgroundService` 接口及其用途。`IHostedService` 定义了 `StartAsync` 和 `StopAsync` 方法,用于在应用启动和停止时执行异步操作,适用于资源初始化和清理等任务。`BackgroundService` 是 `IHostedService` 的抽象实现,简化了后台任务的编写,通过 `ExecuteAsync` 方法实现长时间运行的任务逻辑。文章还提供了创建和注册这两个服务的实战步骤,帮助开发者在实际项目中应用这些功能。
502 0
|
缓存 网络协议 安全
TCP首部格式【TCP原理(笔记五)】
TCP首部格式【TCP原理(笔记五)】
768 0
TCP首部格式【TCP原理(笔记五)】
|
监控 安全 数据安全/隐私保护
SNMPv3:网络管理的安全进化
【4月更文挑战第22天】
578 4
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的在线医疗服务系统的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue+uniapp的在线医疗服务系统的详细设计和实现(源码+lw+部署文档+讲解等)
154 0
|
传感器
[ROS2] --- topic
[ROS2] --- topic
574 0
|
监控 NoSQL JavaScript
看看人家的快速开发平台,确实清新优雅!
看看人家的快速开发平台,确实清新优雅!
看看人家的快速开发平台,确实清新优雅!
|
NoSQL 关系型数据库 MySQL
【Docker】Docker安装MongoDB最新版并连接使用附加docker常用命令
【Docker】Docker安装MongoDB最新版并连接使用附加docker常用命令
1081 0