最近实在有点恶心,于是就一直没有更新了。第五章我估计着要全部弄完还得看项目组里的新人什么时候看到这里。另外上次被管理员小小的黑了一把以后就不发首页了哈,我感觉我以前发了似乎也没事呃,上次的为啥就不行呢?算了不行就不行呗。另外求各位大神解答,如果要用Silverlight做一个视频聊天的应用是要用WCF RIA还是要用Socket做通信呢?纠结中啊,我试着用了无数的Socket模型,似乎只有Tcp Async的模型啊。可是做视频的话我怎么想也是用UDP协议会更好啊,不知如何解。。求各位大神帮助ING。
本节导读:
本节说明了MVVM模式中数据是如何绑定的,包括一般的属性和代表集合的属性。
5.2 类的交互
MVVM模式明确的分离了应用程序中的用户接口,表现逻辑,和业务逻辑及数据,并且将它们放到不同的类当中。因此,当实现MVVM时,要将应用程序代码正确的分解成为前文所述的不同类型。
设计优良的View,View Model,Model不仅仅是封装了正确的类型和行为;更是因为它们被设计成为能够通过数据绑定,命令,和数据验证接口进行交互。
也许View和View Model间的交互是最重要的,但是Model和View Model间的交互也很重要。下文将描述他们交互的几种形式并且说明在应用程序中是如何设计实现MVVM模式的。
5.2.1 数据绑定
数据绑定在MVVM模式中扮演一个很重要的角色。WPF和Silverlight都提供了很强大的数据绑定功能。View Model和Model(理论上)都应该被设计为支持数据绑定以便于它们享受到这些功能所带来的好处。通常来说,为了达到以上目的,就要实现正确的接口。
Silverlight和WPF支持多种绑定模式,在One-Way模式中,UI控件在显示时与View Model中的底层数据相绑定。Two-Way模式中数据绑定也会使当用户在UI修改数据时自动更新底层数据。
为了保证UI显示和数据保持同步,就需要使用正确的改变通知接口。如果定义了可以被数据绑定的属性,那么它就要实现INotifyPropertyChanged接口。如果View Model是一个集合,那么就要实现INotifyCollectionChanged接口或者让它继承于ObservableCollection<T>,该类提供了上文所述的接口。这些接口都提供了底层数据改变时的通知事件。所有数据绑定控件都会在这些事件触发时更新数据。
在一些情况下,View Model定义了返回对象的属性(或者一些属性返回额外的对象)。WPF和Silverlight数据绑定提供用以绑定内嵌属性使用的Path属性。因此,View的View Model返回其它View Model或者Model的对象的引用是一件很正常的事。所有需要和View相关的View Model或者Model都需要依情况而实现INotifyPropertyChanged或者INotifyCollectionChanged接口。
下文将描述如何为MVVM模式中的数据绑定实现必要接口。
5.2.2 实现INotifyPropertyChanged
在View Model和Model中实现INotifyPropertyChanged接口以保证在底层数据发生改变时通知View中所有参与数据绑定的控件以更新数据。该接口的实现直截了当,如下文所述(参见在the Basic MVVM QuickStart中的Questionnaire类)。
public class Questionnaire : INotifyPropertyChanged
{
private string favoriteColor;
public event PropertyChangedEventHandler PropertyChanged;
...
public string FavoriteColor
{
get { return this.favoriteColor; }
set
{
if (value != this.favoriteColor)
{
this.favoriteColor = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this,
new PropertyChangedEventArgs("FavoriteColor"));
}
}
}
}
}
在多个View Model类中实现INotifyPropertyChanged接口是一件重复并且容易出错的事,因为它需要在事件参数中指定属性名。Prism库提供了一种方便的基类,在需要使用它的时候,只需要继承于它,就可以用一种类型安全的方法实现INotifyPropertyChanged接口。
public class NotificationObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
...
protected void RaisePropertyChanged<T>(
Expression<Func<T>> propertyExpresssion )
{...}
protected virtual void RaisePropertyChanged( string propertyName )
{...}
}
一个继承于它的View Model类都可以通过调用RaisePropertyChanged方法触发事件,该方法通过直接指定或者使用Lambda表达式的方式来指定属性名。如下所示。
public string CurrentState
{
get { return this.currentState; }
set
{
if ( this.currentState != value )
{
this.currentState = value;
this.RaisePropertyChanged( () => this.CurrentState );
}
}
}
【注意】:使用Lambda表达式会引起一定的性能消耗,这是因为每次调用的时候该表达式都需要被计算一次。使用这种方法的好处是它提供了一个编译时的类型安全检查并且提供了在修改属性名时的自动重构。虽然由它所引起的性能消耗是很小的并且也不会影响正常程序的运行,但是如果程序中使用大量的通知改变事件那么这些消耗就会积少成多引起质变。在这种情况下,就应该考虑是否应该使用无Lambda表达式的重载。
通常,View Model和View中会包含一些需要通过其它属性计算以得出的属性。在提交数据改变的时候,不要忘了将通过它们所计算出的属性一并通知。
5.2.3 实现INotifyCollectionChanged
如果View Model或者Model表现了一些项目的集合,或者它们的一个或多个属性返回了一个集合。在这些情况下,很可能需要将这些集合在一个ItemsControl,比如View中的ListBox,DataGrid表现它们。这些控件都可以通过ItemSource属性来绑定到View Model中表现集合的部分。
<DataGrid ItemsSource="{Binding Path=LineItems}" />
在View Model和Model中需要实现正确的接口,如果说View Model或者Model本身需要表现集合,那么它们就应该实现INotifyCollectionChanged接口(有些情况还可能需要额外实现INotifyPropertyChanged接口)。如果说是它们中间的某个属性返回值代表一个集合,那么该属性的类型就应该实现INotifyCollectionChanged接口。
然而,实现INotifyCollectionChanged接口是比较有挑战性的,因为它需要保证在集合中的项被添加,删除,修改都能发出通知,所以通过都是简单的使用或者继承于已经实现该接口的类而不是直接实现它。ObservableCollection<T>是一个提供接口实现的类,通常可以直接继承于它或者用它来代表一个集合。
如果你需要在View中绑定一个集合,并且也不需要跟踪用户选中项或者提供筛选,排序,或者分组服务,就可以简单的定义一个属性,返回ObservableCollection<T>实例的引用就行了。
public class OrderViewModel : INotifyPropertyChanged
{
public OrderViewModel( IOrderService orderService )
{
this.LineItems = new ObservableCollection<OrderLineItem>(
orderService.GetLineItemList() );
}
public ObservableCollection<OrderLineItem> LineItems { get; private set; }
}
如果需要绑定一个集合的实例(比如,这个集合是从其它组件或者服务处得到并且没有实现INotifyCollectionChanged接口),封装到ObservableCollection<T>的实例中也是一种实现的方法,通过使用IEnumerable<T>或者List<T>构造实例即可。
5.3.3 实现ICollectionView
前文所述的代码讲述了将View Model中表示集合的属性绑定到View中的简单方法。因为ObservableCollection<T>实现了INotifyCollectionChanged接口,View中的控件可以在集合项被添加或者修改时自动更新以映射正确的项。
但是,有些时候可能需要更加完善的控制,每一项是如何显示到View中的,或者跟踪用户与集合项的交互,与View Model本身的交互。举例而言,有时需要根据View Model中的表现逻辑对集合进行筛选或者排序,有时需要对选中项进行根据以保证View Model中的Command可以作用于正确的项。
WPF和Silverlight提供了多种实现ICollectionView接口的类以支持这些场景。该接口提供了用以支持筛选,排序,分组,以及跟踪选中项改变所需要的属性和方法。WPF和Silverlight都提供了该接口的实现——Silverlight提供了PagedCollectionView类,WPF提供了ListCollectionView类。
Collection View类通过封装一个集合的方式以实现自动的选中跟踪以及筛选,排序,及分类服务。这些类可以通过程序定义也可以通过XAML的CollectionViewSource定义。
【注意】:在WPF中,当绑定集合发生时事实上都会产生一个默认的集合视图(泽注:以下使用集合视图代替Collection View)。在Silverlight中,则只有当被绑定的集合实现ICollectionViewFactory接口时才会自动产生集合视图。
集合视图类在View Model在被用来监控底层数据的重要状态信息,并且用以保证UI和Model中的底层数据中的数据分离和关注点分离。事实上,本身CollectionViews就是一个设计用以支持集合数据的View Model。
因此,如需在View Model内部实现监视被选中项改变,筛选,排序,或者分组,就要在View Model中为每一个集合实现集合视图并且将其暴露给View。之后就可以订阅选中改变事件,如CurrentChanged事件,或者在View Model中使用集合视图中提供的控件筛选,排序,分组方法。
View Model还需要实现一个只读属性并且返回ICollectionView引用以使View可以绑定到集合并且与之交互。所有从ItemsControl继承的WPF和Silverlight控件都可以自动的与ICollectionView类交互。
下文所述代码描述了Silverligth中用PagedCollectionView监视当前选中项的改变。
public class MyViewModel : INotifyPropertyChanged
{
public ICollectionView Customers { get; private set; }
public MyViewModel( ObservableCollection<Customer> customers )
{
// Initialize the CollectionView for the underlying model
// and track the current selection.
Customers = new PagedCollectionView( customers );
Customers.CurrentChanged +=
new EventHandler( SelectedItemChanged );
}
private void SelectedItemChanged( object sender, EventArgs e )
{
Customer current = Customers.CurrentItem as Customer;
...
}
}
在View中,将它与一个ItemsControl绑定,如ListBox,指定ItemsSource属性绑定View Model中的Customers属性。
<ListBox ItemsSource="{Binding Path=Customers}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
当用户在UI中选中一个客户时,View Model就会被通知以便命令可以使用正确的客户进行操作。View Model也可以通过编程的手段直接调用集合视图中的方法改变当前选中项,如下所示。
Customers.MoveCurrentToNext();
当集合视图中的当前选中项被改变时,UI自动将这一改变反应到视图中。在WPF中也是一样的实现方法,不过通常会使用ListCollectionView或者BindingListCollectionView取代PagedCollectionView类,如下所示。
Customers = new ListCollectionView( _model );
Customers.CurrentChanged += new EventHandler( SelectedItemChanged );