公众号推荐
微信公众号搜"智元新知" 关注 微信扫一扫可直接关注哦!
SilverLight 5 数据绑定的高级话题1
编者注:本文译自Chirs Anderson的《Pro Busines s Applications With Silverlight 5》第11章,本人有意将全书译出,希望与有识之士合作,力求最终出版此书。平心而论,这本书无论从布局谋篇还是示例都很到位,是一本不可多得的深入学习SIlverlight5知识的教科书。
由于本章较长,计划分3个部分发表。今天发表第一部分。对于文中提到的诸如“第X章”,“如您所知”等字样,是与书中上下文相关的内容 ,在此不作过多解释。
有关字体的说明:
标题 一律采用“标题 4”,正文一律采用“nor mal”,注解一律采用上下水平线分割。
至此,您已经掌握如何将 UI控件与对象进行绑定以使用户 可以查看和编辑这些对象所暴露的数据。数据不是被推进控件的,而是通过 XMAL提供的绑定将数据拉进控件里。换言之,绑定是控件读取数据的过程。在此前几章里已经介绍了如下信息:
l 将数据 对象分配到控件的 DataContext属性 ;
l 有三种不同的绑定模式: OneTime,OneWay和 TowWay(见第二章)。如果需要 更新绑定源对象的属性 要 设置 mode为 TowWay;
l 当绑定属性 值发生变化时,可以通过实现 InotifyPropertyChanged接口作出通知 ;
l 当绑定属性 值(或被绑定对象本身)无效导致异常时也可以通过实现 IDataErrorInfo或 INotifyDataErrorInfo接口作出通知 ;
l ObservableCollection<T>类型可以用于维护集合中的项目,当项目发生增减时可以自动 通知 绑定的 UI控件,从而自动 进行更新。另外,也可以在集合类中实现 INotifyCollectionChanged接口实现同样的行为;
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:
@H_
502 _200@
<
TextBox
DataContext
="
{StaticResource productResource}
"
Text
="
{Binding Name}
"
/>
但是,有时如果想要将控件的属性 绑定到一个 给定的源时,没有给其子控件修改 继承的数据上下文(此处的数据上下文已经在某处设置到更高级的层次结构中)或者没有为其他属性 修改 绑定源。你可以通过使用 binding的 Source属性 将 TextBox 控件直接绑定到资源而无需使用 TextBox 控件的 DataContext属性 绑定到资源的方式。如下的代码 示例展示了此种绑定方法 :
@H_
502 _200@
<
TextBox
Text
="
{Binding Name, Source={StaticResource productResource}}
"
/>
注:另外,如果在代码 中创建数据绑定,可以分配任何对象到此属性 中充当绑定的源。
ElementName 绑定
第 5, 6章,我们看到了如何通过过使用 ElementName 绑定将各种控件的 ItemsSource属性 绑定到 DomainDataSource控件的 Data属性 上,但是尚未对此进行深入探讨。 DomainDataSource控件从服务器获取 数据并通过其 Data属性 公开暴露数据。然后我们就通过将 ListBox , DataGrid或 DataForm控件的 ItemsSource属性 绑定到 DomainDataSource控件的 Data属性 以利用了这些公开暴露的数据,实际上也就是一个 控件的属性 绑定到另一个 控件的属性 上。如果要绑定一个 控件的属性 到另一个 在视图中的控件(已命名)的属性 上,我们需要使用特殊的绑定方法 称之为 ElemnetName绑定。使用 bingding 的 ElementName属性 ,需要提供在同一命名空间里可以充作绑定源的控件的名称 (而不是分配给控件的 DataContext属性 的对象)。下面一些例子展示了这种绑定。
一个 简单的例子就是将两个 TextBox 控件的 Text属性 时行连接。当修改 一个 TextBox 里的文本时,第二个 TextBox 的文本也会自动 进行更新:
@H_
502 _200@
<
StackPanel
>
< TextBox Name ="Firs tTextBox " /> < TextBox Name ="SecondTextBox " Text =" {Binding Text, ElementName=Firs tTextBox } " /> </ StackPanel >
注:如果将第二个 TextBox 的绑定模式设置为 TowWay,修改 第二个 TextBox 也会更新第一个 TextBox 。第一个 TextBox 只有在第二个 TextBox 失去焦点时才会更新。
类似地,可以将 TextBlock控件的 Text属性 绑定到 Slider控件的 Value属性 上,从而可以在 TextBlock上显示 Slider控件的当前值。
< Slider Name ="sourceSlider" />
@H_
502 _200@
<
TextBlock
Text
="
{Binding ElementName=sourceSlider, Path=Value}
"
/>
下面示例中的 XMAL展示了 ListBox 控件的 ItemsSource属性 绑定到 DomainDataSource控件(名为 productSummaryDDS)的 Data属性 :
@H_
502 _200@
<
ListBox
ItemsSource
=”{Binding
ElementName
=productSummaryDDS,Path=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模式就会返回相应模板项的显示 内容 。注意这一模式并不会返回 ListBox Item控件;数据模板实际上是应用于项目的表现器(Content Presenter),因此返回的是内容 表现器;如果控件模板是针对 ListBox ITem构建的,则TemplatedParent模式就可以返回 LIstBox Item本身。
例如,如下的数据模板绑定表达式将会获取 内容 表现器的实际高度:
"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ActualHeight}"
这种绑定的另一个 有用场景是在需要获得模板项的数据上下文的情形。控件本身数据上下文当然可以向下覆盖到数据模板层,但是如果在控件中的数据模板分配一个 不同的数据上下文,如 Grid,你应该使用这种方法 再次获取 和绑定原始数据上下文到项目中。
注:如果使用控件模板,绑定表达式:“ {Binding RelativeSource={RelativeSource TemplatedParnet}}”等价于“ {TemplateBinding}”标记 扩展。与 TemplateBinding标记 扩展只支持 单向绑定不同, RelativeSource标记 扩展可以用于实现双向绑定,这在很多场景下都很有用,特别是在创建定制控件的控件模板时。在 12章我们会深入讨论。
FindAncestors 模式
SilverLight 5 引入了一种新的 RelativeSource 绑定模式 —FindAncestor模式。这种模式允许你在 XMAL层级结构中向上查找给定类型的控件。假设有如下的 XMAL:
@H_
502 _200@
Grid
Background
="Green"
Width
="200"
Height
>
< 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次暴露:
@H_
502 _200@
<
Grid
Background
="Green"
Width
="200"
Height
="200"
>
<
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模式还有很多潜在用途。比如,如果想要分配一个 viewmode l对象到视图的 DataContext属性 (即在 Page或 UserControl控件),一个 层级低于控件层次结构的控件其 DataContext没有设置到 viewmode l对象 —如果控件包含在 ListBox 项里,仍需要绑定到 viewmode l对象 的一个 属性 上。 RelativeSource绑定的 FindAncestor模式就能够很容易获得对 Page, UserControl或其他顶级控件的引用,可以直接获得 viewmode l对象的 DataContext属性 的引用。另一个 应用是能够使用 ListBox 内部的控件项目数据模板可以获得 ListBox tItem控件的引用(因为包含在其中)。这就使得控制可以访问 ListBox Item的 IsSelected属性 ,使其可以根据 ListBox 项目是否被选定而改变其状态。
直接绑定控件属性 到其 Data Context
你可以直接将控件的属性 与分配给其的 DataContext属性 对象进行绑定,可以直接分配到控件的属性 上,也可以分配到向下的对象层级里,只需要简单设置其值到“ {Binding}”。这一场景适用于绑定大量控件到同一数据上下文的情况。例如,一个 Grid 控件的数据上下文绑定到了一个 集合,在该 Grid内的多个控件都可以继承这个数据上下文将其作为绑定源。因此,为了将 Grid控件内的 ListBox 控件的 ItemsSource属性 绑定到这个集合,可以直接设置绑定表达式为“ {Binding}”。
作为示例,如下 XAML中 TextBox 控件的 Text属性 被绑定到 TextBox 控件的 DataContext属性 ,结果在 TextBox 中的 Text就会显示 “ Hello”字样:
@H_
502 _200@
<
TextBox
DataContext
="Hello"
Text
="
{Binding}
"
/>
注:“ {Binding}”绑定表达式等价于 {Binding Path=.},也等价于 {Binding Path=};
检测 DataContext的值何时变更
假如有一个 视图用于处理由viewmode l对象(分配给视图的 DataContext属性 )引发的事件,如果一个 新的viewmode l对象被分配到 View的 DataContext属性 上,视图需要取消订阅 前面的事件并处理新viewmode l对象的事件。在这 种场景里,视图需要知道 DataContext属性 是否发生变化以便作出相应的处理。
在早期版本的 SilverLight里,没有方便的方法 来确定控件 的 DataContext值是否发生了变化 ,使得这种场景处理起来相当棘手。而 SilverLight5 引入了 DataContextChanged事件,只有当 DataContext属性 变更时才会触发。你可以处理这种事件并作出相应响应。
现在来看一个 例子。如下的 XMAL包含了一个 Button控件和一个 TextBox 控件 。 TextBox 控件 的 Text属性 绑定到其 DataContext,而 TextBox 控件的 DataContextChaged事件在后置代码 中进行处理。 Button控件的 Click事件也在后置代码 进行处理,因为我们将使用这一事件来改变 TextBox 控件的 DataContext属性 :
@H_
502 _200@
<
StackPanel
>
<
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:
@H_502 _200@
private
void ChangeContextButton_Click(
object sender, RoutedEventArgs e)
{
MyText
Box .DataContext = Guid.NewGuid();
}
TextBox 控件的 DataContextChanged事件处理代码 中,我们简单地显示 了一个 信息框指出事件已经引发:
@H_502 _200@
private
void MyText
Box _DataContextChanged(
object sender, DependencyPropertyChangedEventArgs e)
{
Message
Box .Show(
"
My data context has changed!
" );
}
运行代码 ,你会发现每次点击按钮都会弹出一个 信息框。作为练习,分配 GUID值到视图的 DataContext属性 而不是 TextBox 控件的:
@H_502 _200@
private
void ChangeContextButton_Click(
object sender, RoutedEventArgs e)
{
this .DataContext = Guid.NewGuid();
}
TextBox 控件会继承这一数据上下文,这样在视图的数据上下文属性 发生变化 时 TextBox 的 DatContext属性 也会发生变化。因此, DataContextChanged事件仍然会引发。
在 View的后置代码 中绑定到属性
前面介绍了属性 可以绑定到对象,资源或视图中的其他控件,但是如果你要绑定的属性 源实际上是视图的后置代码 类怎么办?见如下的 XAML:
@H_502 _200@
<
UserControl
x:Class
="Chapter11Workshop.MainPage"
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:Ignor able
="d"
d:DesignHeight
="300"
d:DesignWidth
="400"
>
<
TextBlock
Width
="100"
Height
="20"
/>
</
UserControl
>
视图的后置代码 定义了一个 属性 名为 UserName,如下:
@H_502 _200@
using Sy
stem .Windows.Controls;
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绑定了:
@H_502 _200@
<
TextBlock
Width
=”100”
Height
=”20”
Text
=”{Binding
UserName , ElementName
=Root}”
/>
注:通常,任何视图需要绑定的属性 都应在 ViewModle类中进行定义。(为 MVVM设计模式的一部分)。属性 通常在后置代码 中定义;模型提供数据; XAML可以直接通过绑定获取 这些数据而无需后置代码 的交互(除了属性 值的 get和 set访问器的设置以外)。对于MVVM的详细分析见第 12章的讨论。
在 XAML中实例化类
可以在 XAML中实例化类,将其定义为资源或者作为控件属性 的值。下面来看看。
创建一个 可绑定的类
我们先在 XAML中创建类的实例。在你的项目中创建一个 viewmode ls文件 夹。增加 一个 新的类,命名为 Productviewmode l。添加 一些属性 (Name,ProductNumber等) 以便我们进行绑定 。在类的构造器中为这些属性 设置一些默 认值。
@H_502 _200@
public
class Product
viewmode l
{
public
string Name {
get ;
set ; }
public
string ProductNumber {
get ;
set ; }
public Product
viewmode l()
{
Name =
"
Helmet
" ;
ProductNumber =
"
H01
" ;
}
}
注:为了在 XMAL中实例化类,必须有一个 默 认构造器(无参构造器)。如果尚未定义任何构造器,将会为你自动 创建默 认构造器,否则必须向类中明确添加 。
在 XAML中实例化类并作为资源然后进行绑定
在XAM实例化类的第一步 是需要 声明 一个 名称 空间在 XAML文件 中:
Xmlns:vm=”clr-namespace:AdventureWorks.viewmode ls”
下一步是定义 类的实例作为资源,记住要给出一个 key:
@H_502 _200@
<
UserControl.Resources
>
<
vm:Productviewmode l
x:Key
="productResource"
/>
</
UserControl.Resources
>
甚至可以分配一个 值给类中的属性 (这将会覆盖类构造器的默 认值):
< vm:Productviewmode l x:Key ="productResource" Name ="Bike" ProductNumber ="B001" />
注:在 XAML中只能给少数几种数据类型赋值,如 string,Boolean,Int和 Double。其他复杂类型如 Decimal, DateTime等,需要类型转换以便能够适应待处理的语句。创建和使用类型转换器见第 12章。
类被实例化并定义为资源后,就可以作为对象资源绑定到控件上了上了。方法 是使用 StaticResource标记 扩展:
@H_502 _200@
<
TextBox
Text
="
{Binding Name, Source={StaticResource productResource}}
"
/>
注:任何在类中硬编码的数据或作为资源的属性 都会在设计时显示 在被绑定的控件上。
实例化类并作为控件属性 的值
可以直接在 XAML中实例化类并将对象分配到控件的属性 上去,无需定义对象为资源。例如,可能想要分配一个 Productviewmode l类的实例到视图的 DataContext属性 上。就可以采用如下代码 实现:
@H_502 _200@
this .DataContext =
new Product
viewmode l();
在 XAML中,采用如下元素语法操作:
@H_502 _200@
<
UserControl.DataContext
>
<
vm:Productviewmode l
/>
</
UserControl.DataContext
>
这是声明 view到 viewmode l的连接方式比较好的选择,在使用 MVVM设计模式时更是这样。
在后置代码 中定义资源
现在你已经看到可以在 XMAL中定义 资源,但有时使用代码 定义资源也是有用的。例如,可以有一个 factory类需要进行实例化,但是该类没有无参构造函数 (这是 XAML实例化类所必须的)。在这 种情况下就需要在代码 中定义资源。
在本书贯穿始终的 SilverLight业务应用程序项目: AdventureWoks就有这样的例子。如果打开 AdventureWorks项目的 App.xaml.cs文件 就会发现 Application_Startup事件处理函数 ,并可以找到如下代码 :
@H_502 _200@
This.Resources.Add(“WebContext”,WebContext.Current);
这行代码 添加 了一个 WebContext对象作为应用程序范围内的资源。如你所见,其简单地获取 了一个 对资源字典的引用(在本例中是 Application对象资源字典)并调用 Add方法 指定了资源的 key( WebContext)和一个 作为资源的对象( WebContext.Current)。可以在 XAML中像其他资源一样进行绑定和使用。
@H_502 _200@
<
Buttton
Content
=”Click
Me!” IsEnabled
=”{Binding
Path
=User.IsAuthenticated,
Source
={StaticResource
WebContext} }”
/>
绑定到嵌套属性
通常在创建绑定时,需要绑定到源对象的单个属性 。例如 TextBox 控件的 DataContext 属性 可能被分配了一个 Personal 对象,而你可能想要绑定该属性 的 Firs tName 属性 ,因为Firs tName 属性 返回了一个 字符串:
@H_502 _200@
<
TextBox
Text
=”{Binding
Firs tName , Source
={StaticResource
personResource}}”
/>
但是,如果Persona 对象并没有 Firs tName 属性 而只有Name 属性 ,该属性 返回PersonName 对象,而该对象有 Firs tName 属性 ,如何处理这种嵌套属性 呢?这可以使用与C# 代码 相同风格的点式语法访问下级对象:
@H_502 _200@
<
TextBox
Text
=”{Binding
Name.Firs tName, Source
={StaticResource
personResource} }”
/>
可以根据需要遍历任何层级的对象。
声明:本文系本人原创,版权归属作者和博客 园共同所有,任何组织或个人不得随意转载,修改 。需要转载请与本人联系:[email protected] 。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。