Binding to List Data 绑定到列表数据
得到当前项
1 void birthdayButton_Click(object sender, RoutedEventArgs e) { 2 // Get the current person out of the collection view 3 People people = (People)this.FindResource("Family"); 4 ICollectionView view = 5 CollectionViewSource.GetDefaultView(people); 6 Person person = (Person)view.CurrentItem; 7 ++person.Age; 8 MessageBox.Show(...); 9 }
Navigating between items 改变当前项
//得到数据的视图 ICollectionView GetFamilyView( ) { People people = (People)this.FindResource("Family"); return CollectionViewSource.GetDefaultView(people); }
1 //上一条记录 2 void backButton_Click(object sender, RoutedEventArgs e) { 3 ICollectionView view = GetFamilyView(); 4 view.MoveCurrentToPrevious(); 5 if( view.IsCurrentBeforeFirst ) { 6 view.MoveCurrentToFirst(); 7 } 8 }
//下一条记录 void forwardButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView(); view.MoveCurrentToNext(); if( view.IsCurrentAfterLast ) { view.MoveCurrentToLast(); } }
List Data Targets
<ListBox x:Name="lb" ItemsSource="{Binding}" DisplayMemberPath="Name" SelectedValuePath="Age" IsSynchronizedWithCurrentItem="True"/>
ListBox的ItemsSource属性是一个没有路径的Binding ,相当于“绑定到整个当前对象”,即从父控件继承上下文数据 DataContent
将ListBox类的IsSynchronizedWithCurrentItem属性设置为True ,这样当列表框的选定项发生变化时,它会更新视图中的当前项(反之亦然)
<ComboBox ItemsSource="{StaticResource NamedAgeLookup}" DisplayMemberPath="NameForAge" SelectedValuePath="AgeId" SelectedValue="{Binding Path=Age}" />
注意:绑定的“Age"属性在当前数据源中是没有的,他会找上级数据源,当上级数据源的当前项变更时,ConboBox会显示 上级的当前项Age值=AgeId值对应的项。
Data Templates 数据模板
<ListBox x:Name="lb" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock> <TextBlock Text="{Binding Path=Name}"/> (age:<TextBlock Text="{Binding Path=Age}" Foreground="{Binding Path=Age, Converter={StaticResource ageConverter}}"/>) </TextBlock> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
Typed data templates 类型化数据模板
你希望Person对象有一个特定的模板,无论它出现在哪里,你可以使用类型化数据模板来实现。即:为特定类设置一个固定模板,然后在到处使用
<Window.Resources> <DataTemplate DataType="{x:Type local:Person}"> <TextBlock> <TextBlock Text="{Binding Path=Name}" /> (age: <TextBlock Text="{Binding Path=Age}" />) </TextBlock> </DataTemplate> </Window.Resources>
<ListBox x:Name="lb" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"/>
注意:当在资源中定义了 Person 类型的模板,那么 ListBox 控件中就不需要再定义 ItemTemplate 模板了。
我们将数据模板定义提升到资源块中,并使用DataType属性为其标记类型。*现在,除非另有说明,否则每当使用 WPF 内容模型†的元素看到Person对象在数据模板范围内,就会应
用相应的数据模板。这是一种确保数据在整个应用程序中以一致的方式显示而无需担心数据显示位置的简便方法。
DataTemplates and the DataContext
引用命名空间
声明排序资源
控件展示
View Code
数据资源
XAML文件
<Window.Resources> <DataTemplate DataType="{x:Type local:Person}"> <TextBlock> <TextBlock Text="{Binding Path=Name}" /> (age: <TextBlock Text="{Binding Path=Age}" />) <Button Click="Button_Click">Show</Button> </TextBlock> </DataTemplate> </Window.Resources>
private void Button_Click(object sender, RoutedEventArgs e) {//得到 DataTemplate 中对应的数据 DataContext Button showButton=(Button)sender; Person person= (Person)showButton.DataContext; MessageBox.Show(string.Format("{0} is {1} years old", person.Name, person.Age)); }
ListBox 当数据源新添或删除项时,列表能自动刷新
public class People:ObservableCollection<Person> { }
Sorting 排序
1.简单排序
private void sortButton_Click(object sender, RoutedEventArgs e) {//简单排序 ICollectionView view=GetFamilyView(); if(view.SortDescriptions.Count ==0) { view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending)); view.SortDescriptions.Add(new SortDescription("Age", ListSortDirection.Descending)); } else { view.SortDescriptions.Clear(); } }
2. 自定义排序
public class PersonSorter : IComparer {//自定义排序 public int Compare(object x, object y) {//按Name升序,再按Age降序 Person lhs = (Person)x; Person rhs = (Person)y; // Sort Name ascending and Age descending int nameCompare = lhs.Name.CompareTo(rhs.Name); if (nameCompare != 0) return nameCompare; return rhs.Age - lhs.Age; } }
private void sortButton_Click(object sender, RoutedEventArgs e) { var view=(ListCollectionView)GetFamilyView(); if(view.CustomSort==null) { view.CustomSort =new PersonSorter(); } else { view.CustomSort = null; } }
Default Collection Views 默认集合视图
每个集合数据类型的默认视图
Filtering 过滤
private void filterButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView(); if (view.Filter == null) { view.Filter = (item) => { return ((Person)item).Age >= 25; }; } else { view.Filter = null; } }
Grouping 分组
1. 使用默认组样式进行分组
<ListBox x:Name="lb" ItemsSource="{StaticResource Family}" IsSynchronizedWithCurrentItem="True"> <ListBox.GroupStyle> <x:Static Member="GroupStyle.Default"/> </ListBox.GroupStyle> </ListBox>
private void groupButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView(); if (view.GroupDescriptions.Count == 0) { //Group by age view.GroupDescriptions.Add(new PropertyGroupDescription("Age")); } else { view.GroupDescriptions.Clear(); } }
2. 自定义组样式
<ListBox x:Name="lb" ItemsSource="{StaticResource Family}" IsSynchronizedWithCurrentItem="True"> <ListBox.GroupStyle> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> <TextBlock Background="Black" Foreground="White" FontWeight="Bold"> <TextBlock Text="{Binding Name}"/> (<TextBlock Text="{Binding ItemCount}"/>) </TextBlock> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </ListBox.GroupStyle> </ListBox>
3. 使用自定义值转换器进行分组
private void groupButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView(); if (view.GroupDescriptions.Count == 0) { //Group by age view.GroupDescriptions.Add(new PropertyGroupDescription("Age",new AgeToRangeConverter())); } else { view.GroupDescriptions.Clear(); } }
public class AgeToRangeConverter : IValueConverter {// 用于分组的自定义值转换器 public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (int)value < 25 ? "Under the Hill" : "Over the Hill"; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
注意:如果你想获取对象的所有数据而不只是单个属性,你可以构造一个以 null 作为属性名称的PropertyGroupDescriptor ,它将整个对象作为值参数传递给Convert ,而不是仅传递单个属性的数据。
多级分组
private void groupButton_Click(object sender, RoutedEventArgs e) { ICollectionView view = GetFamilyView(); if (view.GroupDescriptions.Count == 0) { //Group by age view.GroupDescriptions.Add(new PropertyGroupDescription("Age",new AgeToRangeConverter())); view.GroupDescriptions.Add(new PropertyGroupDescription("Age")); } else { view.GroupDescriptions.Clear(); } }
Declarative Sorting and Grouping 声明式排序和分组
xmlns:compModel="clr-namespace:System.ComponentModel;assembly=WindowsBase" xmlns:data="clr-namespace:System.Windows.Data;assembly=PresentationFramework">
<Window.Resources> <CollectionViewSource x:Key="SortedGroupedFamily" Source="{StaticResource Family}"> <CollectionViewSource.SortDescriptions> <compModel:SortDescription PropertyName="Name" Direction="Ascending" /> <compModel:SortDescription PropertyName="Age" Direction="Descending" /> </CollectionViewSource.SortDescriptions> <CollectionViewSource.GroupDescriptions> <data:PropertyGroupDescription PropertyName="Age" Converter="{StaticResource ageConverter}" /> <data:PropertyGroupDescription PropertyName="Age" /> </CollectionViewSource.GroupDescriptions> </CollectionViewSource> </Window.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource SortedGroupedFamily}}" DisplayMemberPath="Name"> <ListBox.GroupStyle> <x:Static Member="GroupStyle.Default" /> </ListBox.GroupStyle> </ListBox>
Data Source Providers 数据源提供者
两个数据源提供程序,它们都派生自DataSourceProvider基类: ObjectDataProvider 和 XmlDataProvider。数据源提供程序为任何类型的操作创建一个间接层,这些操作会生成数据绑定对象。
Object Data Provider
public class RemotePeopleLoader { public People LoadPeople() {//加载远程数据 People people = new People(); people.Add(new Person("张三", 20)); people.Add(new Person("李四", 15)); return people; } }
<ObjectDataProvider x:Key="Family" ObjectType="{x:Type local:RemotePeopleLoader}" MethodName="LoadPeople" IsAsynchronous="True"/>
<Grid DataContext="{StaticResource Family}" > <StackPanel> <ListBox x:Name="lb" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"> <ListBox.GroupStyle> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> <TextBlock Background="Black" Foreground="White" FontWeight="Bold"> <TextBlock Text="{Binding Name}"/> (<TextBlock Text="{Binding ItemCount}"/>) </TextBlock> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </ListBox.GroupStyle> </ListBox>
private ICollectionView GetFamilyView() {//得到数据绑定所使用的视图 //1.查询资源 //People people = (People)this.FindResource("Family"); //2. 查找 DataSourceProvider 数据源提供者 DataSourceProvider provider = (DataSourceProvider)this.FindResource("Family"); People people = (People)provider.Data; return CollectionViewSource.GetDefaultView(people); }
XML Data Source Provider XML 数据源提供者
Master-Detail Binding 主从绑定
家庭有成员,成员由有名字和年龄的人组成
Families、 Family、 People和Person的实例如图 7‑21 所示
在图 7‑21 中, Families集合形成主数据,包含Family类的实例,每个实例包含一个People类型的Members属性,该属性包含详细的Person数据。
public class Person : INotifyPropertyChanged {//单个人 public Person() { } public Person(string name, int age) { this.name = name; this.age = age; } string name; public string Name { get { return this.name; } set { this.name = value; Notify(nameof(Name)); } } int age; public event PropertyChangedEventHandler PropertyChanged; protected void Notify(string propName) { if (this.PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propName)); ; } } public int Age { get { return this.age; } set { this.age = value; Notify(nameof(Age)); } } Traits traits; public Traits Traits { get { return this.traits; } set { traits = value; Notify(nameof(Traits)); } } } public class People : ObservableCollection<Person> {//人的集合 } public class Family {//家庭 string familyName; public string FamilyName { get { return this.familyName; } set { this.familyName = value; } } People members; public People Members { get { return this.members; } set { members = value; } } } public class Families:ObservableCollection<Family> {//家庭集合 } public class Trait {//描述 string description; public string Description { get { return this.description; } set { description = value; } } } public class Traits:ObservableCollection<Trait> { }
<Window.Resources> <local:Families x:Key="Families"> <local:Family FamilyName="Stooge"> <local:Family.Members> <local:People> <local:Person Name="Larry" Age="21" > <local:Person.Traits> <local:Traits> <local:Trait Description="In Charge"/> <local:Trait Description="Mean"/> <local:Trait Description="Ugly"/> </local:Traits> </local:Person.Traits> </local:Person> <local:Person Name="Curly" Age="22" /> <local:Person Name="Moe" Age="23" /> </local:People> </local:Family.Members> </local:Family> <local:Family FamilyName="Addams"> <local:Family.Members> <local:People> <local:Person Name="Gomez" Age="135" /> <local:Person Name="Morticia" Age="121" /> <local:Person Name="Fester" Age="137" /> </local:People> </local:Family.Members> </local:Family> </local:Families> </Window.Resources>
<Grid DataContext="{StaticResource Families}" > <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <!--Families Column--> <TextBlock >Familise:</TextBlock> <ListBox Grid.Row="1" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Path=FamilyName}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <!--Members Column--> <StackPanel Grid.Column="1" Orientation="Horizontal"> <TextBlock Text="{Binding Path=FamilyName}"/> <TextBlock Text=" Family Members:"/> </StackPanel> <ListBox Grid.Row="1" Grid.Column="1" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=Members}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=Name}"/> <TextBlock Text=" (age: "/> <TextBlock Text="{Binding Path= Age}"/> <TextBlock Text=" )"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <!-- Traits Column --> <StackPanel Grid.Row="0" Grid.Column="2" Orientation="Horizontal"> <TextBlock Text="{Binding Path=Members/Name}" /> <TextBlock Text=" Traits:" /> </StackPanel> <ListBox Grid.Row="1" Grid.Column="2" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=Members/Traits}" > <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Path=Description}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid>
在 traits 列标题的情况下,我们从当前所选Family的Members属性绑定到当前所选
Person的Name属性,绑定如下:
<TextBlock Text="{Binding Path=Members/Name}" />
绑定语句中的/充当对象之间的分隔符,假定每个级别的对象都是“当前选定的”
Hierarchical Binding 树形绑定
<Window.Resources> <HierarchicalDataTemplate DataType="{x:Type local:Family}" ItemsSource="{Binding Path=Members}"> <TextBlock Text="{Binding Path=FamilyName}"/> </HierarchicalDataTemplate> <HierarchicalDataTemplate DataType="{x:Type local:Person}" ItemsSource="{Binding Path=Traits}"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=Name}"/> <TextBlock Text=" (age: "/> <TextBlock Text="{Binding Path=Age}"/> <TextBlock Text=")"/> </StackPanel> </HierarchicalDataTemplate> <DataTemplate DataType="{x:Type local:Trait}"> <TextBlock Text="{Binding Path=Description}"/> </DataTemplate> </Window.Resources>
<TreeView DataContext="{StaticResource Families}" Grid.Row="1" Grid.Column="3"> <TreeViewItem ItemsSource="{Binding}" Header="Families"> </TreeViewItem> </TreeView>