参考源码
上一篇关注Silverlight包含的表单控件.这一篇,你会看到两个显示列表数据的控件:ListBox和DataGrid.这些是典型的通过数据绑定技术实现绑定数据的控件.
通过数据绑定,UI元素会从数据源中"绑定"数据,就像下图,当数据源改变,UI元素绑定的这些数据会更新已反应数据源的变化.数据可以来自不同类型的源,同时绑定目标也可以是任何UI元素,包括标准的Silverlight控件.
数据绑定简化了应用程序的开发.因为改变是自动反应的,你不要手动更新UI元素.所以,通过使用数据绑定,你可以把你的应用程序的UI从数据中分离出来,这样就可以得到干净的UI和更简易的可维护性.
在Silverlight中,数据绑定是通过使用绑定类实现的.绑定类有两个组件-绑定源和绑定目标,还有一个属性决定绑定的方向,这个方向叫绑定模型(binding mode).绑定源就是要被绑定的数据,绑定目标就是要被数据去绑定的控件的属性,绑定模型就是定义绑定源和绑定目标之间怎么样传递数据(单向的(one-way),一次的(one-time,或者双向的(two-way))).你会在接下来的练习里看到它们是怎么工作的.
要定义控件属性的绑定,你可以使用XAML的标记扩展,例如{Binding <path>}.例如,要绑定一个TextBox的Text属性到一个数据源的FirstName字段,你可以使用下面的XAML:
<TextBox Text="{Binding FirstName}"/>
为了理解Silverlight的数据绑定,让我们建立一个非常简单的应用程序.这个应用程序会包含一个Book对象,对象包含两个属性:Title和ISBN.这些属性会被绑定到两个TextBox控件.
<Grid x:Name="LayoutRoot" Background="White"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <TextBlock Text="Book Title" VerticalAlignment="Center" Margin="5" /> <TextBlock Text="ISBN-13" VerticalAlignment="Center" Margin="5" Grid.Row="1" /> <TextBox Text="{Binding Title}" Height="24" Margin="5" Grid.Column="1" /> <TextBox Text="{Binding ISBN}" Height="24" Margin="5" Grid.Column="1" Grid.Row="1" /> <TextBlock Text="Book Title" VerticalAlignment="Center" Margin="5" Grid.Row="2" /> <TextBlock Text="ISBN-13" VerticalAlignment="Center" Margin="5" Grid.Row="3" /> <TextBox Text="{Binding Title}" Height="24" Margin="5" Grid.Column="1" Grid.Row="2" /> <TextBox Text="{Binding ISBN}" Height="24" Margin="5" Grid.Column="1" Grid.Row="3" /> </Grid>
public MainPage() { InitializeComponent(); this.Loaded += new RoutedEventHandler(MainPage_Loaded); } void MainPage_Loaded(object sender, RoutedEventArgs e) { var b = new Book { Title = "Beginning Silverlight 4: From Novice to Professional", ISBN = "978-1430229889" }; this.LayoutRoot.DataContext = b; } } public class Book { public string Title { get; set; } public string ISBN { get; set; } }
当你要为不同的控件设置绑定定义时,控件不会知道到哪里取得数据.这个DataContext属性就是为控件设置参与数据绑定的数据上下文.这个DataContext属性可以直接在控件里设置.如果控件没有指定DataContext属性,它会到父控件里查找数据上下文.这个模型的一个好处是如果你查看页面的XANL,你会很容易地发现控件是从哪里获得数据的.这样提供了最大程度的代码分离,允许设计师设置XAML的UI,同时开发者与设计师可以协同工作,以定义控件怎么绑定到数据源.
到这里,你会看到如下所示效果.
在程序运行时,在第一个Textbox中改变book的title为"Beginning Silverlight".
你可能会认为因为第三个Textbox是绑定到相同数据的,所以第三个Textbox会自动更新.但是要实现这种双向绑定要做两件事.
第一个问题是,当前,这个Book类不支持当它的属性改变时通知绑定客户端.换句话说,当Book的一个属性改变时,Book类不会把这个属性的改变通知到Textbox的实例.你可以为每个属性添加一个change事件来实现.这样做很糟糕;好彩,这里有一个接口,当类实现这个接口时可以处理这件事.这个接口被称为INotifyPropertyChanged.让我们使用它.
代码如下:
public class Book : INotifyPropertyChanged { private string _Title; public string Title { get { return _Title; } set { _Title = value; FirePropertyChanged("Title"); } } private string _ISBN; public string ISBN { get { return _ISBN; } set { _ISBN = value; FirePropertyChanged("ISBN"); } } public event PropertyChangedEventHandler PropertyChanged; void FirePropertyChanged(string property) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); } } }
修改Book,继承并实现INotifyPropertyChanged接口.注意需要using System.ComponentModel.接着,创建一个辅助方法类触发事件,在set方法里调用.还有把简单的属性定义改为添加一个私有字段形式的完整属性定义.
这样完成后,你的类已设为可把Title和ISBN属性改变通知到绑定客户端.但是你需要在做多一步.默认,当你绑定数据源到目标时,BindingMode是单向的(OneWay)绑定,意思是数据源会把数据发送到目标,但是目标不会发送数据回数据源,为了获得数据来更新数据源,你需要实现双向绑定(TwoWay).
要改为双向绑定,要在控件定义绑定时添加Mode=TwoWay参数.如下:
<TextBox Text="{Binding Title,Mode=TwoWay}" Height="24" Margin="5" Grid.Column="1" />
运行应用程序,改变第一个Textbox,把焦点离开控件.你会发现触发了双向绑定.第三个Textbox也会更新.
恭喜贺喜!你刚刚创建了一个实现了双向绑定的Silverlight应用程序.现在我们去看看绑定列表数据到Silverlight提供的两个控件:DataGrid和ListBox.
除了绑定数据外,元素可以直接绑定到其它元素,这意味着可以改进你代码的可读性和效率.绑定一个元素的语法与绑定数据非常相似,唯一的不同是绑定的ElementName要明确指定,这与给设置元素设置ItemsSource属性非常相似.例如,如果你想绑定一个控件的IsEnabled属性到一个checkbox的IsChecked属性.绑定语法如下:
<CheckBox Name="HadRead"/> <Button IsEnabled="{Binding IsChecked,Mode=OneWay,ElementName=HadRead}"/>
注意,这个绑定就像是绑定到一个数据源一样,除了我们添加了ElementName=HadRead.让我们在练习中试试.
为了解释Silverlight中元素到元素的绑定,让我们建立一个非常简单的应用程序.这个应用程序只包含一个按钮和checkbox.当checkbox选中时,按钮可用,checkbox没有选中时,按钮不可用.Go.
<Grid x:Name="LayoutRoot" Background="White"> <StackPanel Margin="20"> <ToggleButton Margin="5" Content="Click to Toggle" IsEnabled="{Binding IsChecked, Mode=OneWay, ElementName=EnableButton}" /> <CheckBox x:Name="EnableButton" IsChecked="true" Margin="5" Content="Enable Button" /> </StackPanel> </Grid>
数据表格类型的控件已经有很多年历史同时也是开发者用于显示大量数据的首选.Silverlight提供的DataGrid控件不只是一个标准的数据表格,它还包含了大量在以前只会在第三方表格组件才有的用户功能.例如,Silverlight的DataGrid控件可以调整表格列的大小和排序表格列.
让我们通过一个简单的例子学习DataGrid.
<Grid x:Name="LayoutRoot" Background="White"> <sdk:DataGrid AutoGenerateColumns="False" Name="dataGrid1" /> </Grid>
public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); this.Loaded += new RoutedEventHandler(MainPage_Loaded); } void MainPage_Loaded(object sender, RoutedEventArgs e) { this.grid.ItemsSource = GridData.GetData(); } } public class GridData { public string Name { get; set; } public int Age { get; set; } public bool Male { get; set; } public static ObservableCollection<GridData> GetData() { return new ObservableCollection<GridData> { new GridData{Name = "John Doe", Age = 30, Male = true}, new GridData{Name = "Jane Doe", Age = 32, Male = false}, new GridData{Name = "Jason Smith", Age = 54, Male = true}, new GridData{Name = "Kayli Jayne", Age = 25, Male = false} }; } }
结果如下:
<sdk:DataGrid Margin="10" Name="grid" />
在上一个例子,你允许DataGrid基于绑定的数据自动生成列.这并不是新的概念,ASP.NET的表格数据组件就是这样的.但你想要对创建的列添加一些额外的控制时应该怎么办呢?如果添加的列包含一些复杂的信息怎么办呢?例如显示一张图片?你可以首先设置AutoGenerateColumns属性为false,然后手动地创建列.
在DataGrid里定义列是使用Columns集合.下面是在XAML里设置Columns集合的一个例子.注意,它设置了AutoGenerateColumns属性为false,如果你忽略了这个,你就会在Columns里额外地多了一些自动创建的列.
<Grid x:Name="LayoutRoot" Background="White"> <sdk:DataGrid Margin="10" AutoGenerateColumns="False" Name="grid"> <sdk:DataGrid.Columns> </sdk:DataGrid.Columns> </sdk:DataGrid> </Grid>
你可以放置三列到Columns集合里:一个文本列(DataGridTextColumn),一个checkbox列(DataGridCheckBoxColumn),和一个模版列(DataGridTemplateColumn).所有这些列的类型都是继承自DataGridColumn.下面是一些这三个列类型值得注意的一些属性.
属性 | 描述 |
CanUserReorder | 是否可以让用户拖动列虫排序它们 |
CanUserResize | 是否可以让用户用鼠标调整列宽度 |
DisplayIndex | 决定在DatabaseGrid的列顺序. |
Header | 定义列表头的内容 |
IsReadOnly | 决定列是否可以被用户编辑 |
MaxWidth | 用px设置列的最大宽度 |
MinWidth | 用px设置列的最小宽度 |
Visibility | 决定用户是否能看到列 |
Width | 设置列的宽度,或者可以在automatic sizing模式里被自动设置 |
DataGridTextColumn在你的表格里定义一列来显示简单的文本.这个与ASP.NET的DataGrid的BoundColumn等价.DataGridTextColumn首先要设置的属性是Header,决定列头部要显示的文本,还有DisplayMemberBinding()属性,决定要绑定数据源的那个属性要绑定到列.下面是一个例子:(我没有找到DisplayMemberBinding,只有Binding属性)
<Grid x:Name="LayoutRoot" Background="White"> <sdk:DataGrid Margin="10" AutoGenerateColumns="False" Name="grid"> <sdk:DataGrid.Columns> <sdk:DataGridTextColumn Header="姓名" Binding="{Binding Name}"/> </sdk:DataGrid.Columns> </sdk:DataGrid> </Grid>
没错,就像你想的一样,DataGridCheckBoxColumn会包含一个Checkbox.如果你有想在表格里用Checkbox显示的数据,这就是那个控件.这里就是一个使用DataGridCheckBoxColumn的例子,列标题为Male?,绑定数据源的Male属性:
<sdk:DataGrid Margin="10" AutoGenerateColumns="False" Name="grid"> <sdk:DataGrid.Columns> <sdk:DataGridCheckBoxColumn Header="Male?" Binding="{Binding Male}"/> </sdk:DataGrid.Columns> </sdk:DataGrid>
如果你想你的表格列显示的既不是简单的文本,也不是一个Checkbox,DataGridTemplateColumn提供一个一种方式来让你自己定义列的内容.DataGridTemplateColumn可以包含一个CellTemplate和CellEditingTemplate,这个用来决定什么内容会显示和决定表格是正常模式还是编辑模式.
注意,当你要在其它类型(DataGridTemplateColumn)的DataGrid列得到诸如自动排序的特性时,需要额外添加来允许排序.
让我们来考虑一个例子,有两个字段:FirstName和LastName.假设你现在在正常的视图模式,你想数据在两个TextBlock里并列显示.但是当编辑这个列时,你想显示两个TextBox控件,允许用户分别显示FirstName和LastName.
<sdk:DataGrid Margin="10" AutoGenerateColumns="False" Name="grid"> <sdk:DataGrid.Columns> <sdk:DataGridTemplateColumn Header="姓名"> <sdk:DataGridTemplateColumn.CellTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Padding="5,0,5,0" Text="{Binding FirstName}"/> <TextBlock Text="{Binding LastName}"/> </StackPanel> </DataTemplate> </sdk:DataGridTemplateColumn.CellTemplate> <sdk:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBox Padding="5,0,5,0" Text="{Binding FirstName}"/> <TextBox Text="{Binding LastName}"/> </StackPanel> </DataTemplate> </sdk:DataGridTemplateColumn.CellEditingTemplate> </sdk:DataGridTemplateColumn> </sdk:DataGrid.Columns> </sdk:DataGrid>
我想,把扑克的起手牌绑定到一个DataGrid应该挺好玩的.如果你在电视里看到过别人打扑克,你最有可能听到有关选手的事情是"pocket rockets"和"cowboys".这是它们给起手牌的一个简单的绰号.要做的就是用DataGrid显示这起手牌.
public class StartingHands { public string NickName { get; set; } public string Notes { get; set; } public string Card1 { get; set; } public string Card2 { get; set; } public static ObservableCollection<StartingHands> GetHands() { return new ObservableCollection<StartingHands>(){ new StartingHands{ NickName = "Big Slick", Notes = "Also referred to as Anna Kournikova.", Card1 = "As", Card2 = "Ks" }, new StartingHands{ NickName = "Pocket Rockets",Notes = "Also referred to as Bullets.", Card1 = "As", Card2 = "Ad" }, new StartingHands{ NickName = "Blackjack",Notes = "The casino game blackjack.",Card1 = "As", Card2 = "Js" }, new StartingHands{ NickName = "Cowboys",Notes = "Also referred to as King Kong",Card1 = "Ks",Card2 = "Kd"}, new StartingHands{ NickName = "Doyle Brunson",Notes = "Named after poker great Doyle Brunson",Card1 = "Ts", Card2 = "2s"} }; } }
<sdk:DataGrid AutoGenerateColumns="False" Name="grdGrid"> <sdk:DataGrid.Columns> <sdk:DataGridTemplateColumn Header="Hand"> <sdk:DataGridTemplateColumn.CellTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Border Margin="2" CornerRadius="4" BorderBrush="Black" BorderThickness="1"/> <Rectangle Margin="4" Fill="White" Grid.Column="0"/> <Border Margin="2" CornerRadius="4" BorderBrush="Black" BorderThickness="1" Grid.Column="1" /> <Rectangle Margin="4" Fill="White" Grid.Column="1" /> <TextBlock Text="{Binding Card1}" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="0" /> <TextBlock Text="{Binding Card2}" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1" /> </Grid> </DataTemplate> </sdk:DataGridTemplateColumn.CellTemplate> </sdk:DataGridTemplateColumn> <sdk:DataGridTextColumn Header="NickName" Binding="{Binding NickName}"/> <sdk:DataGridTextColumn Header="Notes" Binding="{Binding Notes}"/> </sdk:DataGrid.Columns> </sdk:DataGrid>
最后,把数据源和控件连接起来.转到MainPage.xaml.cs文件和添加Loaded事件,只需简单地设置DataGrid的ItemsSource属性等于StartingHands.GetHands() 返回的数据.代码如下:
public MainPage() { InitializeComponent(); this.Loaded += new RoutedEventHandler(MainPage_Loaded); } void MainPage_Loaded(object sender, RoutedEventArgs e) { this.grdGrid.ItemsSource = StartingHands.GetHands(); }
编译和运行应用程序.如果正常的话,得到如下效果:
就这完成了DataGrid使用自定义列的例子.当然,在实际的应用程序里,你会用其他地方获取这些手牌的数据,例如一个web服务或者一个xam文件.
以往,一个列表类型控件被认为是编程中最普通的控件之一,没有比下拉框更特别的控件了.但是,在Silverlight里,有些变化.ListBox可能是其中一个用于显示列表数据的最灵活的控件.实际上,参考ASP.NET的控件,Silverlight的ListBox更像ASP.NET的DataList,而不是ASP.NET的ListBox控件.让我们偷偷看看这个强大的控件.
如果我们把前面再前面那个例子的Person数据绑定到ListBox,你会发现.默认,ListBox真的只是一个标准的ListBox.
ListBox Margin="10" x:Name="list" DisplayMemberPath="Name"/>
这个ListBox里,你可能注意到有个额外的属性就是DisplayMemberPath.如果你只是定义一个简单的字符串ListBox,ListBox控件需要知道显示那个数据.因为Person类包含三个属性(Name,Age和Male),我们需要告诉它要显示Name.结果如下图:
然而,ListBox控件比只显示简单的字符串强大得多.实际,如果你为ListBox定义一个自定义模版,你可以用一个更有趣的方式呈现数据.下面是一个例子:
<ListBox Margin="10" x:Name="list"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock FontSize="17" FontWeight="Bold" Text="{Binding Name}"/> <StackPanel Margin="5,0,0,0" Orientation="Horizontal"> <TextBlock Text="Age: "/> <TextBlock Text="{Binding Age}"/> <TextBlock Text=",Male:"/> <TextBlock Text="{Binding Male}"/> </StackPanel> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
结果如下图:
让我们用让一个练习的数据来显示扑克的起手牌,看看用ListBox显示有多Cool.
<Grid x:Name="LayoutRoot" Background="White"> <ListBox Margin="10" x:Name="list"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Margin="5" Orientation="Horizontal"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Border Margin="2" CornerRadius="4" BorderBrush="Black" BorderThickness="1" /> <Rectangle Margin="4" Fill="White" Grid.Column="0" Width="20" /> <Border Margin="2" CornerRadius="4" BorderBrush="Black" BorderThickness="1" Grid.Column="1" /> <Rectangle Margin="4" Fill="White" Grid.Column="1" Width="20" /> <TextBlock Text="{Binding Card1}" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="0" /> <TextBlock Text="{Binding Card2}" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1" /> </Grid> <StackPanel Orientation="Vertical"> <TextBlock Text="{Binding NickName}" FontSize="16" FontWeight="Bold"/> <TextBlock Text="{Binding Notes}"/> </StackPanel> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid>
public MainPage() { InitializeComponent(); this.Loaded += new RoutedEventHandler(MainPage_Loaded); } void MainPage_Loaded(object sender, RoutedEventArgs e) { this.list.ItemsSource = StartingHands.GetHands(); }
在前面你也看到元素绑定需求的基本类型变为DependencyObject类.另外,在Silverlight 4里还有大量的关于数据绑定的新特性,这里其中的三个.三个都是新的绑定扩展.第一个是字符串格式化.然后是TargetNullValue和FallBackValue的扩展.
在前面版本的Silverlight里,为了在数据绑定时格式话字符串,你需要在绑定里写一个转换器.在Silverlight 4,你现在只需在XAML里就可以格式化数据.增加字符串的格式就是简单地在XAML里增加一个StringFormat扩展.这个StringFormat扩展支持String类型的Format方法一样的参数.
考虑下面XAML,这里显示4个文本框,所有4个在后置代码里绑定相同的属性.不同的是,每个都在StringFormat扩展的基础上实现不同的显示.第一个显示原始数据,第二个显示三位小数,第三个用科学记数法显示,用货币格式显示第四个:
<Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBox Margin="5" Grid.Row="0" Text="{Binding someDecimal}"/> <TextBox Margin="5" Grid.Row="1" Text="{Binding someDecimal, StringFormat='##.###'}"/> <TextBox Margin="5" Grid.Row="2" Text="{Binding someDecimal, StringFormat='E'}"/> <TextBox Margin="5" Grid.Row="3" Text="{Binding someDecimal,StringFormat='c'}"/> </Grid>
结果如下:
在Silverlight 4里添加了两个扩展到基础绑定类里:TargetNullValue和FallBackValue.这些扩展允许你当绑定的数据不是期望的数据时指定情况来显示.TargetNullValue提供一个当绑定值为空时使用的值,下面是一个使用例子:
<TextBox Margin="5" Grid.Row="0" Text="{Binding someDecimal, TargetNullValue='空值!'}"/>
FallBackValue是当绑定的数据丢失或者如果出现不匹配时使用.例子如下:
<TextBox Margin="5" Grid.Row="0" Text="{Binding someDecimal, FallbackValue='找不到值!'}"/>
这一章,你看到怎么绑定数据给Silverlight的列表数据控件.然后,你研究了两个典型的控件:DataGrid和ListBox.你看到这些控件的灵活性和可以用很神奇的方式显示数据.但是所有的例子都是静态的数据,在实际的项目里,数据可能来自web服务或者一个xml文件.