mvvm模式,使用MVVM模式开发自定义UserControl

本篇讲述使用MVVM来开发用户控件。由于用户控件在大部分情况下不涉及到数据的持久化,所以如果将M纯粹理解为DomainModel的话,使用MVVM模式来进行自定义控件开发实际上可以省略掉M,变成了VVM。
一:基本结构
本演示样例包含两个项目,WpfControls是用户控件项目,我们的用户控件全部包含在这里。项目WpfApplication1是Wpf窗体项目,为调用方。我们的第一步的整体解决方案结构如下所示:
imagemvvm模式,使用MVVM模式开发自定义UserControl
二:第一阶段源码
建立UserControl1,要求能够对输入属性StudentName和Age,做出反应,即呈现在UI上。
首先创建ViewModel,即StudentViewModel:
public class StudentViewModel : NotificationObject { string studentName; public string StudentName { get { return studentName; } set { studentName = value; this.RaisePropertyChanged(() => this.StudentName); } } int age; public int Age { get { return age; } set { age = value; this.RaisePropertyChanged(() => this.Age); } } }

如果对于NotificationObject不熟悉的,可以参考《Prism安装、MVVM基础概念及一个简单的样例》。
UserControl1部分的前台:
imageimagemvvm模式,使用MVVM模式开发自定义UserControl
后台部分。由于是用户控件,所以我们将要属性直接绑定在控件上:
public partial class UserControl1 : UserControl { public UserControl1() { InitializeComponent(); } public StudentViewModel Student { get { return this.studentsViewModel; } set { this.studentsViewModel = value; } } }

现在,再来看调用方,即WpfApplication1的MainWindow,前台:
imageimageimagemvvm模式,使用MVVM模式开发自定义UserControl
后台。出于简化演示需要,WpfApplication1我们就不采用MVVM了,数据的获取,也直接在代码中硬编码:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { uc1.Student.StudentName = "lmj" + DateTime.Now.ToString(); uc1.Student.Age = 90; } }

运行效果为:
imageimageimageimagemvvm模式,使用MVVM模式开发自定义UserControl
三:问题来了
第一阶段的UI没有采用绑定机制,如果我们的调用方也要采用类似MVVM模式的架构,则需要我们在使用用户控件的时候采用绑定机制。即,我们现在要将前台修改为:
imageimageimageimageimagemvvm模式,使用MVVM模式开发自定义UserControl
可以看到,控件实例uc2 ,完全采用绑定的机制。采用绑定机制后的后台代码为:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } StudentViewModel student1 = new StudentViewModel(); StudentViewModel student2 = new StudentViewModel(); //初始化 private void Button_Click(object sender, RoutedEventArgs e) { //采用非绑定机制 uc1.Student = student1; //采用绑定机制 uc2.DataContext = student2; } //改Model值 private void Button_Click1(object sender, RoutedEventArgs e) { student1.StudentName = "lmj" + DateTime.Now.ToString(); student1.Age = 90; student2.StudentName = "hzh" + DateTime.Now.ToString(); student2.Age = 100; } }

我们没有对控件的属性值直接赋值,而是给控件的DataContext赋值,然后,通过该属性值的变化,前台就能相应的变化。注意,此时编译能通过,但是要运行代码会提示我们Student没有实现为DependencyProperty。这是因为WPF要求我们如果被用于绑定的,则必须要将属性注册为DependencyProperty。如果没有绑定上面的要求,则只要属性类型中的属性能够通过某种关系和INotifyPropertyChanged起来就OK(见源码中的NotificationObject)。
基于以上考虑,相应的,我们要将UserControl1修改一下,变为如下:
public partial class UserControl1 : UserControl { public UserControl1() { InitializeComponent(); } public static readonly DependencyProperty StudentProperty = DependencyProperty.Register("Student", typeof(StudentViewModel), typeof(UserControl1)); public StudentViewModel Student { get { return (StudentViewModel)GetValue(StudentProperty); } set { SetValue(StudentProperty, value); } } }

