编者注:本文译自Chirs Anderson的《Pro Business Applications With Silverlight 5》第11章,本人有意将全书译出,希望与有识之士合作,力求最终出版此书。平心而论,这本书无论从布局谋篇还是示例都很到位,是一本不可多得的深入学习SIlverlight5知识的教科书。
由于本章较长,计划分3个部分发表。今天发表第一部分。对于文中提到的诸如“第X章”,“如您所知”等字样,是与书中上下文相关的内容,在此不作过多解释。
有关字体的说明:
标题一律采用“标题4”,正文一律采用“Normal”,注解一律采用上下水平线分割。
至此,您已经掌握如何将UI控件与对象进行绑定以使用户可以查看和编辑这些对象所暴露的数据。数据不是被推进控件的,而是通过XMAL提供的绑定将数据拉进控件里。换言之,绑定是控件读取数据的过程。在此前几章里已经介绍了如下信息:
l 将数据对象分配到控件的DataContext属性;
l 有三种不同的绑定模式:OneTime,OneWay和TowWay(见第二章)。如果需要更新绑定源对象的属性要设置mode为TowWay;
l 当绑定属性值发生变化时,可以通过实现InotifyPropertyChanged接口作出通知;
l 当绑定属性值(或被绑定对象本身)无效导致异常时也可以通过实现IDataErrorInfo或INotifyDataErrorInfo接口作出通知;
l ObservableCollection
l 可以封装在Collection View里集合的视图,使得集合中的数据可以进行操控(即过滤,排序,分组和分页)而无需要修改其依赖的集合。Collection View还提供了一个当前记录的指针用于跟踪集合中的项目,使得多个控件可以绑定到同一Collection View以保持同步;
为了有效开发SilverLight业务应用程序,需要清晰理解数据绑定机制。本章,我们来看看一些高级数据绑定特征,在此通过一些话题的展开,充分领略SliverLight数据绑定引擎的强大之处。
分配绑定源
在第2章提到数据绑定时,已经指出数据绑定即要有source(下称源)又要有target(下称目标),绑定源来自于被绑定控件的DataContext属性。分配给DataContext属性的对象向下继承直到对象的每个层次,因此分配给Grid控件的DataContext属性,完全可以应用于该Grid控件包含的所有控件。
但是,如果你想要一个控件的属性绑定到控件的DataContext属性以外的属性时,例如资源,或另一个控件的某个属性,那么该怎样做呢?下面来看看。
使用绑定的Source属性
设置绑定的Source属性,需要使用对象作为绑定的source而不是将对象分配给控件的DataContext属性。如果在XAML中实现,一般采取使用StaticResource标记扩展
绑定到资源这种方式。
假定有一个Products对象被定义为资源,使用productResource作为其key。(“定义和实例化一个类作为资源用于绑定”在本章后面的“绑定到资源”一节中介绍)。绑定到这种资源的标准方法是使用StaticResource标记扩展将这种资源分配到目标控件的DataContext属性中。例如,可以将TextBox控件的Text属性绑定到Products对象资源 的Name属性,使用如下的XMAL:
但是,有时如果想要将控件的属性绑定到一个给定的源时,没有给其子控件修改继承的数据上下文(此处的数据上下文已经在某处设置到更高级的层次结构中)或者没有为其他属性修改绑定源。你可以通过使用binding的Source属性将TextBox控件直接绑定到资源而无需使用TextBox控件的DataContext属性绑定到资源的方式。如下的代码示例展示了此种绑定方法:
注:另外,如果在代码中创建数据绑定,可以分配任何对象到此属性中充当绑定的源。
ElementName绑定
第5,6章,我们看到了如何通过过使用ElementName 绑定将各种控件的ItemsSource属性绑定到DomainDataSource控件的Data属性上,但是尚未对此进行深入探讨。DomainDataSource控件从服务器获取数据并通过其Data属性公开暴露数据。然后我们就通过将ListBox,DataGrid或DataForm控件的ItemsSource属性绑定到DomainDataSource控件的Data属性以利用了这些公开暴露的数据,实际上也就是一个控件的属性绑定到另一个控件的属性上。如果要绑定一个控件的属性到另一个在视图中的控件(已命名)的属性上,我们需要使用特殊的绑定方法称之为ElemnetName绑定。使用bingding 的ElementName属性,需要提供在同一命名空间里可以充作绑定源的控件的名称(而不是分配给控件的DataContext属性的对象)。下面一些例子展示了这种绑定。
一个简单的例子就是将两个TextBox控件的Text属性时行连接。当修改一个TextBox里的文本时,第二个TextBox的文本也会自动进行更新:
<TextBox Name="FirstTextBox"/>
<TextBox Name="SecondTextBox" Text="{Binding Text, ElementName=FirstTextBox}" />
StackPanel>
注:如果将第二个TextBox的绑定模式设置为TowWay,修改第二个TextBox也会更新第一个TextBox。第一个TextBox只有在第二个TextBox失去焦点时才会更新。
类似地,可以将TextBlock控件的Text属性绑定到Slider控件的Value属性上,从而可以在TextBlock上显示Slider控件的当前值。
<Slider Name="sourceSlider" />
下面示例中的XMAL展示了ListBox控件的ItemsSource属性绑定到DomainDataSource控件(名为productSummaryDDS)的Data属性:
下面这个例子展示了BusyIndicator控件的IsBusy属性绑定到DomainDataSource控件(名为)的IsBusy属性。当DomainDataSource控件的IsBusy属性值为ture时会显示BusyIndicatior控件:
<controlsTollkit:BusyIndicator IsBusy=”{Binding ElementName=productSummaryDDS, Path=IsBusy}” />
最后一个例子,将Lable控件的Target属性绑定到TextBox上去。注意,没有指定绑定的路径,而是将Target属性绑定到了TextBox自身:
<sdk:Label Content=”Name:” Target=”{Binding EmementName=ProductNameTextBox}” />
RelativeSource绑定
在绑定的标记扩展中有一个RelativeSource属性,使得我们可以绑定到相对于目标的源上。前面介绍了的ElementName绑定是将一个控件的属性绑定到另一个控件的属性上。但是,只有当源控件进行了命名时ElementName绑定才可以使用。而RelativeSource则可以绑定到相对于目标控件的未命名源控件上。
通过使用RelativeSource标记扩展可以取得一个对源控件的引用,返回的值可以分配给绑定的RelativeSource属性。RelativeSource标记扩展有三种模式:Self,TemplateParent和FindAncestor。下面依次看看。
Self 模式
Slef模式返回目标控件本身,用于绑定同一控件的两个属性。如果想要将控件自身的属性与控件的附加属性进行绑定时,这一模式就能发挥作用。例如,如果想要让用户在TextBox上停靠显示相关工具提示时,需要在提示中显示文本框内的所有文本。在这里,我们需要使用ToolTipService.ToopTip附加属性(见第2章)然后将其绑定到TextBox的Text属性,就需要使用RelativeSource标记扩展的Self模式:
<TextBox Text=”{Binding Path=CurrentItem}” ToolTipService.ToolTIp=”{Binding Text, RelativeSource={RelativeSource Self}}” />
TemplatedParent 模式
TemplatedParent模式仅应用于控件内包含控件模板或数据模板的控件。这种模式返回模板项目对象并且可以使模板项目的有关属性绑定到目标属性上。当在控件内使用数据模板时,例如在ListBox控件的Item数据模板,TemplatedParent模式就会返回相应模板项的显示内容。注意这一模式并不会返回ListBoxItem控件;数据模板实际上是应用于项目的表现器(Content Presenter),因此返回的是内容表现器;如果控件模板是针对ListBoxITem构建的,则TemplatedParent模式就可以返回LIstBoxItem本身。
例如,如下的数据模板绑定表达式将会获取内容表现器的实际高度:
"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ActualHeight}"
这种绑定的另一个有用场景是在需要获得模板项的数据上下文的情形。控件本身数据上下文当然可以向下覆盖到数据模板层,但是如果在控件中的数据模板分配一个不同的数据上下文,如Grid,你应该使用这种方法再次获取和绑定原始数据上下文到项目中。
注:如果使用控件模板,绑定表达式:“{Binding RelativeSource={RelativeSource TemplatedParnet}}”等价于“{TemplateBinding}”标记扩展。与TemplateBinding标记扩展只支持单向绑定不同,RelativeSource标记扩展可以用于实现双向绑定,这在很多场景下都很有用,特别是在创建定制控件的控件模板时。在12章我们会深入讨论。
FindAncestors 模式
SilverLight 5 引入了一种新的RelativeSource 绑定模式—FindAncestor模式。这种模式允许你在XMAL层级结构中向上查找给定类型的控件。假设有如下的XMAL:
<Grid Background="Red" Margin="20">
<Grid Background="Blue" Margin="20">
<Grid Margin="20" />
Grid>
Grid>
Grid>
正如所见,这个XMAL代码中包含了四个嵌套的Grid控件。如果现在想要将最里面的Grid控件的背景颜色设置为与上面各层级Grid控件之一的相同,就可以使用RelativeSource绑定的FindAncesotr模式来进行。RelativeSource标记扩展有两个属性:AncestorsType和AncestorLevel。前者用于指定供搜索的控件类型,后者用于指定在控件被选定前有多少次指定控件类型应该在控件层级中暴露。如下XMAL展示了BackGround属性的绑定(最内侧的Grid控件与最外侧的Grid控件相应属性进行绑定),一共跳过两个Grid实例,因此共有3次暴露:
< Grid Background ="Red" Margin ="20" >
< Grid Background ="Blue" Margin ="20" >
< Grid Background =" {Binding Background,
RelativeSource={RelativeSource FindAncestor,
AncestorType=Grid,
AncestorLevel=3}} "
Margin ="20" />
Grid >
Grid >
Grid >
如果将AncestorLever属性调整为1或2,会看到最内侧Grid控件的背景会与相应Grid控件的属性进行变化。
注:RelativeSource的FindAncestors模式还有很多潜在用途。比如,如果想要分配一个ViewModel对象到视图的DataContext属性(即在Page或UserControl控件),一个层级低于控件层次结构的控件其DataContext没有设置到ViewModel对象—如果控件包含在ListBox项里,仍需要绑定到ViewModel对象 的一个属性上。RelativeSource绑定的FindAncestor模式就能够很容易获得对Page,UserControl或其他顶级控件的引用,可以直接获得ViewModel对象的DataContext属性的引用。另一个应用是能够使用ListBox内部的控件项目数据模板可以获得ListBoxtItem控件的引用(因为包含在其中)。这就使得控制可以访问ListBoxItem的IsSelected属性,使其可以根据ListBox项目是否被选定而改变其状态。
直接绑定控件属性到其Data Context
你可以直接将控件的属性与分配给其的DataContext属性对象进行绑定,可以直接分配到控件的属性上,也可以分配到向下的对象层级里,只需要简单设置其值到“{Binding}”。这一场景适用于绑定大量控件到同一数据上下文的情况。例如,一个Grid 控件的数据上下文绑定到了一个集合,在该Grid内的多个控件都可以继承这个数据上下文将其作为绑定源。因此,为了将Grid控件内的ListBox控件的ItemsSource属性绑定到这个集合,可以直接设置绑定表达式为“{Binding}”。
作为示例,如下XAML中TextBox控件的Text属性被绑定到TextBox控件的DataContext属性,结果在TextBox中的Text就会显示“Hello”字样:
注:“{Binding}”绑定表达式等价于{Binding Path=.},也等价于{Binding Path=};
检测DataContext的值何时变更
假如有一个视图用于处理由ViewModel对象(分配给视图的DataContext属性)引发的事件,如果一个新的ViewModel对象被分配到View的DataContext属性上,视图需要取消订阅前面的事件并处理新ViewModel对象的事件。在这种场景里,视图需要知道DataContext属性是否发生变化以便作出相应的处理。
在早期版本的SilverLight里,没有方便的方法来确定控件的DataContext值是否发生了变化 ,使得这种场景处理起来相当棘手。而SilverLight5 引入了DataContextChanged事件,只有当DataContext属性变更时才会触发。你可以处理这种事件并作出相应响应。
现在来看一个例子。如下的XMAL包含了一个Button控件和一个TextBox控件 。TextBox控件 的Text属性绑定到其DataContext,而TextBox控件的DataContextChaged事件在后置代码中进行处理。Button控件的Click事件也在后置代码进行处理,因为我们将使用这一事件来改变 TextBox控件的DataContext属性:
< Button Name ="ChangeContextButton" Content ="Change Context"
Height ="33" Width ="143" Click ="ChangeContextButton_Click" />
< TextBox Name ="MyTextBox" Text =" {Binding} "
DataContextChanged ="MyTextBox_DataContextChanged" />
StackPanel >
在后置代码里,我们现在需要处理Button控件的Click事件和TextBox控件的DataContextChanged事件。在Button控件的Click事件处理方法中,将TextBox控件的DataContext属性值进行变更。如下代码为其分配了一个新的GUID:
{
MyTextBox.DataContext = Guid.NewGuid();
}
TextBox控件的DataContextChanged事件处理代码中,我们简单地显示了一个信息框指出事件已经引发:
{
MessageBox.Show( " My data context has changed! ");
}
运行代码,你会发现每次点击按钮都会弹出一个信息框。作为练习,分配GUID值到视图的DataContext属性而不是TextBox控件的:
{
this.DataContext = Guid.NewGuid();
}
TextBox控件会继承这一数据上下文,这样在视图的数据上下文属性发生变化时TextBox的DatContext属性也会发生变化。因此,DataContextChanged事件仍然会引发。
在View的后置代码中绑定到属性
前面介绍了属性可以绑定到对象,资源或视图中的其他控件,但是如果你要绑定的属性源实际上是视图的后置代码类怎么办?见如下的XAML:
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d ="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable ="d"
d:DesignHeight ="300" d:DesignWidth ="400" >
< TextBlock Width ="100" Height ="20" />
UserControl >
视图的后置代码定义了一个属性名为UserName,如下:
namespace Chapter11Workshop
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
public string UserName
{
get { return " Chris Anderson "; }
}
}
}
TextBlock控件可以使用RelativeSource绑定的FindAncestor模式来找到视图的根元素并将其作为绑定源,如下所示:
<TextBlock Width="100" Height="20" Text="{Binding UserName, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" />
另外,也可以使用ElementName绑定获得同样的效果。这必须给XAML文件的根元素用Name属性进行命名。此例中将其命名为Root。这样就可以使用ElementName绑定了:
注:通常,任何视图需要绑定的属性都应在ViewModle类中进行定义。(为MVVM设计模式的一部分)。属性通常在后置代码中定义;模型提供数据;XAML可以直接通过绑定获取这些数据而无需后置代码的交互(除了属性值的get和set访问器的设置以外)。对于MVVM的详细分析见第12章的讨论。
在XAML中实例化类
可以在XAML中实例化类,将其定义为资源或者作为控件属性的值。下面来看看。
创建一个可绑定的类
我们先在XAML中创建类的实例。在你的项目中创建一个ViewModels文件夹。增加一个新的类,命名为ProductViewModel。添加一些属性(Name,ProductNumber等) 以便我们进行绑定。在类的构造器中为这些属性设置一些默认值。
{
public string Name { get; set; }
public string ProductNumber { get; set; }
public ProductViewModel()
{
Name = " Helmet ";
ProductNumber = " H01 ";
}
}
注:为了在XMAL中实例化类,必须有一个默认构造器(无参构造器)。如果尚未定义任何构造器,将会为你自动创建默认构造器,否则必须向类中明确添加。
在XAML中实例化类并作为资源然后进行绑定
在XAM实例化类的第一步是需要 声明 一个名称 空间在XAML文件中:
Xmlns:vm=”clr-namespace:AdventureWorks.ViewMOdels”
下一步是定义 类的实例作为资源,记住要给出一个key:
< vm:ProductViewModel x:Key ="productResource" />
UserControl.Resources >
甚至可以分配一个值给类中的属性(这将会覆盖类构造器的默认值):
<vm:ProductViewModel x:Key="productResource" Name="Bike" ProductNumber="B001" />
注:在XAML中只能给少数几种数据类型赋值,如string,Boolean,Int和Double。其他复杂类型如Decimal,DateTime等,需要类型转换以便能够适应待处理的语句。创建和使用类型转换器见第12章。
类被实例化并定义为资源后,就可以作为对象资源绑定到控件上了上了。方法是使用StaticResource标记扩展:
注:任何在类中硬编码的数据或作为资源的属性都会在设计时显示在被绑定的控件上。
实例化类并作为控件属性的值
可以直接在XAML中实例化类并将对象分配到控件的属性上去,无需定义对象为资源。例如,可能想要分配一个ProductViewModel类的实例到视图的DataContext属性上。就可以采用如下代码实现:
在XAML中,采用如下元素语法操作:
< vm:ProductViewModel />
UserControl.DataContext >
这是声明view到ViewModel的连接方式比较好的选择,在使用MVVM设计模式时更是这样。
在后置代码中定义资源
现在你已经看到可以在XMAL中定义资源,但有时使用代码定义资源也是有用的。例如,可以有一个factory类需要进行实例化,但是该类没有无参构造函数(这是XAML实例化类所必须的)。在这种情况下就需要在代码中定义资源。
在本书贯穿始终的SilverLight业务应用程序项目:AdventureWoks就有这样的例子。如果打开AdventureWorks项目的App.xaml.cs文件就会发现Application_Startup事件处理函数,并可以找到如下代码:
这行代码添加了一个WebContext对象作为应用程序范围内的资源。如你所见,其简单地获取了一个对资源字典的引用(在本例中是Application对象资源字典)并调用Add方法指定了资源的key(WebContext)和一个作为资源的对象(WebContext.Current)。可以在XAML中像其他资源一样进行绑定和使用。
IsEnabled =”{Binding Path =User.IsAuthenticated,
Source ={StaticResource WebContext} }” />
绑定到嵌套属性
通常在创建绑定时,需要绑定到源对象的单个属性。例如TextBox控件的DataContext属性可能被分配了一个Personal对象,而你可能想要绑定该属性的FirstName属性,因为FirstName属性返回了一个字符串:
但是,如果Persona对象并没有FirstName属性而只有Name属性,该属性返回PersonName对象,而该对象有FirstName属性,如何处理这种嵌套属性呢?这可以使用与C#代码相同风格的点式语法访问下级对象:
可以根据需要遍历任何层级的对象。
声明:本文系本人原创,版权归属作者和博客园共同所有,任何组织或个人不得随意转载,修改。需要转载请与本人联系:[email protected]。