绑定到索引属性
除了绑定到嵌套属性以外,也可以绑定到索引属性,即可以是整数索引也可是字符串索引。索引选择语法与C#类似-简单地在集合属性名后附加一个方括号作为索引号。例如,Person对象有一个Address属性返回了一个Address对象集合,就可以用如下代码绑定到第一个Address对象:
<TextBox DataContext=”{StaticResource personResource}” Text=”{Binding Address[0].AddressLine1}” />
如果集合具有字符串式索引,如Dictionary<string,string>,可以采用字符串式索引引用语法,如下:
<TextBox DataContext=”{StaticResource personResource}” Text=”{Binding Address[HOME].AddressLine1}” />
在上例中,HOME是Address对象返回的一个字符串索引。如果给定的索引未发现,绑定就会失败,在VS的输出窗口就会出现一个错误提示。
绑定到动态属性
有时,需要绑定到设计时并不明确结构的数据上,如XML或JSON数据。这种情况下,应该创建一个运行时对象,然后反序列化数据并进行绑定。SilverLight4添加了对动态类型的支持,解决了这一问题。但是,the downside to creating dynamic objects is that you can’t
actually bind to them,因为SilverLight的绑定引擎并不能识别动态属性为真正的属性。任何直接针对动态属性绑定的尝试都会失败。为了克服这一难题,SIlverLight5引入了ICustomTypeProvider接口,可以在类中实现以告知绑定引擎对象的结构。
ICustomTypeProvider接口位于System.Reflection命名空间,只需要实现一个方法:GetCustomType。但是,这一方法的实现过程非常复杂,需要你从头创建一个类型。Type类在SilverLight5是非封闭类,可以从这个类继承并覆写其属性和方法。
Alexandra Rusina,SilverLight团队的项目经理创建了一个Helper类避免了自已实现ICustomTypeProvider接口的复杂性,你可以从以下网址下载这个类:http://blogs.msdn.com/b/silverlight_sdk/archive/2011/04/26/binding-to-dynamic-properties-with-icustomtypeprovider-silverlight-5-beta.aspx.
使用这个helper类,就可以创建一个即继承于此类又实现ICustomTypeProvider接口的类,或者创建委托GetCustomType方法到helper类中。后一个方法用于目标类已经继承于其他类的情况。
下面举例说明,如何创建一个Products类并在运行时定义其结构:
1、 从创建类开始,并继承自范型CutomTypeHelper类:
2、 通过调用静态的AddProperty方法(继承自CustomTypeHelper<T>类)添加属性到类中,为每个属性提供一个命名和类型:
Product.AddProperty("Category", typeof(string));
Product.AddProperty("QtyAvailable", typeof(int));
3、 在实例化类的时候,很显然不能像以往在设计时定义类的方式一样进行值的设置。而是应该调用SetPropertyValue方法(也是继承自CustomTypeHelper<T>类),传递属性名作为参数并为其赋值:
product.SetPropertyValue("ProductName", "Helmet");
product.SetPropertyValue("Category", "Accessories");
product.SetPropertyValue("QtyAvailable", 10);
4、 现在就可以使用与在标准CLR对象中相同的方法绑定到这一对象及其属性了。幸亏有helper类,属性自动实现 了INotifyPropertyChanged接口,因此不必担心这一问题。必要的话,也可以调用 GetPropertyValue方法获取值:
string category = (string)product.GetPropertyValue("Category");
int qtyAvailable = (int)product.GetPropertyValue("QtyAvailable");
注:Matt Duffield也提供了实现 ICustomTypeProver接口的实现方法,见:http://mattduffield.wordpress.com/2011/09/16/using-icustomtypeprovider-in-
silverlight-5-with-xml.
增强的数据绑定
Binding类有很多属性可用于进一步配置数据绑定,而不仅仅是将目标属性的值与源属性直接相连接。这些属性是使得SilverLight数据绑定功能更加强大和灵活的关键所在,我们来看看:
字符串格式
Bindings有一个StringFormat属性可以用于在数据传递到目标控件之前格式化被绑定属性的值。这是一个很有用的功能,特别是绑定到非字符串属性时,如DateTime或Decimal类型。可以使用StringFormat属性来设置格式 ,使用与在代码中ToString方法相同的格式化字符串值的方法,即可以使用标准格式也可以定制格式。比如,如下绑定使用标准格式显示被绑定属性的值在TextBox中,将文件格式为现金格式:
或者你可以将日期格式化为长日期格式 (如Sunday,May 16,2011):
或者使用定制的格式(如2011-05-16):
也可以格式化decimal,singl,double或float为一种给定小数位数的格式(如3.14):
如果想要在字符串中包含 一些特殊字符,将格式化字符串包含在单引号内(不能用引号,因为引号表示属性值):
另外,也可以在指定字符串前使用反斜线:
Alternatively, you can simply escape them by inserting a backslash (\) prior to the special character:
注:可用的格式化字符串的标准和定制符号的说明详见MSDN:http://msdn.microsoft.com/ en-us/library/26etazsy.aspx.其他资源示例也可以在VS2010 data binding expression builder中找到。
另一个整洁的数据绑定技巧是使用绑定的StringFormat属性连接绑定值与其他文本。比如,你可能想要在一个值前显示一个标签。假定你绑定到了一个属性,返回“Chris Anderson”作为其值,想要在视图中进行如下的显示:
Name: Chris Anderson
你就可以使用StackPanel控件将两个TextBlock控件放在一 起获得此效果:
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" />
</StackPanel>
或者使用TextBlock控件的Inlines属性:
<TextBlock.Inlines>
<Run Text="Name: " />
<Run Text="{Binding Name}" />
</TextBlock.Inlines>
</TextBlock>
甚至可以创建一个Value转换器或定制的标记扩展来实现。但是,最简单和优雅的方法是使用Binding对象的StringFormat属性套用字符串占位符来实现 ,与String类中的StringFormat属性的语法一样:
{0}占位符取代了被绑定的值,从而有效地实现了两个字符串的连接。
下面是另一个例子,在这里被绑定值将会包含在两个括号中间(也就是说TextBlock控件会显示"(Chris Anderson)");
当然,你只有一个可供替换的值,因为绑定只返回一个值,但是如果你愿意,这是一个非常干净的方法将被绑定的值与其他值相连接,而无需要一些复杂的实现过程。
当源属性值为空时的可选值
如果一个属性被绑定到一个空值,可以获得一个可替代的值,方法是通过binding的TargetNullValue属性。这很有用,特别是想要为未提供的数据提供默认值时:
<TextBlock Text=”{Binding TotalAmount ,TargetNullValue=0}” />
回调值
如果绑定的源为空,就意味着控件的DataContex属性和binding的Source属性都为空,或者说被绑定的属性在对象中并不存在,你可以使用FallbackValue属性指定一个替代值。比如,如下的绑定将显示“Oops!”:
<TextBox Text="{Binding Name, Mode=TwoWay, FallbackValue=Oops!}" />
UpdateSourceTrigger
Binding对象的UpdateSourceTrigger属性可以在双向绑定模式下指定源属性何时更新以响应目标属性的变化。通常在对TextBox控件的Text属性进行绑定时,UpdateSourceTrigger属性允许指定是在TextBox控件失去焦点时控件进行更新,还是选择文件一旦发生变化立即更新,或是只有当代码显示指定时更新。现在我们逐个来看看:
默认模式
当UpdateSourceTrigger属性设置为Default,源属性将在TextBox控件失去焦点进行更新,比方说用户使用Tab键切换。由于是默认模式,无需在XAML文件中显示指定。当然为了完整,也可以像如下进行设定:
<TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=Default}" />
PropertyChanged模式
SilverLight5现在支持新的UpdateSourceTrigger模式:PropertyChanged模式。这种模式使得被绑定(源)属性在控件属性变更时立即进行变更;
<TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
正如在第6章已经看到的,这种模式特别适用于实现文本框筛选查询功能,可以根据用户输入的文本内容即时过滤数据,从而创建良好的用户体验。
显示模式
第7章,已经讨论过实现IEditableObject接口以添加transaction行为到类中(begin,accept和cancel changes)。借助IEditableObject接口支持的行为,可以在属性变更的各个阶段设置逻辑,可以选择显示声明后再进行更新操作,比如用户点击OK按钮等。这就需要设置Binding对象 的UpdateSourceTrigger属性为Explicit:
<TextBox Name="NameTextBox" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=Explicit}" />
注意我们设置的是TextBox控件的Name属性。这一属性在后置代码进行调用时将发挥作用。
如果现在就运行程序,你会发现源属性永远不会更新。要让绑定的源属性发生更新,必须在代码中手工进行更新的初始化。假如在视图中有一个OK按钮,当按钮点击后需要将字段的变更更新到被绑定的对象上。为达此目的,需要在后置代码中处理按钮的Click事件,并使用文本框的Text属性(使用GetBindingExpression方法,返回BindingExpression对象)。你可以通过调用BindingExpression对象的UpdateSource方法显示地强制绑定的源属性更新,如下所示:
expr.UpdateSource();
数值转换
你已经看到了如何使用Binding的StringFormat属性格式化被绑定的数值,这种格式化是在传递到目标控件之前进行的,如果想要将数值转化为完全不同的数值该怎么办呢?一个例子是尝试绑定Boolean值到控件的Visibility属性。正常情况下这是不行的,因为Visibility属性只接受Visibility枚举值(Visible或Collapsed)。可以修改需要绑定对象的属性以便返回一个Visibility枚举值,但这并非总是可行的,也并不理想。有一种方法可以在绑定过程中将Boolean值转化为Visibility枚举值,这种方法就是value converter(下称值转换器),值转换器需要实现IValueConverter接口,共有两个方法:
l Convert方法传递绑定源对象的属性值(作为参数),你可以实现必须的逻辑将该值转换为可供分配到目标属性的值(作为方法的返回结果)。
l ConvertibleBack方法用于双向绑定以转换UI上修改的目标值(作为参数),返回一个可以分配给被绑定属性(源属性)的值;
创建一个简单的值转换器
下面实现一个简单的值转换器:从Boolean值转换为Visibility枚举值:
using System.Windows;
using System.Windows.Data;
public class BoolToVisibilityValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
注意我们没有实现 ConvertBack的逻辑,是因为对控件Visibility属性的绑定只有one-time或one-way绑定模式。因此,这一方法不会用到,没有必要实现它。但是,对其他转换,有可能会用到此方法,就需要相应的具体实现了。
注:在使用SilverLight业务应用程序项目模板作为应用程序的基础时,你会发现在项目中已经实现了一些值转换器(在Helpers文件夹中),例如NotOperationValueConverter值转换器将ture值转换为false,而false则转换为true;
使用值转换器
一旦创建了值转换器,就需要在对象的某一层级的资源上实例化它。可以是page,user control的资源,也可以分配到Application对象的资源(在App.xaml文件里)从而可以在整个应用程序中使用。首先,声明一个命名空间指向值转换器所在空间:
然后,实例化值转换器作为某个对象层级的资源:
现在,就可以通过数据绑定的Converter属性使用值转换器了,使用StaticResource标记扩展获得对其的引用。如下示例示意了如果被绑定对象的IsDirty属性在对象处于编辑状态时将Save按钮的Visibility值设置为true(经过值转换器转换为Visibility.Visible)。
<Button Content="Save" Height="23" Width="75" Visibility="{Binding IsDirty, Converter={StaticResource VisibilityConverter}}" />
向值转换器传递参数
有时需要向值转换器传递附加的参数才能使其正常工作。可以在XAML定义中向值转换器传递参数值,使用Binding的ConverterParameter属性;
在SIlverLight的早期版本中,值转换器主要用于格式化被绑定的数值,直到SIlverLight4引入StringForamt属性才有所改观,这样值转换器在字符串转换上的用途就不是很大了,但是在某些场景仍然是很有用的。与前述讨论有所不同的一个场景是在第7章讨论过的对RadioButton控件的绑定,在此如果想要将RadioButton控件绑定到一个枚举类型的被绑定对象上,并且每个RadioButton控件分别显示一个不同的枚举值。正如在第7章讨论的一样,并没有一个可以直接使用的标准数据绑定模型,如果在绑定中使用值转换器并传递一个参数就使这种场景成为可能。如下的值转换器展示了一个范型的枚举到布尔值的转换器,通过比较所传递数值与所传递的参数,如果匹配就返回true,反之返回false.
using System.Windows.Data;
public class EnumEqualityValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return value.ToString() == parameter.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return Enum.Parse(targetType, parameter.ToString(), true);
}
}
注:在上述场景中采用了TwoWay绑定,因此需要能够将所选定的单选按钮返回所代表的枚举值,需要为此转换器实现ConvertBack方法。
这样我们就可以使用该值转换器了。假如已经声明了命名空间并将转换器实例化为资源,那么只需要简单地将值转换器设置到RadioButton控件的Binding对象的Converter属性,并为每个RadioButton控件分配一个枚举值,设置在ConverterParameter属性上,就可以通过对绑定属性值和参数值的比较来实现IsChecked是否返回true的操作。
Converter={StaticResource EnumConverter}, ConverterParameter=BelowTwenty}" />
<RadioButton Content="20 to 29" IsChecked="{Binding AgeBracket, Mode=TwoWay,
Converter={StaticResource EnumConverter}, ConverterParameter=Twenties}" />
<RadioButton Content="30 to 39" IsChecked="{Binding AgeBracket, Mode=TwoWay,
Converter={StaticResource EnumConverter}, ConverterParameter=Thirties}" />
<RadioButton Content="40 Plus" IsChecked="{Binding AgeBracket, Mode=TwoWay,
Converter={StaticResource EnumConverter}, ConverterParameter=FourtyPlus}" />
传递文化属性到值转换器
CultureInfo对象也可作为参数传递给值转换器。可用于根据给定的区域与语言类型显示所需要的数据。
默认情况下,传递到值转换器的区域与语言信息是从控件 的Language属性获取的,使用的是标准的区域与语言字符串,如en-US,表示英语(美国)。该值向下覆盖到全部层级,因此如果在XAML文件中的根结点指定这一属性,XAML文件内的的所有控件就都继承了此值。如果区域与语言未经此属性显示指定,默认为美国英语(en-US)(译者注:默认应为服务器所使用的区域与语言信息)。你可以通过Binding对象的ConverterCulture属性显示地传递区域与语言字符串到值转换器,该字符串必须使用culture参数进行传递。
传递整个对象到值转换器
如果想要使用源对象的多个属性转换为一个单一的输出,例如将first name与surname属性组合成单一字符串,也可以用值转换器实现。如下代码所示:
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{ Person person = value as Person;
return person.FirstName + " " + person.Surname;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
注:这种场景中,最好是添加一个FullName属性在ViewModel中(指MVVM设计模式)并对其进行绑定。但是,如果没有实现MVVM设计模式,值转换器提供了一种可选择的方案以修改绑定的对象使其适用于UI的需要。
使用属性元素语法进行绑定
尽管很少使用,事实上也可以使用属性元素语法进行绑定操作。如下代码所示:
<TextBox.Text>
<Binding Path="Name" />
</TextBox.Text>
</TextBox>
多绑定
有时,想要绑定控件的属性到源对象的多个属性,在WPF中称为多绑定,但这一特性在SilverLight中并不可用。前面已经讨论了使用值转换器将多个属性合并的方法,可以采用这种方法取代多绑定。Colin Eberhardt创建了可以在SilverLight中实现的多绑定方法,可在此获取:http://www.scottlogic.co.uk/blog/colin/2010/08/silverlight-multibinding-updated-adding-support-for-elementname-and-twoway-binding/.
注:如果使用MVVM设计模式,很少会用到多绑定,因为可以在VidwModel中添加属性来暴露组合属性值。
在代码中绑定数据
有时,需要在后置代码中对控件的属性进行绑定。这可以使用控件的GetBindingExpression 和 SetBinding方法实现。
为了创建新的绑定并分配给控件的属性,简单实例化一个Binding对象(位于System.Windows.Data名称空间),分配绑定配置到所需要的属性上,然后使用控件的SetBinding方法应用绑定到目标控件的属性上。SetBinding方法需要一个Binding对象和与目标控件需要绑定的属性相连的依赖属性两个参数,记住你只能给依赖属性分配数据绑定值。
例如,需要绑定Product实体的Name属性到名为NameTextBox的TextBox控件的Text属性:
Binding.Mode=BindingMode.TwoWay;
NameTextBox.SetBinding(TextBox.TextProperty,binding);
在代码中创建绑定很简单,也可以使用binding对象的其他属性,如StringFormat, TargetNullValue, FallbackValue和 Source等。
可以使用控件的GetBindingExpression方法来获取分配给控件的绑定属性:
BindingExpression expr=NameTextBox.GetBindingExpression(TextBox.TextProperty);
这一方法返回一个BindingExpression对象。BindingExpression类有两个属性:ParentBinding和DataItem,以及前面提到的UpdateSource方法。ParentBinding属性提供了分配给控件属性的Binding对象,而DataItem属性提供了为绑定充当源的对象。
在代码中获取和设置附加属性
尽管这是一个与数据绑定不同的主题,但是获取和设置控件的附加属性与获取和设置绑定对象相类似。使用控件的GetValue方法,传递给这个方法与附加属性相关的依赖属性,就能获得指定的附加属性值。比如,使用如下代码获取一个名为NameTextBox的控件在Grid控件内的行号(Grid.Row附加属性值):
Int row=(int)NameTextBox.GetValue(Grid.RowProperty);
可以为附加属性指定新值,使用SetValue方法,该方法需要附加属性以及新的值作为参数:
NameTextBox.SetValue(Grid.RowProperty,row);
上述代码就可以用于对控件在窗体中布局的动态调整。