在这篇文章中,我将谈一下在windows phone 7.1 Mango应用程序中使用MVVM设计模式。用较少的理论、更多的示例,在10分钟内来解释MVVM模式。
在开始之前,先简短介绍一下什么是MVVM:Model-View-ViewModel (MVVM)模式提供了一种灵活的方式,通过将应用程序分隔成三部分来创建windows phone应用程序。
a:View:放置XAML文件。
b:ViewModel:放置连接UI和数据的显示逻辑层。
c:放置数据模型和业务对象。
有关更多信息,看参阅官方MSDN文档。
为什么选择MVVM?有什么好处?
视图和显示逻辑层分离:从显示逻辑层分离出来View/XAML,可以使开发者只关注Code,而设计人员只关注XAML。
自动单元测试:视图/逻辑分离大大改进了表现逻辑层的自动单元测试。
代码重用:因为显示逻辑层在独立的组件或类中,和View(XAML)分离开来,你可以按照你喜欢的方式,通过继承和组合使它们结合起来。
设计时数据支持: 你可以在Blend中看到UI的样子,也就是说,设计人员可以使用样本数据来测试UI,甚至可以模拟实际场景数据。
多视图: 依据用户角色,同一ViewModel可以在不同的多视图中展现。
入门开始:
我们需要Windows Phone 7.1 Mango工程项目。在这个实例中,我们会使用上篇文章 为Windows Phone Mango MVVM 应用创建可复用 ICommand 实现类 所创建的DelegateCommand。请注意:commanding是伴随mango更新的新特性。(commanding意味着一些控件不支持Commands)。
MODEL
首先我们需要定义Model。在示例中,我们创建Person类,有两个属性:Name和Age。在这里,最重要的就是要实现INotifyPropertyChanged接口,因为,当Person对象发现变化时,需要通知给UI。(本例中,当按下Save Changes时,更新ListBox,更多信息参阅下面部分中的View)
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace WPMangoMVVMSample
{
public class Person : INotifyPropertyChanged
{
private string name;
private int age;
public string Name
{
get
{
return name;
}
set
{
if (this.name != value)
{
this.name = value;
this.RaisePropertyChanged("Name");
}
}
}
public int Age
{
get
{
return this.age;
}
set
{
if (this.age != value)
{
this.age = value;
this.RaisePropertyChanged("Age");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, "Courier New", courier, monospace; background-color: rgba(255, 255, 255, 1) }
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }
VIEW MODEL
下一步就是创建PersonViewModel,主要包含以下部分:
SaveChangesCommand - 保存用户选择Person对象所引发的修改。
LoadDataCommand - 此命令是用来填充ObservableCollection(Person对象)。
SelectedName - 此属性表示选中的Person对象的名称。
SelectedAge - 此属性表示选中的Person对象的年龄。
SelectedPerson - 此属性表示所选中的Person对象。
PersonViewModel也实现了INotifyPropertyChanged,所有当某些属性更新时,UI会接到通知。 代码如下:
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WPMangoMVVMSample
{
public class PersonViewModel : INotifyPropertyChanged
{
private string name;
private int age;
private ObservableCollection
personDataSource;
private ICommand loadDataCommand;
private ICommand saveChangesCommand;
public PersonViewModel()
{
this.loadDataCommand = new DelegateCommand(this.LoadDataAction);
this.saveChangesCommand = new DelegateCommand(this.SaveChangesAction);
}
private void LoadDataAction(object p)
{
this.DataSource.Add(new Person() { Name = "John", Age = 32 });
this.DataSource.Add(new Person() { Name = "Kate", Age = 27 });
this.DataSource.Add(new Person() { Name = "Sam", Age = 30 });
}
private void SaveChangesAction(object p)
{
if (this.SelectedPerson != null)
{
this.SelectedPerson.Name = this.name;
this.SelectedPerson.Age = this.age;
}
}
public ICommand LoadDataCommand
{
get
{
return this.loadDataCommand;
}
}
public ICommand SaveChangesCommand
{
get
{
return this.saveChangesCommand;
}
}
public ObservableCollection
DataSource
{
get
{
if (this.personDataSource == null)
{
this.personDataSource = new ObservableCollection
();
}
return this.personDataSource;
}
}
public string SelectedName
{
get
//代码效果参考:http://www.lyjsj.net.cn/wx/art_23076.html
{if (this.SelectedPerson != null)
{
return this.SelectedPerson.Name;
}
return string.Empty;
}
set
{
this.name = value;
}
}
public int SelectedAge
{
get
{
if (this.SelectedPerson != null)
{
return this.SelectedPerson.Age;
}
return 0;
}
set
{
this.age = value;
}
}
private Person selectedPerson;
public Person SelectedPerson
{
get
{
return this.selectedPerson;
}
set
{
if (this.selectedPerson != //代码效果参考:http://www.lyjsj.net.cn/wx/art_23074.html
value){
this.selectedPerson = value;
if (this.selectedPerson != null)
{
this.name = this.selectedPerson.Name;
this.age = this.selectedPerson.Age;
}
this.RaisePropertyChanged("SelectedName");
this.RaisePropertyChanged("SelectedAge");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, "Courier New", courier, monospace; background-color: rgba(255, 255, 255, 1) }
//代码效果参考: http://www.lyjsj.net.cn/wz/art_23072.html
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }
VIEW
最后一步就是创建View。 我们仍然使用 MainPage.xaml。我们将创建:
"LoadData" 按钮: 调用LoadDataCommand填充数据源。
ListBox 绑定PersonViewModel,两个TextBox用来编辑选择的Person对象: 姓名和年龄。
"Save Changes" 调用SaveChangesCommand保存修改信息。
注意: TextBox的Binding 数据流向:Mode=TwoWay ,可以编辑和更新值。
[/span>StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"
[/span>Button Content="LoadData" Command="{Binding LoadDataCommand}" />
[/span>ListBox ItemsSource="{Binding DataSource}" SelectedItem="{Binding SelectedPerson, Mode=TwoWay}" Height</span>="100"
[/span>ListBox.ItemTemplate
[/span>DataTemplate
[/span>StackPanel Orientation="Horizontal"
[/span>TextBlock Text="Name:"/>
[/span>TextBlock Text="{Binding Name}" />
[/span>TextBlock Text="Age:" Margin="10,0,0,0"/>
[/span>TextBlock Text="{Binding Age}" />
[/span>TextBlock Text="Name:"/>
[/span>TextBox Text="{Binding SelectedName, Mode=TwoWay}" />
[/span>TextBlock Text="Age:"/>
[/span>TextBox Text="{Binding SelectedAge, Mode=TwoWay}" />
[/span>Button Content="Save Changes" Command="{Binding SaveChangesCommand}" />
.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, "Courier New", courier, monospace; background-color: rgba(255, 255, 255, 1) }
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }
绑定view至view model简单方法就是设置DataContext:
public MainPage()
{
InitializeComponent();
// simple way to bind the view to the view model
this.DataContext = new PersonViewModel();
}
.csharpcode, .csharpcode pre { font-size: small; color: rgba(0, 0, 0, 1); font-family: consolas, "Courier New", courier, monospace; background-color: rgba(255, 255, 255, 1) }
.csharpcode pre { margin: 0 }
.csharpcode .rem { color: rgba(0, 128, 0, 1) }
.csharpcode .kwrd { color: rgba(0, 0, 255, 1) }
.csharpcode .str { color: rgba(0, 96, 128, 1) }
.csharpcode .op { color: rgba(0, 0, 192, 1) }
.csharpcode .preproc { color: rgba(204, 102, 51, 1) }
.csharpcode .asp { background-color: rgba(255, 255, 0, 1) }
.csharpcode .html { color: rgba(128, 0, 0, 1) }
.csharpcode .attr { color: rgba(255, 0, 0, 1) }
.csharpcode .alt { background-color: rgba(244, 244, 244, 1); width: 100%; margin: 0 }
.csharpcode .lnum { color: rgba(96, 96, 96, 1) }
效果图:
源代码下载
译自:windowsphonegeek
由于本人翻译水平有限,有些地方欠妥,请园友们不吝指教!