题外话:仅仅因为要采用MVVM,所以我们将代码重构为绑定模式,可能并不能说服你增加这些额外工作量。但是,想象一下这个场景,我们将不得不考虑采用绑定机制。即:显示一个学生列表,如果我们采用ListBox来绑定这个列表,势必采用DataTemplate,这将让我们必须采用绑定机制的理由。
四:实现一个列表
继续重构调用者代码,前台为:
imageimageimageimageimageimagemvvm模式,使用MVVM模式开发自定义UserControl
后台:
StudentViewModel student1 = new StudentViewModel(); StudentViewModel student2 = new StudentViewModel(); ObservableCollection students = new ObservableCollection(); //初始化 private void Button_Click(object sender, RoutedEventArgs e) { //采用非绑定机制 student1.StudentName = "lmj" + DateTime.Now.ToString(); student1.Age = 90; uc1.Student = student1; //采用绑定机制 uc2.DataContext = student2; //采用绑定机制,显示学生列表 lb1.ItemsSource = students; } //改Model值 private void Button_Click1(object sender, RoutedEventArgs e) { student1.StudentName = "lmj" + DateTime.Now.ToString(); student1.Age = 90; student2.StudentName = "hzh" + DateTime.Now.ToString(); student2.Age = 90; StudentViewModel svm1 = new StudentViewModel() { StudentName = "lmj", Age = 2 }; students.Add(svm1); foreach (var item in students) { item.StudentName = "lmj" + DateTime.Now.ToString(); } }

注意:
运行代码。结果我们很遗憾的发现,列表虽然在随着Click1的点击而增加,却并未能显示出来任何的Student内容。回到本文最开始的代码去看,我们发现,在用户控件的前台代码中,我们有这样3行XAML语句:
imageimageimageimageimageimageimagemvvm模式,使用MVVM模式开发自定义UserControl
这会使得在控件初始化的时候就生成一个StudentViewModel的实例,并绑定到控件的DataContext上,这导致在ListBox中的item元素在初始化的时候把PropertyChanged绑定到该事件上,而不是最终我们赋值给item的StudentViewModel实例。所以,应去掉这三行语句。记住,如果我们发现绑定失效,首先检测绑定元素的命名是否正确,其实就是检查绑定的实例是否只有一个。
到目前为止,源码如下:http://files.cnblogs.com/luminji/WpfApplication2.zip
五:问题来了
VM显然不应该作为控件的公开属性对调用者开放,为什么呢,因为VM的主要作用不是作为实体MODEL对外公开的,它的最重要的作用是作为联系UI和DOMAINMODEL的纽带,有点类似与MVC中的C,同时,它还可以包含一些自身的逻辑。所以,必须将VM中的实体Model部分(在本例中是Student)剥离出去,而仅仅保持逻辑部分。鉴于此,我们建立Student类型,并将其丢入UIModel中。
为了演示VM的逻辑功能,我们将UserControl1的前台加入一些命令绑定的指令(TextBlock的MouseLeftButtonDown),同时在VM中处理这些命令。修改后的UserControl1如下:
imageimageimageimageimageimageimageimagemvvm模式,使用MVVM模式开发自定义UserControl
注意:
1:前台引入了i和Behaviours两个命名空间。Behaviours中的ExecuteCommandAction,请直接查看源码,这里不再详细列出。
2:图中最后一个红框部分绑定了VM,但是VM不负责显示,所以TB的Height=0。
VM,即StudentViewModel如下:
public class StudentViewModel : NotificationObject { public StudentViewModel() { Clicked = new ActionCommand(this.Click); } public ICommand Clicked { get; private set; } public void Click(object arg) { //为了演示需要,在这里用了一个MessageBox //应尽量避免在VM中揉杂UI交互功能 MessageBox.Show((arg as Student).StudentName); } }

可以看到VM中已经完全没有实体信息了。当然,在本例中,实体信息作为参数可以通过Click方法被传入到VM中。
本例的最终源码的下载:http://files.cnblogs.com/luminji/WpfApplication3.zip
Tags:  mvvmpdf mvvm实例 wpfmvvm mvvm开发模式 mvvm模式

延伸阅读

最新评论

发表评论