使用MVVM模式开发自定义UserControl

简介: 本篇讲述使用MVVM来开发用户控件。由于用户控件在大部分情况下不涉及到数据的持久化,所以如果将M纯粹理解为DomainModel的话,使用MVVM模式来进行自定义控件开发实际上可以省略掉M,变成了VVM。

本篇讲述使用MVVM来开发用户控件。由于用户控件在大部分情况下不涉及到数据的持久化,所以如果将M纯粹理解为DomainModel的话,使用MVVM模式来进行自定义控件开发实际上可以省略掉M,变成了VVM。

一:基本结构

本演示样例包含两个项目,WpfControls是用户控件项目,我们的用户控件全部包含在这里。项目WpfApplication1是Wpf窗体项目,为调用方。我们的第一步的整体解决方案结构如下所示:

image

二:第一阶段源码

建立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部分的前台:

image

后台部分。由于是用户控件,所以我们将要属性直接绑定在控件上:

 
 
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public StudentViewModel Student
{
get
{
return this .studentsViewModel;
}
set
{
this .studentsViewModel = value;
}
}
}

现在,再来看调用方,即WpfApplication1的MainWindow,前台:

image

后台。出于简化演示需要,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 ;
}
}

运行效果为:

image

三:问题来了

第一阶段的UI没有采用绑定机制,如果我们的调用方也要采用类似MVVM模式的架构,则需要我们在使用用户控件的时候采用绑定机制。即,我们现在要将前台修改为:

image

可以看到,控件实例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,这将让我们必须采用绑定机制的理由。

四:实现一个列表

继续重构调用者代码,前台为:

image

后台:

 
 
StudentViewModel student1 = new StudentViewModel();
StudentViewModel student2
= new StudentViewModel();
ObservableCollection
< StudentViewModel > students = new ObservableCollection < StudentViewModel > ();

// 初始化
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语句:

image

这会使得在控件初始化的时候就生成一个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如下:

image

注意:

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

Creative Commons License本文基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 http://www.cnblogs.com/luminji(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。
目录
相关文章
|
1月前
|
开发框架 前端开发 JavaScript
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(10) -- 在DataGrid上直接编辑保存数据
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(10) -- 在DataGrid上直接编辑保存数据
|
1月前
|
开发框架 前端开发 搜索推荐
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(4) -- 实现DataGrid数据的导入和导出操作
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(4) -- 实现DataGrid数据的导入和导出操作
|
1月前
|
开发框架 前端开发 JavaScript
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(3)--自定义用户控件
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(3)--自定义用户控件
|
1月前
|
存储 开发框架 前端开发
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(9) -- 实现系统动态菜单的配置和权限分配
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(9) -- 实现系统动态菜单的配置和权限分配
|
前端开发 C# 图形学
【WPF】WPF开发用户控件、用户控件属性依赖DependencyProperty实现双向绑定、以及自定义实现Command双向绑定功能演示
Wpf开发过程中,最经常使用的功能之一,就是用户控件(UserControl)了。用户控件可以用于开发用户自己的控件进行使用,甚至可以用于打造一套属于自己的UI框架。依赖属性(DependencyProperty)是为用户控件提供可支持双向绑定的必备技巧之一,同样用处也非常广泛。
886 0
【WPF】WPF开发用户控件、用户控件属性依赖DependencyProperty实现双向绑定、以及自定义实现Command双向绑定功能演示
|
前端开发 C#
WPF 之 数据与命令绑定 (MVVM方式)
WPF 之 数据与命令绑定 (MVVM方式)
191 0
WPF 之 数据与命令绑定 (MVVM方式)
|
SQL 前端开发 C#
WPF MVVM模式
WPF MVVM模式
|
前端开发
MVVM模式下 DataTemplate 中控件的绑定
原文:MVVM模式下 DataTemplate 中控件的绑定   今天给ListBox中通过DataTemplate生成的Button绑定命令时,一开始Button始终找不到绑定的命令。现找到了正确的绑定方式,特来记录一下。
1708 0
|
前端开发 C#
使用MVVM DataTemplate在WPF XAML视图之间切换
原文 使用MVVM DataTemplate在WPF XAML视图之间切换 更新:这个技术的改进版本,一个不创建视图,可以在以下链接找到: http://www.technical-recipes.
1278 0
|
C#
WPF ViewModel与多个View绑定后如何解决的问题
原文:WPF ViewModel与多个View绑定后如何解决的问题 当重复创建View并绑定同一个ViewModel后,ViewModel中的字段更新,在新的View中的没有反应或者在View中找不到相应的视觉树(如ListBox的ListBoxItem) 初始的解决方案:View关闭后,注销属性Unregister Dependency。
1384 0