zoukankan      html  css  js  c++  java
  • Prism 4 文档 ---第5章 实现MVVM模式

        MVVM模式有助于清楚的区分应用程序界面的业务层和展现层。保持一个清晰的应用程序逻辑和UI分离有助于处理开发和设计过程中大量的问题,同时,使得应用程序的测试,维护,和扩展更加容易。MVVM也可以极大的提升代码的可重用性也可以使开发人员和设计人员在分别开发应用程序的某一部分时更方便的沟通交流。
        运用MVVM模式,应用程序的UI和下层表现以及业务逻辑被拆分到三个单独到类中:视图,封装了UI和UI逻辑;视图模型,封装了展现逻辑和状态;模型,封装了应用程序的业务逻辑和数据。
        Prism包含了如何在Silverlight和WPF应用程序中实现MVVM模式的实例和参考实现。Prism类库也提供了有助于实现自己应用程序模式的功能。这些功能提现了最常用的实现MVVM模式的方法,并且设计为支持测试的并且能够同Blend和VisualStudio良好协同工作。
        本章节提供了对MVVM的概述并且描述了如何实现这些基本的功能。第6章描述了如果使用Prism类库实现更加高级的MVVM场景。
    类的责任与特征
        MVVM模式是展现模型(Presentation Model)模式的一个变种,它最大程度上使用了WPF和Silverlight的核心功能,例如数据绑定,数据模板,命令和行为。
        在MVVM模式中,View封装了UI和UI逻辑,View Model封装了展现逻辑和状态,Model封装了业务逻辑和数据,View通过数据绑定和命令一起更改通知事件同View Model进行交互。View Model查询。监视并同步更新Model,转换,验证和汇总需要显示在View中的数据。
    下面的插图展示了MVVM中的类和它们之间的交互过程。
        就像所有的表现层分离的模式一样,有效的使用MVVM模式的关键在于理解用适当的途径将应用程序的代码分解到正确的类中,理解这些类在复杂场景下的交互方式。下面的几个章节描述了MVVM模式中的类的职责和特征。
    View类
        View类的职责就是定义用户在屏幕上看到的界面的结构和外表。理想情况下,View类的后台代码中应该只含有调用InitializeComponent方法的构造方法。在一些情况下,View类的后台代码中可能包含一些使用XAML非常困难或者低效的可视化行为的UI逻辑代码,比如复杂动画,或者需要直接操作一些视图中的可视化元素。你不应该在View中放置任何需要单元测试的逻辑代码。通常,View的后置代码是通过UI自动化测试进行的。
        在Silverlight和WPF中,视图中的数据绑定表达是以她的上下文数据为依据的。在MVVM中,View的上下文在View Model中定义。View Model实现了视图可以绑定的属性和通过Change Notification事件可以通知View在当前状态中的改变的命令。通常,View和View Model之间是一对一的对应关系。
        通常,Views派生自Control类或者UserControl类,然而,在一些情况下,View可能是由数据模板来表现,也就是用来指定显示一个对象所用的UI元素。使用数据模板,可视化设计器可以简单的定义一个View Model是如何被渲染的,或者如何在不改变它的底层类的情况下改变其默认的显示形式,或者用于显示控件的行为。
        数据模板可以被看作是不包含任何后台代码的View。它们被设计用来绑定在任何时候可能被显示的View Model类型。在运行时,被View Model所定义的View将自动被实例化,并且将它们的数据上下文设置为与之相匹配的View Model。
        在WPF中,你可以在应用程序级关联数据模板和View Model,WPF 将会自动的指定的类型的View Model对象被UI显示的时候应用数据模板。这就是隐式的数据绑定。在Silverlight中,你必须明确的指定数据模板中用以显示ViewModel对象所使用的控件。无论在任何情况下,数据模板都可以与它所使用的控件一起以内联的方式在视图定义,或者以字典的形式在父视图之外,与视图的资源字典整合在一起。
        简而言之,View有以下几个关键特征:
    • 视图是一个可视化元素,比如Window,Page,User Control或者Data Template。View定义了视图中包含的控件和它们的可视化层次及样式。
    • View通过DataContext属性引用View Model。View中的控件绑定View Model 暴露的属性及命令。
    • View可以自定义View和View Model之间的数据绑定。例如,View可以使用转换器来格式化在UI中展示的数据,或者使用验证规则来提供保证用户输入数据的校验。
    • View定义和处理UI中的可视化行为,例如动画或者由View Model中的状态的改变或通过与用户交互引起的变换行为。
    • View的后台代码可以定义UI逻辑来实现对于XAML表达式实现起来比较困难或者直接要求引用定义在View中的特定UI控件可视化行为。
    View Model类
        MVVM中的ViewModel封装了View的展现逻辑和数据。它不包含视图的直接引用也不包含任何View的特定实现或者类型。ViewModel实现了View可以用来绑定的属性和通过Change Notification事件通知视图发送状态改变的命令。View Model提供的属性和命令定义了UI需要使用的功能,但视图仅仅关系这些功能是如何被渲染的。
        ViewModel负责用于协调视图与Model之间所可能存在的交互。通常,在ViewModel和Model之间是一种一对多的关系。ViewModel可能会向View直接暴露Model类这样View中的控件可以直接与之绑定。在这种情况下,Model类需要被设计成支持数据绑定和支持改变通知事件的类。关于更多的这种场景的的信息,请参考接下来的DataBing这一节。
        ViewModel可以转换或者操作Model层的数据使其容易被View层使用。ViewModel可以定义额外的属性来支持View的需求。这些属性通常将不会添加(或者不能添加到)Modlel中。例如,View Model可能需要合并两个字段使它们可以更容易的在View中展现,或者它需要计算有最大输入长度限制的文本框中还可以输入多少字符。View Model也可以实现数据验证规则来确保数据的一致性。
        ViewModel也可以定义UI状态转换使用的的逻辑状态。View可以定义反映View Model不同状态的布局和样式。例如,ViewModel可以定义一个只是数据已经异步提交到Web服务上的状态。在这个状态中,View可以显示一个动画给用户。
        通常,ViewModel将会定义UI中展现的用户可以操作的命令或者行为。常见的例子,View Model提供一个提交命令让用户将数据提交到Web服务或者数据库中。View可以选择使用一个按钮展示命令,那样用户可以点击这个按钮来提交数据。通常,当命令不可用是,关联它的UI元素也将不可用,命令提供了一种封装用户行为并将其与UI表现层分离的方式。
    简而言之,View Model有以下几个关键特点:
    • View Model是一个不可视的类,并且并非从WPF或者Silverlight的记录派生。它封装了应用程序中User case或者user task需要的表现逻辑。View Model 与View 和Mode是单独测试。
    • View Model通常来说并不直接依赖View,它实现View绑定需要的属性和命令。它通过用INotifyPropertyChangedINotifyCollectionChanged接口通知视图的任何状态变化 。
    • ViewModel负责协调View和Model之间的交互。它可以转换或者修改数据使得View可以更方便的使用,它也可以实现不在Model中的额外的属性。也可以通过IDataErrorInfo 或者 INotifyDataErrorInfo接口实现数据校验。
    • View Model 可以定义View显示用户的逻辑状态。
    注意:
        View还是ViewModel?
        许多时候,某些功能在哪里实现是很不明显的。一般的经验是:任何关注于UI显示或者屏幕上的视觉效果,在之后需要调整样式(即使现在还没计划这样做)都应该放到View中;应用程序中任何关乎逻辑行为的应该放到ViewModel中。另外,因为View Model中应该不包含任何关于View中具体UI元素的信息。所以编写有关操作View中UI元素的代码都应该放在View的后台代码中或者封装到行为中。同理,任何关于操作绑定到View中的数据项代码应该放在View Model中。
        例如,ListBox中选中项的高亮颜色应该定义在View中。但是列表展示的数据以及被选中数据的引用,都应该在ViewModel中定义。
    Model类
        MVVM中的Model类封装了业务逻辑和数据,业务逻辑定义了所有检索和管理应用数据的应用程序逻辑,和确保应用数据的一致性和校验规则的应用逻辑。
        通常,Model类标识了应用程序的客户端领域模型。它定义了已经有应用程序数据模型的数据结构以支持业务逻辑和验证逻辑。Model也可能包含了一些数据存取和缓存,虽然一般来讲会有一个独立的数据库或者服务支持它。通常,Model和数据访问层一部分是由数据介入或者服务策略所生成的,比如ADO.NET Entity Framework, WCF Data Services,或者WCF RIA Services。
        通常,Model实现了易于绑定到View的功能。这也意味这它通过INotifyPropertyChanged 和INotifyCollectionChanged 接口实现了属性和计划的改变通知机制。Model类中的对象集合通常派生自实现了INotifyCollectionChanged接口的ObservableCollection<T>类。
        Model也提供了数据验证和通过IDataErrorInfo (或者 INotifyDataErrorInfo)接口实现错误报告的功能。这个接口允许WPF和Silverlight的绑定在值发生变化时收到通知以更新UI,也可以保证UI层上的数据交易和错误报告。
        注意:
        如果Model类不是先必要的接口怎么办?
        有时候,你需要使用一些没有实现INotifyPropertyChangedINotifyCollectionChangedIDataErrorInfo,或者INotifyDataErrorInfo接口的model,在这种情况下,View Model就应该封装这些模型对象并且将需要使用的属性暴露给View。这些属性值由数据模型直接提供。View Model将会实现必要的接口并且暴露这些属性,使其更容易被View绑定。
    Model拥有以下关键特征:
    • Model类是封装了应用程序数据和业务逻辑的不可视的类。他们负责管理应用程序的数据并通过本身封装的验证逻辑和业务规则确保数据的一致性和合法性。
    • Model不直接依赖View或者ViewModel,并且也不依赖它们的实现。
    • Model类通常提供了实现INotifyPropertyChangedINotifyCollectionChanged接口的的属性和集合。它们使的View更容易绑定它们,Model类的对象的集合通常由ObservableCollection<T>派生。
    • Model类通过DataErrorInfo或者INotifyDataErrorInfo接口提供数据校验和错误报告的功能。
    • Model类通常和数据接入或者缓存服务一起使用。
    类集成
        MVVM模式通过将应用程序用户界面,表示逻辑和业务逻辑以及数据分离到不同的类中的方式提供了分离。因此,当你实现MVVM是,像上一节中描述的,非常重要的一点就是将程序中的代码归置到正确的类中。
        设计良好的View,View Model和Model类不仅仅封装了正确的类型代码和行为,而且被设计为通过数据绑定,命令和数据验证接口使得它们之间交互非常方便。
        View和View Model之间的交互或许是我们需要重点考虑的,但是Model和View Model之间的交互也是非常重要的。接下来的几节将会描述这样几种不同模式的交互以及描述当在应用程序中实现MVVM时是如何设计它们的。
    数据绑定

        数据绑定在MVVM中扮演了一个非常重要的角色,WPF和Silverlight中都提供了强有力的数据绑定能力。你的View Model和(理想情况下)你的Model类都应该支持数据绑定,那样就可以利用这些能力。通常,这意味这他们必须实现正确的接口。

        Silverlight和WPF数据绑定都支持多重绑定模式,使用单项绑定模式,UI控件可以绑定到View Model中,那样它们可以反映当界面展示渲染时背后绑定的数据值。使用双向绑定模式,当用户更新UI时将同时也会自动的更新底层数据。
        为了确保在View Model中的数据发生变化时UI能够保持最新,它需要实现适当的改变通知事件接口。如果它定义的属性可以被数据绑定,它需要实现INotifyPropertyChanged接口。如果View Model的表现对象是一个集合,它应该实现INotifyCollectionChanged接口,或者派生自实现了这个接口的ObservableCollection<T>类,这些接口都定义了一个在底层数据发生变化时都会触发的事件。当这些事件发生时任何绑定到控件的数据都会自动的更新。
        在许多情况下,View Model都会定义返回对象(返回的对象中可能定义了返回额外对象属性)的属性。WPF和Silverlight中的数据绑定支持通过Path属性进行绑定。因此,一个视图的View Modelf返回另外一个View Model或者 Model类是很常见的。所有访问View的View Model和Model类都需要实现INotifyPropertyChanged 或者 INotifyCollectionChanged接口。
        在接下来的几节中将会描述如何实现要求的接口来实现MVVM中的数据绑定的支持。
    实现INotifyPropertyChanged
        在ViewModel或者Model类中实现INotifyPeropertyChanged接口将会使得它们支持在底层数据发生变化时事件通知View中的控件的绑定。实现这个接口将会非常简单,就像下面的代码实例展示(参考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接口的View Model类。如下所示。
    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表达式必须评估每个调用。这样使用的优点是这种方式提供了实现编译的类型安全和支持反射当你重命名了一个属性时。虽然这点性能消耗非常小而且通常情况下不会影响应用程序,但是如果你有许多这样的通知属性,性能消耗也会产生。在那种情况下,你需要考虑使用非Lambda的方式实现。    
        通常,Model和View Model中将会包含那些通过其他属性值计算而来的属性,当处理属性变化时,确保引发这些计算而来的属性的通知事件。
    实现INotifyCollectionChanged
        你的View Model或者Model类可能表现为一些项的集合,或者是定义了一个或多个包含返回一些集合结果项的属性。在这些情况下,将会在一个ItemsControl中展示这些集合数据,例如者View中的ListBox或DataGrid控件。这些控件的数据源将会绑定到表现为一个机会或者一个返回集合结果的属性上。
    <DataGrid ItemsSource="{Binding Path=LineItems}" />
     

        为了正确支持更改通知请求,View Model和Model类,如果是一个集合,应该实现INotifyCollectionChanged接口(除了实现 INotifyPropertyChanged 接口以外)。如果View Model或者Model中定义了一个含有返回集合引用的属性,那么这个集合类也应该实现INotifyCollectionChanged接口。

        然而,实现INotifyCollectionChanged接口将会是个挑战,因为它需要在添加、删除、修改子项时提供属性变化通知。通常更简单的方法是使用或者是使用一个派生自一个已经实现了这个功能的类,而不是直接实现这个接口。ObservableCollection<T>类提供了这个接口的实现,并且它被广泛的用在了表现为集合项的基类和实现类中。
        如果你需要为View的数据绑定提供一个集合,而你并不需要跟踪用户的选择或者支持筛选、排序、或者分组集合中的子项的功能,你可以在View Model中仅定义一个包含了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的组件或者服务),你通常可以通过使用IEnumerable<T> 或者List<T>作为参数出入到一个ObservableCollection<T>实例的构造方法中对这些引用进行包装。
    实现ICollectionView
        在前面的代码中展示了如何实现一个返回了一个可以通过数据绑定方式在View的控件中展示的集合项的简单的View Model的属性。因为ObservableCollection<T> 类实现了INotifyCollectionChanged 接口,当集合中的项添加或者删除时View中的控件将会自动的更新。
        然而,你会经常需要更精细控制项显示在视图的集合,或跟踪用户的交互显示的项目集合,从内部视图模型本身。例如,你课呢个需要允许根据ViewModel中实现的展现逻辑对集合项数据进行筛选、排序,或者你需要保持对View中选中项的跟踪,来使得当前项被选中时,View Model中的实现的命令被调用。
    WPF和Silverlight通过提供了许多实现了ICollectionView 接口的类来提供对这种场景的支持。这个接口提供了集合被筛选、排序或者分组的属性和方法。并且允许当前选中项被跟踪和改变。在Silverlight中提供了实现此接口的PagedCollectionView类,WPF提供了实现此接口的ListCollectionView类。
        集合视图类通过底层计划的包装来实现自动选择跟踪和排序、筛选、分页集合数据工作。可以通过代码或者在XAML中使用CollectionViewSource 类的实例。
    注意:
        在WPF中,一个默认的集合视图在绑定到一个集合时会被自动的创建。在Silverlight中,一个集合视图只有在绑定到实现了ICollectionViewFactory接口的计划时才会自动创建。
        集合视图类可以在保持底层计划的一个重要状态信息当考虑保持UI和View及Model中底层数据之间清晰的分离时被使用。实际上,CollectionViews是特意为支持ViewModel中集合而设计的。
        因此,当你需要在View Model中定义一个实现筛选、排序、分组或者跟踪选中项的集合时,你的View Model应该为每一个计划创建一个集合视图类对象来暴露给View。然后你可以在View Model中订阅选中项改变事件,例如CurrentChanged 事件,或者使用计划视图提供的控件筛选、排序或者使用分组方法。
        View Model应该实现一个返回一个ICollectionView 引用的只读属性,那样,View中的控件可以绑定到集合视图对象并与之交互。在WPF和Silverlight控件中,派生自ItemsControl基类的类型将会自动的实现ICollectionView 类。
        下面的代码实例展示了在Silverlight中使用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中,你可以通过ViewModel中Customers属性绑定到一个ItemsControlItemsSource 属性,比如ListBox,如下所示:
    <ListBox ItemsSource="{Binding Path=Customers}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock Text="{Binding Path=Name}"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
        当用户在UI中选择了一个customer,View Model将会被通知,那样它才可以应用与当先选中的customer相关的命令。View Model也可以通过代码调用集合视图对象的方法来改变UI的当前的选中项。就像以下代码所示:
    Customers.MoveCurrentToNext();
        当集合视图中选中项变化后,UI将会自动的更新选中项的显示的可视化状态。在WPF中实现方式类似,上面的例子中的PagedCollectionView将会被ListCollectionView或者BindingListCollectionView类替换,如下所示:
    Customers = new ListCollectionView( _model );
    Customers.CurrentChanged += new EventHandler( SelectedItemChanged );
    命令
        除了提供的获取数据在View中展示或者编辑的功能,View Model似乎还定义了一个或者多个行为或操作可以被用户使用。在WPF和Sliverlight中,用户可以使用的行为或者操作通常被定义为命令。命令提供了一种可以被UI非常容易绑定行为或动作的方式。它们封装了实现了行为或者操作的实际代码并且使之与View中实际的可视化元素解耦。
        命令可以被可视化的展示,并且可以通过多种不同的方式被用户在同View交互时调用。绝大多数情况下,它们作为鼠标点击进行调用,但是它们也可以被快捷键,触摸手势,或者其他输入事件等调用。View中的控件绑定了View Model的命令,那样可以通过控件的任何输入事件或者动作调用命令。在View中UI控件和命令之间的交互有两种方式。在这种情况下,当用户在UI中交互时将会调用命令,当底层命令变的可用或者不可用时UI元素也可以自动的变得可用或者不可用状态。
        View Model可以以一个Command Method或者一个Command Object(一个实现了ICommand接口的对象)来实现命令。无论哪种情况,View中的交互都可以不用在View的后台代码中使用复杂的事件处理代码通过声明的方式定义为命令。例如,在WPF和Silverlight中某个继承了支持命令并提供了Command属性的控件就可以同View Model中提供的ICommand对象进行绑定。在其他情况下,一个命令行为可以用来关联控件和ViewModel中的命令方法或者命令对象。
    注意:
        行为是一种有力并且灵活扩展性的机制,它可以用来封装声明关联View中控件的交互逻辑。命令行为可以用来关联命令对象或者命令方法与并未专门设计命令交互的控件。
        接下来的几节将会描述View中是如何使用命令方法或对象来实现命令,并同控件进行关联的。
    实现命令对象
        命令对象是一个实现了ICommand接口的对象。这个接口定义了一个封装了自身操作的Execute方法,和一个表明了命令在特定时间内是否可以被调用的CanExecute方法。这两个方法都需要一个参数作为命令的参数。命令对象的操作逻辑的封装意味着它可以被更容易的用于单元测试和维护。
        实现ICommand接口非常简单直接。然而,却有很多你可以在程序中使用的实现这个接口的方式。例如,你可以使用Benld中的ActionCommand类或者Prism的DelegateCommand类。
        Prism的DelegateCommand类封装了各自实现了View Model中方法的引用的两个委托,它派生自通过调用委托实现了ICommand接口中ExecuteCanExecute方法的DelegateCommandBase基类。你可以在ViewModel中的DelegateCommand类的构造方法中指定委托,例如以下定义:
    public class DelegateCommand<T> : DelegateCommandBase
    {
        public DelegateCommand(Action<T> executeMethod,Func<T,bool> canExecuteMethod ): base((o) => executeMethod((T)o), (o) => canExecuteMethod((T)o))
        {
            ...
        }
    }
        例如,接下来的示例代码展示了一个展现为Submit命令的DelegateCommand实例是如何通过ViewModel中指定的OnSubmitCanSubmit方法委托被构造的。这个命令在后面通过一个返回一个ICommand引用的只读属性暴露给了View。
    public class QuestionnaireViewModel
    {
        public QuestionnaireViewModel()
        {
           this.SubmitCommand = new DelegateCommand<object>(
                                            this.OnSubmit, this.CanSubmit );
        }
     
        public ICommand SubmitCommand { get; private set; }
    
        private void OnSubmit(object arg)   {...}
        private bool CanSubmit(object arg)  { return true; }
    }
        当调用DelegateCommand对象的Execute方法时,它只是向前ViewModel类中调用方法通过在构造函数中指定的委托。类似,当调用CanExecute方法时,ViewModel中相应的方法也会被调用。在构造中指定CanExecute方法的委托是可选的。如果委托没有指定,DelegateCommand中的CanExecute将一直是True。
        DelegateCommand类是一个传统类型。类型的参数指定了传递到ExecuteCanExecute方法的参数。在前面的例子中,命令的参数是Object类型,Prism也提供了使用时不需要传参数的非一般的DelegateCommand类。
        View Model可以通过调用RaisCanExecutdChanged方法来表明DelegateCommand对象的命令是否可用的状态。这个可以引发CanExecuteChaned事件。任何UI中绑定了命令的控件将会根据绑定命令的是否额可用更新它们是否可用的状态。
        ICommand接口的一些其他的实现同样可用。Blend SDK中提供的ActionCommand类与前面所述的DelegateCommand相似,但是它只支持Execute方法委托。Prism也提供了CompositeCommand类,这个类允许将DelegateCommands一起分组执行。想了解关于CompsiteCommand更多内容,请参考第6章

    "Advanced MVVM Scenarios."中的"Composite Commands"    

     
    在View中调用命令对象
     
        这里有一系列的将ViewModel中提供的命令对象与View中的控件相结合的方法。在WPF和Silverlight4中的控件中,尤其是派生自ButtonBase的控件,例如,Button或者RadioButton,以及Hyperlink或者MenuItem等派生的控件,可以通过Command属性非常方便的绑定到命令对象上。WPF也支持将ViewModel中的ICommand绑定到一个关键手势。
    <Button Command="{Binding Path=SubmitCommand}" CommandParameter="SubmitOrder"/>
        命令参数可以通过CommandParameter属性可选的指定。期望的参数类型在ExecuteCanExecute的目标方法中指定。当用户同控件交互时控件将会自动的调用命令以及传递命令参数,如果提供了参数,将会将参数传递给命令的Execute方法。在前面的示例中,按钮在被点击时将会自动的调用SubmitCommand。另外如果CanExecute的处理被指定了,按钮将自动的根据CanExecute的返回结果变得可用或不可用。
        一个可供选择的方式就是使用Blend的交互触发器和InvokeCommandAction行为。
    <Button Content="Submit" IsEnabled="{Binding CanSubmit}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
                <i:InvokeCommandAction Command="{Binding SubmitCommand}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Button>
        这种方法可以用于任何一个添加了交互触发器的控件。如果过你想要将一个命令绑定到一个并非派生自ButtonBase的控件上或者或者你想通过一个并非通过点击事件调用一个命令的时候,它将非常有用。再次说明,如果需要提供命令参数,可以使用CommandParameter属性。
        不像其他控件那样可以直接可以绑定到一个命令,InvokeCommandAction没有根据命令的CanExecute方法返回值自动的启用或者禁用控件的功能,为了实现这个行为,需要将控件的IsEnabled属性绑定到ViewModel的适当的属性,例如之前面展示。
        注意:
        命令-可用控件VS行为
        WPF和Silverlight4的控件支持的命令使得你可以通过声明式的将控件和命令关联。当用户同控件在通过特定方式交互时,控件将会调用指定的命令。例如,一个Button控件,当用户点击时将会调用它的命令。关联到命令的事件是固定的不可改变的。
        行为也对你通过声明式的将控件和命令进行关联的支持。然而,行为可以关联控件引发的一系列的事件,它也可以用于根据条件关联ViewModel中的一个命令对象或者命令方法。换而言之,行为可以结局许多相同场景的控件是否可用的控制,并且它们可以提供一个具有更高灵活性的控件。
        你需要选择何时使用命令控制控件是否的方式以及何时使用行为,以及使用哪种行为。如果你倾向于使用一种单一的机制或者更方便的来关联View中的控件和ViewModel中的功能,你应该考虑使用行为,即使是那些继承自提供了命令绑定的控件。
        如果你只是需要命令控制控件是否可用的方式调用ViewModel中的命令,并且加入你也非常乐意使用默认的事件来调用命令,行为就并非必须的了。相似的,如果开发人员或者UI设计人员不使用Blend,你可能更倾向与使用命令控制控件是否可用方式(或者使用自定义附加行为),因为Blend需要了解额外的语法。

    在View中调用命令方法
        实现ICommand对象的命令的一种可选的方式就是通过在ViewModel中定义方法然后从View中直接使用行为来调用这些方法。
        这可以以类似的方式来实现通过行为调用命令。就像在上一节所展示的。然而,除了使用InvokeCommandAction,你也可使用CallMethodAction。下面的示例代码展示了调用ViewModel中(参数缺省)Submit方法。
    <Button Content="Submit" IsEnabled="{Binding CanSubmit}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Click">
                <i:CallMethodAction TargetObject="{Binding}" Method="Submit"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Button>
        这个TargetObject通过使用{Binding}表达式绑定到了底层的上下文数据(就是ViewModel)。Method参数指定了需要调用的方法。
        注意:
        CallMethodAction不支持参数;如果需要向目标方法传递参数,需要ViewModel提供属性,切换到使用InvokeCommandAction,或者编写自己版本的CallMethodAction来传递参数。
    数据验证和错误报告 
        View Model或者Model需要经常被要求执行数据验证并且在View中展示错误数据信号以使得用户可以去改正它们。Silverlight和WPF提供了管理在修改绑定到View中控件的个别属性的时候验证数据错误的支持,绑定到控件的单一数据,ViewModel或者Model可以在节点属性拒绝传入坏数据并抛出异常的方式展示一个数据验证错误的信号。如果数据绑定的ValidatesOnExceptions属性的值为True,那么WPF和Sliverlight的数据绑定引擎将会处理异常并且向用户展示一个可视化的提示这里有一个数据验证错误。
        然而,这种通过抛出属性异常的方式应该尽可能的避免。一个可选的方案就是在ViewModel或Model类中实现IDataErrorInfo或者INotifyDataErrorInfo接口。这些接口可以使得ViewModel或者Model执行一个或者多个属性的数据验证并且向View返回一个错误信息,那样用户可以修改这个错误。
    实现IDataErrorInfo
        IDataErrorInfo接口提供了对数据验证和错误报告最基本的支持。它定义了两个只读的属性:一个indexer属性,indexer的参数即属性名称,另一个是Error属性。这两个属性都返回一个字符串值。
        indexer属性使得ViewModel或者Model类为特定名称的属性提供了一个错误信息。如果返回值为一个空字符串或者null,就表明被改变的值是非法的。Error属性使的ViewModel或者Model类为整个对象提供了一个错误信息。注意,然而,这个属性不是刚才值得WPF或者Sliverlight的数据绑定引擎。
        IDataErrorInfo的indexer属性在数据绑定熟悉第一次显示时或者后来任何时候其值改变的时候将会被调用。因为Indexer属性在所有属性改变时都会被调用,所以你应该小心的确保数据验证尽可能地快速和搞笑。
        当你想要通过IDataErrorInfo接口验证与View中控件绑定的控件的时候,在数据绑定中设置ValidatesOnDataErrors属性值为True。这样可以保证数据绑定引擎可以获得数据绑定的错误信息。
    <TextBox
    Text="{Binding Path=CurrentEmployee.Name, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True }"
    />
    实现INotifyDataErrorInfo
        INotifyDataErrorInfo接口是一个比IDataErrorInfo接口更加灵活的接口。它支持多个属性错误,异步数据绑定,并且当对象的错误状态改变后有通知View的能力。然而,INotifyDataErrorInfo目前仅在Sliverlight4中支持,在WPF4中并不支持(WPF4.5中已经支持了)。
        INotifyDataErrorInfo接口定义了HasErrors属性,它表明了属性中是否存在一个错误(或多个错误),定义了一个GetErrors方法,它使得ViewModel可以返回一个特定的错误消息列表属性。
        INotifyDataErrorInfo接口也定义了一个ErrorsChanged事件,在Silverlight中通过ErrorsChanged事件使得View和ViewModel中错误状态的变化为特定属性支持异步验证场景。属性值可以通过一系列的方法被改变,而不仅仅是通过数据绑定。例如,将一个调用Web服务的返回值或者后台计算的值作为属性的值。ErrorsChanged使得View Model可以当一个数据验证错误一旦被证实时通知View一个错误信息。
    为了支持INotifyDataErrorInfo,你需要为每个属性维护一个错误信息的列表。MVVM RI演示了使用一个ErrorsContainer集合的方式保持一个对象的数据验证错误的跟踪。它在错误列表变化时引发错误事件。下面的代码展示了一个DomainObject(一个根模型对象)和一个通过使用ErrorContainer类实现了INotifyDataErrorInfo的实现。
    public abstract class DomainObject : INotifyPropertyChanged, 
                                         INotifyDataErrorInfo
    {
        private ErrorsContainer<ValidationResult> errorsContainer =
                        new ErrorsContainer<ValidationResult>(
                           pn => this.RaiseErrorsChanged( pn ) );
    
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
    
        public bool HasErrors
        {
            get { return this.ErrorsContainer.HasErrors; }
        }
     
        public IEnumerable GetErrors( string propertyName )
        {
            return this.errorsContainer.GetErrors( propertyName );
        }
    
        protected void RaiseErrorsChanged( string propertyName )
        {
            var handler = this.ErrorsChanged;
            if (handler != null)
            {
                handler(this, new DataErrorsChangedEventArgs(propertyName) );
            }
        }
       ...
    }
     
        Silverlight中,任何绑定到ViewModel属性的控件将会自动的定义INotifyDataErrorInfo事件并且如果属性包含错误是展示错误。
    构建和粘合
        MVVM模式有助于保持应用程序的UI同展示和业务逻辑及数据分离。所以使用MVVM模式的第一步就是将正确的代码在正确的类中实现是非常关键的。通过数据绑定和命令来管理View和ViewModel之间的交互也是需要重点考虑的一方面。下一步就是烤炉如何实例化View,ViewModel和Model,以及如何在运行时将他们有机的联系起来。
        注意:选择一个合适的策略来管理这一步对于使用了依赖注入容器的应用程序尤其的重要。MEF和Unity都提供了关联View,ViewModel和Model以及它们的容器之间特定依赖关系的能力。
        通常,在View和ViewModel是一对一的关系。View和ViewModel之间通过View的上下文属性保存松散耦合关系。这使得View中的可视化元素及行为可以帮绑定到ViewModel中的属性,命令以及方法。你需要决定如何管理在运行时View和ViewMode之间的实例以及通过数据上下文关联的关系。还必须小心当构造和连接视图和视图模型以确保保持松散耦合。正如在前一节中提到的,ViewModel应该不依赖于任何特定的实现一个View。同样,View应该不依赖于任何特定的ViewModel实现。
     
        注意:
        然而,应该指出的是,视图将隐式依赖于特定的属性,命令和方法的视图模型,因为它定义了数据绑定。如果视图模型没有实现所需的属性,命令,或方法,会产生一个运行时异常的数据绑定引擎,这将显示在Visual Studio调试期间输出窗口。

        有多种方法可以在运行时构建和关联View和ViewMode。最合适应用程序的方法将在很大程度上取决于首先创建View还是ViewModel,以及你是否这种编程方式或声明的方式创建。以下部分描述常见的在运行时创建并相互关联View和ViewModel类的方法。

    使用XAML创建View Model
        也许最简单的方法是在XAML声明视图实例化其相应的ViewModel。当View被构建的时候,与之对应的ViewModel也会被构建。你也可以在XAML中指定的ViewModel为View的上下文数据。
        基于XAML的方式在Basic MVVM QuickStart中的QuestionnaireView.xaml已经证明。在那个示例中,QuestionaireViewModel的实例就是在QuestionnaireView的XAML中定义的,如下所示:
    <UserControl.DataContext>
        <my:QuestionnaireViewModel/>
    </UserControl.DataContext>
     
       QuestionnaireView创建的时候,QuestionaireViewModel也会被自动的构建,并且设置为View的上下文数据。这种方式要求ViewModel必须有一个默认(无参的)的构造方法。   

        声明式构建和分配ViewModel的View的优点是,它非常简单,在设计工具中工作的很好,如Microsoft Expression Blend或Visual Studio。这种方法的缺点是,View知道了与之对应的ViewModel类型。

     
    使用代码创建View Model
       一种创建View的相对应的ViewModel实例的方式就是以编程的方式在它的构造方法中进行。它可以将ViewModel的实例作为它的上下文数据,如下所示:
     
    public QuestionnaireView()
    {
        InitializeComponent();
        this.DataContext = new QuestionnaireViewModel();
    }
       以编程的方式在后台代码中构建和分配View的ViewModel的方式的优点是简单和在比如Microsoft Expression Blend或Visual Studio工具中工作的比较好,其缺点是View选用知道与之相应的ViewModel类型以及在后台代码中知道对应的类型。使用依赖注入容器,比如Unity或MEF,有助于维护View和ViewModel之间的松散耦合。
     
    创建定义为数据模板的View
        一个View可以被定义为一个数据模板和与一个ViewModel类型有关。数据模板可以定义为资源,或者他们可以定义内联在控制显示ViewModel。View控件的“内容”就是ViewModel实例,以及使用数据模板用来直观地呈现它。WPF和Silverlight在运行时将自动实例化数据模板,并将其数据上下文指定ViewModel。这种技术的一个例子的情况先实例化ViewModel,其次是View的创建。
        数据模板是灵活和轻量级。UI设计师可以使用它们来轻松地定义一个ViewModel的可视化表示不需要任何复杂的代码。数据视图模板限制,不需要任何UI逻辑(后台代码)。可以使用Microsoft Expression Blend视觉设计和编辑数据模板。    

        下面的示例显示了一个绑定到一个列表的客户的ItemsControl。每个客户对象在底层集合是一个ViewModel实例。客户的View被定义为内联数据模板。在接下来的例子中,每个客户View的ViewModel由一个包含label和textbox绑定到Name属性ViewModel的StackPanel

    <ItemsControl ItemsSource="{Binding Customers}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock VerticalAlignment="Center" Text="Customer Name: " />
                    <TextBox Text="{Binding Name}" />
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
     

    您还可以定义一个数据模板作为一个资源。下面的例子显示了数据模板定义的资源和制通过StaticResource标记应用于内容控件。

    <UserControl ...>
        <UserControl.Resources>
            <DataTemplate x:Key="CustomerViewTemplate">
                <local:CustomerContactView />
            </DataTemplate>
        </UserControl.Resources>
    
        <Grid>
            <ContentControl Content="{Binding Customer}"                ContentTemplate="{StaticResource CustomerViewTemplate}" />
        </Grid>
    </Window>

        在这里,数据模板封装一个具体的View类型。这允许View定义后台代码的行为。通过这种方式,数据模板机制可用于外部提供View和ViewModel之间的关系模型。虽然前面的示例显示了UseControl模板的资源,它常常被放置在应用程序的资源以便重用。你可以在MVVM QuickStar中QuestionnaireView.xaml文件找到使用数据的模板实例化View,并关联他们与他们的ViewModel的例子。

     
    关键决定
     

        当你选择使用MVVM模式构建您的应用程序,你将不得不做出某些设计决策,将很难改变。一般来说,这些决策是应用程序范围及其在整个应用程序中一致的使用将提高开发人员和设计人员的工作效率。以下总结了实现MVVM模式时最重要的决定:

    • 决定你将采用的构建View和ViewModel的方式。你需要决定在应用程序将先定义View还是先构建ViewModel,以及是否采用一个依赖注入容器,比如Unity或者MEF。你通常会想要这是应用程序范围内一致的。更过信息,请看本章中的"构建和粘合"这一节以及第6章中的 "Advanced Construction and Wire-Up"。
    •  决定你将要在ViewModel中作为命令方法或者命令对象暴露给View的命令。暴露为命令方法非常简单并且可以通过View中的行为调用。命令对象可以整齐地封装命令和启用/禁用逻辑,可以通过行为或通过派生自ButtonBase控件的Command属性来调用。为了使得开发人员和设计人员跟好沟通交流,使之为一个应用程序级的选择将是一个不错的想法。更多信息请看本章的”命令“一节。
    • 决定ViewModel和Model将会如何向View报告错误。你的Model类妖魔支持IDataErrorInfo或者要么使用Sliverlight的INOtifyDataErrorInfo。不是所有的Model都需要报告错误信息。但是为了开发人员更方便的使用,最好这样做。更多信息情况本章的”数据验证和错误报告“一节。
    • 决定是否Microsoft Expression Blend设计时数据支持你的团队是很重要的。果你将使用Expression Blend设计和维护你的UI和希望看到设计时数据,确保你的View和ViewModel提供构造函数没有参数,为你的View提供了一个设计时数据上下文。或者,考虑使用微软提供的设计时特性Expression Blend使用设计时属性,如d:DataContextd:DesignSource。更多信息请看第7章的"Guidelines for Creating Designer Friendly Views"。
     
    更多信息

    关于WPF中的数据绑定更多信息,请看MSDN上的"Data Binding"http://msdn.microsoft.com/en-us/library/ms750612.aspx.

    关于Silverlight中的数据绑定更多信息,请看MSDN上的"Data Binding"http://msdn.microsoft.com/en-us/library/cc278072(VS.95).aspx.

    关于WPF中的数据集合绑定更多信息,请看MSDN上的"Data Binding Overview"中的"Binding to Collections"http://msdn.microsoft.com/en-us/library/ms752347.aspx.

    关于Silverlight中的数据集合绑定更多信息,请看MSDN上的"Data Binding"中的"Binding to Collections"http://msdn.microsoft.com/en-us/library/cc278072(VS.95).aspx.

    关于展现模式的更多信息,请看Martin Fowler's的博客"Presentation Model"http://www.martinfowler.com/eaaDev/PresentationModel.html

    关于数据模板的更多信息,请看MSDN的"Data Templating Overview"http://msdn.microsoft.com/en-us/library/ms742521.aspx

    关于MEF的更多信息,请看MSDN的"Managed Extensibility Framework Overview"http://msdn.microsoft.com/en-us/library/dd460648.aspx

    关于Unity的更多信息,请看MSDN的"Unity Application Block"http://www.msdn.com/unity

    关于DelegateCommand 和CompositeCommand的更多信息,请看第9章的, "Communicating Between Loosely Coupled Components."

  • 相关阅读:
    刷题记录:[ByteCTF 2019]EZCMS
    angularJS——自定义指令
    HTML5——语音输入
    jQuery表单验证插件——jquery.validate.js
    纯CSS气泡效果
    管理Cookie的插件——jquery.cookie.js
    网页打印
    CSS hack
    如何挑选适合的前端框架(去哪儿网前端架构师司徒正美)
    让DIV水平和垂直居中的几种方法
  • 原文地址:https://www.cnblogs.com/xixia/p/4026857.html
Copyright © 2011-2022 走看看