#2
Gavinzc2012-10-25 14:59
|
MVVM模式入门
MVVM会定义三部分,包括Model、View和ViewModel,下图中展示图片是来自我们silverlight课程中的幻灯片,以一种简明的方式总结了MVVM模式中各个部分。
只有本站会员才能查看附件,请 登录
通过每一部分的描述,可以看到Model代表了业务领域,包括实体类(如Customer、order等)、数据访问和业务规则,一般说来,你可以把 Model看做服务端的实体,也是负责与应用程序中数据交互的对象和填充实体的数据。也有人认为Model只代表了应用程序中的实体类(Customer、order等类),我个人认为它是更加广泛的,还包括了数据访问和业务规则。silverlight程序通过WCF、ASMX、 REST编写的服务甚至自定义的解决方案来调用Model中的代码。
View代表了你创建的silverlight页面,它包括了XAML文件和后台代码文件,负责将数据展示给终端用户。View的功能在于显示数据以及从终端用户处收集数据。View不负责检索数据,执行业务规则和检验数据。
ViewModel可以看做是View和Model的中间人,负责聚合存储数据,并将会绑定到View上。例如,一ViewModel可能包含 List<State>属性和List<Person>属性,绑定到View中的两ComboBox控件,ViewModel将会从Model中检索出这两个属性值,使用ViewModel,View不必担心在不知道数据来源的情况下检索数据。
附件的成员可能会被添加到Model-View-ViewModel中以实现进一步的隔离,例如,我通常会创建一service agent类,service agent初始化服务调用、捕获已返回的数据、发送数据到ViewModel。这样,ViewModel就把聚合数据的职责委托给了service agent。此service agent可以根据需要在多个ViewModel中重用。下图是展示的把service agent集成到MVVM模式中。
只有本站会员才能查看附件,请 登录
当开发人员第一次开始创建silverlight应用程序时,一般会把所有的代码都添加到后台文件中(例如MainPage.xaml.cs),虽然这样也无可厚非,但使用MVVM模式有很多有利点,如代码重用、易维护、代码模块化以及增强的测试支持,在以下的文章中,我将会侧重于通过使用MVVM模式获得的益处。
Model
有许多不同的方法来创建Model,如Microsoft Entity Framework、LINQ to SQL、nHibernate、PLINQO、SubSonic等,具体选用哪种技术要根据公司的开发规则,所有在这里我不打算对每种技术的优劣点进行讨论,重要的是使用工具或者手写代码来创建Model,包括定义类要暴露的所有属性,例如,下面是一简单的Model类:
程序代码:
public class Person
{
public string FirstName { get;set;}
public string LastName { get;set; }
public int Age { get; set; }
}
一旦Model类创建完毕,需要通过编写自定义代码或者ORM框架来填充数据,处理查询结果映射到对象实例。然后使用WCF、ASMX或者自定义的REST服务来编写Services,来暴露一个或者多个Model类,在Silverlight应用程序中使用。 {
public string FirstName { get;set;}
public string LastName { get;set; }
public int Age { get; set; }
}
View和ViewModel
在准备好Model后,View和ViewModel就可以创建了,View依靠ViewModel类来检索数据,然后绑定到ViewModel中的属性,而不是添加所有的代码到View的后台cs代码中。
ViewModel类需要实现INotifyPropertyChanged借口,其中定义了一个事件PropertyChanged,这个事件用来告知 silverlight绑定,数据已经改变,然后控件也可以自动更新,虽然INotifyPropertyChanged也可以在ViewModel类中直接实现,然而,你的应用程序中可能包含多个ViewModel类,那么就需要写很多重复的代码,创建一个ViewModel的基类,来实现 INotifyPropertyChanged,来达到代码重用的目的。下面的代码定义了一个ViewModelBase的类,实现了 INotifyPropertyChanged接口,该类也提供了OnNotifyPropertyChanged方法,来触发 PropertyChanged事件。
程序代码:
public class ViewModelBase : INotifyPropertyChanged
{
protected void OnNotifyPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
public bool IsDesignTime
{
get
{
return (Application.Current == null) || (Application.Current.GetType() == typeof(Application));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
ViewModel类继承自ViewModelBase,下面是一个叫PeopleViewModel的ViewModel类继承自ViewModelBase:{
protected void OnNotifyPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
public bool IsDesignTime
{
get
{
return (Application.Current == null) || (Application.Current.GetType() == typeof(Application));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
程序代码:
public class PeopleViewModel : ViewModelBase
{
IServiceAgent _ServiceAgent;
Person _Person;
ObservableCollection<Person> _People;
public PeopleViewModel() : this(new ServiceAgent()) {}
public PeopleViewModel(IServiceAgent serviceAgent)
{
if (!IsDesignTime)
{
_ServiceAgent = serviceAgent;
GetPeople();
}
}
#region Properties
public Person Person
{
get
{
return _Person;
}
set
{
if (_Person != value)
{
_Person = value;
OnNotifyPropertyChanged("Person");
}
}
}
public ObservableCollection<Person> People {
get
{
return _People;
}
set
{
if (_People != value)
{
_People = value;
OnNotifyPropertyChanged("People");
}
}
}
#endregion
public void GetPeople()
{
_ServiceAgent.GetPeople((s,e) => this.People = e.Result);
}
public void UpdatePerson()
{
_ServiceAgent.UpdatePerson(this.Person, (s, e) =>
{
PeopleEventBus.OnOperationCompleted(this, new OperationCompletedEventArgs { OperationStatus = e.Result });
});
}
}
我们可以看到PeopleViewModel定义了两个fields、两个properties和两个methods,每一个属性都会触发PropertyChanged事件,因为在set块中调用了在ViewModelBase中定义的OnNotifyPropertyChanged方法,在值改变时,会通知绑定到此属性上的控件自动更新。在PeopleViewModel中的第一个构造器中,将会调用第二个构造器,有一个IServiceAgent类型的参数。为什么会有两个构造器呢?这样设计,测试框架可以给ViewModel传入不同类型的service agents,当ViewModel在运行时被调用时,当参数的构造器会被调用,ServiceAgent的实例对象会作为IServiceAgent类型的参数传递。一旦service agent对象传给ViewModel的构造器,会调用GetPeople方法,最终会调用service agent中的方法。service agent会调用WCF服务,结果值分配给People属性。{
IServiceAgent _ServiceAgent;
Person _Person;
ObservableCollection<Person> _People;
public PeopleViewModel() : this(new ServiceAgent()) {}
public PeopleViewModel(IServiceAgent serviceAgent)
{
if (!IsDesignTime)
{
_ServiceAgent = serviceAgent;
GetPeople();
}
}
#region Properties
public Person Person
{
get
{
return _Person;
}
set
{
if (_Person != value)
{
_Person = value;
OnNotifyPropertyChanged("Person");
}
}
}
public ObservableCollection<Person> People {
get
{
return _People;
}
set
{
if (_People != value)
{
_People = value;
OnNotifyPropertyChanged("People");
}
}
}
#endregion
public void GetPeople()
{
_ServiceAgent.GetPeople((s,e) => this.People = e.Result);
}
public void UpdatePerson()
{
_ServiceAgent.UpdatePerson(this.Person, (s, e) =>
{
PeopleEventBus.OnOperationCompleted(this, new OperationCompletedEventArgs { OperationStatus = e.Result });
});
}
}
程序代码:
public interface IServiceAgent
{
void GetPeople(EventHandler<GetPeopleCompletedEventArgs> callback);
void UpdatePerson(Person p, EventHandler<UpdatePersonCompletedEventArgs> callback);
}
public class ServiceAgent : IServiceAgent
{
public void GetPeople(EventHandler<GetPeopleCompletedEventArgs> callback)
{
PeopleServiceClient proxy = new PeopleServiceClient();
proxy.GetPeopleCompleted += callback;
proxy.GetPeopleAsync();
}
public void UpdatePerson(Person p, EventHandler<UpdatePersonCompletedEventArgs> callback)
{
PeopleServiceClient proxy = new PeopleServiceClient();
proxy.UpdatePersonCompleted += callback;
proxy.UpdatePersonAsync(p);
}
}
{
void GetPeople(EventHandler<GetPeopleCompletedEventArgs> callback);
void UpdatePerson(Person p, EventHandler<UpdatePersonCompletedEventArgs> callback);
}
public class ServiceAgent : IServiceAgent
{
public void GetPeople(EventHandler<GetPeopleCompletedEventArgs> callback)
{
PeopleServiceClient proxy = new PeopleServiceClient();
proxy.GetPeopleCompleted += callback;
proxy.GetPeopleAsync();
}
public void UpdatePerson(Person p, EventHandler<UpdatePersonCompletedEventArgs> callback)
{
PeopleServiceClient proxy = new PeopleServiceClient();
proxy.UpdatePersonCompleted += callback;
proxy.UpdatePersonAsync(p);
}
}
绑定ViewModel至View
ViewModel可以绑定到View,可以在XAML中声明,也可以在CS后台代码中实现,分配一个ViewModel实例给布局元素根的DataContext属性
this.LayoutRoot.DataContext = new PeopleViewModel();
下面是一绑定ViewModel到Model的例子:
程序代码:
<UserControl x:Class="ViewModelExample.MainPage"
xmlns="http://schemas. xmlns:x="http://schemas. xmlns:d="http://schemas. xmlns:mc="http://schemas. xmlns:converter="clr-namespace:ViewModelExample"
xmlns:viewModel="clr-namespace:ViewModelExample.ViewModel"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<UserControl.Resources>
<viewModel:PeopleViewModel x:Key="ViewModel" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource ViewModel}}">
</Grid>
</UserControl>
ViewModel命名空间使用ViewModel作为XML命名空间的前缀,然后ViewModel使用关键字ViewModel定义在<UserControl.Resources>中,关键字是非常重要的,因为它“劫持”ViewModel至DataContext,layout的子元素可以绑定在ViewModel上,下面是绑定ListBox、StackPanel至ViewModel的People属性。xmlns="http://schemas. xmlns:x="http://schemas. xmlns:d="http://schemas. xmlns:mc="http://schemas. xmlns:converter="clr-namespace:ViewModelExample"
xmlns:viewModel="clr-namespace:ViewModelExample.ViewModel"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<UserControl.Resources>
<viewModel:PeopleViewModel x:Key="ViewModel" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource ViewModel}}">
</Grid>
</UserControl>
程序代码:
<StackPanel Margin="20">
<TextBlock Text="Binding Controls to a ViewModel" Margin="20,0,0,0" FontWeight="Bold" FontSize="12" />
<ListBox x:Name="lbPeople" Margin="0,10,0,0" Height="250" Width="300" HorizontalAlignment="Left"
ItemsSource="{Binding People}" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
SelectedItem="{Binding Person, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="10" Text="{Binding FirstName}" />
<TextBlock Grid.Column="1" Margin="10" Text="{Binding LastName}" />
<TextBlock Grid.Column="2" Margin="10" Text="{Binding Age}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding Person}">
<TextBlock Text="First Name" Margin="0,10,0,0" />
<TextBox Text="{Binding FirstName,Mode=TwoWay}" Width="100" Height="25" HorizontalAlignment="Left"/>
<TextBlock Text="Last Name" />
<TextBox Text="{Binding LastName,Mode=TwoWay}" Width="100" Height="25" HorizontalAlignment="Left"/>
<TextBlock Text="Age" />
<TextBox Text="{Binding Age,Mode=TwoWay}" Width="100" Height="25" HorizontalAlignment="Left"/>
</StackPanel>
<Button Margin="0,10,0,0" Click="Button_Click" Content="Submit" Height="20" Width="100" HorizontalAlignment="Left" />
</StackPanel>
MVVM模式提供了灵活的方式来处理数据,增加代码重用、简化、易维护。当然MVVM还有更多可讨论的地方,例如:event buses、commanding、dependency injection,但我希望这篇文章可以帮助你开发silverlight应用程序。<TextBlock Text="Binding Controls to a ViewModel" Margin="20,0,0,0" FontWeight="Bold" FontSize="12" />
<ListBox x:Name="lbPeople" Margin="0,10,0,0" Height="250" Width="300" HorizontalAlignment="Left"
ItemsSource="{Binding People}" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
SelectedItem="{Binding Person, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="10" Text="{Binding FirstName}" />
<TextBlock Grid.Column="1" Margin="10" Text="{Binding LastName}" />
<TextBlock Grid.Column="2" Margin="10" Text="{Binding Age}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel DataContext="{Binding Person}">
<TextBlock Text="First Name" Margin="0,10,0,0" />
<TextBox Text="{Binding FirstName,Mode=TwoWay}" Width="100" Height="25" HorizontalAlignment="Left"/>
<TextBlock Text="Last Name" />
<TextBox Text="{Binding LastName,Mode=TwoWay}" Width="100" Height="25" HorizontalAlignment="Left"/>
<TextBlock Text="Age" />
<TextBox Text="{Binding Age,Mode=TwoWay}" Width="100" Height="25" HorizontalAlignment="Left"/>
</StackPanel>
<Button Margin="0,10,0,0" Click="Button_Click" Content="Submit" Height="20" Width="100" HorizontalAlignment="Left" />
</StackPanel>
[ 本帖最后由 wangnannan 于 2011-10-8 13:52 编辑 ]