(读完此系列WPF和Silverlight的数据绑定问题你就轻松搞定 ) 1 Binding to List Data 前面都是绑定到一个对象,下面我们学习绑定到对象列表的方法。 我们还是先组织要绑定的数据,对象所对应的类还是Person,但新增了一个新类People,该类用来组织Person的列
(读完此系列WPF和Silverlight的数据绑定问题你就轻松搞定 )
1 Binding to List Data前面都是绑定到一个对象,下面我们学习绑定到对象列表的方法。
我们还是先组织要绑定的数据,对象所对应的类还是Person,但新增了一个新类People,该类用来组织Person的列表.代码如下:
using System; using System.Collections.Generic; using System.ComponentModel; //INotifyPropertyChanged namespace SimpleDataBinding { class Person : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void Notify( string PropName) { if ( this .PropertyChanged != null ) { PropertyChanged( this , new PropertyChangedEventArgs (PropName)); } } public Person() { _Age = 0; _name = "Null" ; this .CurrentDate = DateTime .Now; } private string _name; public string Name { get { return _name; } set { if ( value == _name) { return ; } _name = value ; //注意:不能用this.Name来赋值,如果这样形成循环调用,栈溢出 Notify( "Name" ); } } private int _Age; public int Age { get { return _Age; } set { if ( value == _Age) return ; _Age = value ; Notify( "Age" ); } } public DateTime CurrentDate { get ; set ; } } //People类 class People : List Person > { } }
注意在同一命名空间下的代码最后添加了Perople类。
我们在UI里显示的XAML如下:
Window x : Class ="ListDataBinding.BindListDataTest" xmlns ="http://schemas.microsoft测试数据/winfx/2006/xaml/presentation" xmlns : x ="http://schemas.microsoft测试数据/winfx/2006/xaml" xmlns : src ="clr-namespace:ListDataBinding" Title ="BindListDataTest" Height ="113" Width ="300"> Window.Resources > src : People x : Key ="Family"> src : Person Name ="Jack" Age ="18"/> src : Person Name ="Tom" Age ="30"/> src : Person Name ="Jone" Age ="14"/> src : Person Name ="Rose" Age ="17"/> src : Person Name ="Mike" Age ="13"/> src : People > Window.Resources > Grid DataContext ="{ StaticResource Family }"> Grid.RowDefinitions > RowDefinition /> RowDefinition /> Grid.RowDefinitions > Grid.ColumnDefinitions > ColumnDefinition Width ="80"/> ColumnDefinition Width ="*"/> Grid.ColumnDefinitions > TextBlock Grid.Row ="0" Grid.Column ="0" Text ="Name" TextAlignment ="Center" VerticalAlignment ="Center"/> TextBlock Grid.Row ="1" Grid.Column ="0" Text ="Age" TextAlignment ="Center" VerticalAlignment ="Center"/> TextBox Grid.Row ="0" Grid.Column ="1" Name ="txtName" Text ="{ Binding Path =Name}" /> TextBox Grid.Row ="1" Grid.Column ="1" Name ="txtAge" Text ="{ Binding Path =Age}"/ > Grid > Window >
我们发现这样的UI只能显示第一个数据项目,也就是说列表的当前项为0,至于其他的就无法显示出来了。
如果要显示其他的只有可通过如下代码的方式来取(注意:书中代码似乎有问题):
private void btnNext_Click( object sender, RoutedEventArgs e) { People people = ( People ) this .FindResource( "Family" ); txtName.Text = people[1].Name; txtAge.Text = people[1].Age.ToString(); }
1.1当前项Current Item
取得当前项
可以通过上面的方法取得当前项,当然我们更专业的做法还是使用Collection View
还是代码说明比较简洁:
People people = ( People ) this .FindResource( "Family" ); ICollectionView view = CollectionViewSource .GetDefaultView(people); Person peron = ( Person )view.CurrentItem;
注意:ICollectionView在System.ComponentModel命名空间里。
导航当前项
还是代码来说明更合适点:
private ICollectionView GetView() { People people = ( People ) this .FindResource( "Family" ); ICollectionView view = CollectionViewSource .GetDefaultView(people); return view; } private void btnNext_Click( object sender, RoutedEventArgs e) { ICollectionView view = GetView(); view.MoveCurrentToNext(); if (view.IsCurrentAfterLast) { view.MoveCurrentToLast(); } } private void btnPrior_Click( object sender, RoutedEventArgs e) { ICollectionView view = GetView(); view.MoveCurrentToPrevious(); if (view.IsCurrentBeforeFirst) { view.MoveCurrentToFirst(); } }1.2 List Data Targets
我们将列表数据绑定到类似TextBox这样的控件难以很好地展现列表数据。我们考虑ListBox控件来列举多个数据信息。
这时的效果如下:列表确实显示了所有对象的信息,因为我们没有设置Path属性,所以采用默认的Convertation来处理,显示对象类型。同时一定要注意使用IsSynchronizatizedWithCurrentItem=True,这样才能列表信息与其他信息同步。但究竟如何才能更好地表达我们需要的信息呢,请参看下一节:
1.3 Display Members, Value Members, and Look-Up Bindings
代码示例也许更易理解:
ListBox Grid.Row ="3" Grid.Column ="1" Name ="lstbox" ItemsSource ="{ Binding }" DisplayMemberPath ="Name" SelectedValuePath ="Age" IsSynchronizedWithCurrentItem ="True"/> Button Grid.Row ="4" Grid.Column ="0" Name ="btnShowValue" Content ="ShowValue" Click ="btnShowValue_Click" />
private void btnShowValue_Click( object sender, RoutedEventArgs e) { MessageBox .Show(lstbox.SelectedValue.ToString()); }1.4数据模板Data Templates
这是利用ListBox控件有一个ItemTemplate属性下面,他可以接受一个DataTemplate类实例,
该模板可以重复绑定到ListBox的每一个项目元素,注意DataTemplate只能指定一个孩子节点,所以一般使用容器控件来组织下面的布局。
ListBox Grid.Row ="3" Grid.Column ="1" Name ="lstbox" ItemsSource ="{ Binding }"> ListBox.ItemTemplate > DataTemplate > TextBlock Text ="{ Binding Path =Name}"> 的年龄是 TextBlock Text ="{ Binding Path =Age}"> TextBlock > TextBlock > DataTemplate > ListBox.ItemTemplate > ListBox >
我本人不赞同书中这样的做法,添加一个StackPanel更舒服点。
1.5 列表改变List Changes
当我们改变列表的数据的时候,却出现如下现象:
只是因为我们需要绑定的列表需要实现INotifyCollectionChanged接口:
namespace System.Collections.Specialized { public interface INotifyCollectionChanged { event NotifyCollectionChangedEventHandler CollectionChanged; } }
namespace System.Collections.ObjectModel { public class ObservableCollection : Collection , INotifyCollectionChanged, INotifyPropertyChanged { ... } }
欢呼雀跃吧,我们改变上面例题的代码,一切如我们想象的美好。
所有的一切就如此简单,简单代码改动:
//People类 class People : ObservableCollection Person > { }
1.6 排序Sorting
简单的代码还是足以繁杂的文字,让我们看如下方法:
private void btnSort_Click( object sender, RoutedEventArgs e) { ICollectionView view = GetView(); 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(); } }
当然我们还可以自定义排序方式:
class PersonSorter : IComparer { public int Compare( object x, object y) { 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; } }
注意:WPF不使用System.Collection.Generic命名空间的泛型IComparer接口,而是使用System.Collection的。呵呵。
使用代码如下:
private void btnSort_Click( object sender, RoutedEventArgs e) { ListCollectionView view = ( ListCollectionView )GetView(); if (view.CustomSort == null ) { view.CustomSort = new PersonSorter (); } else { view.CustomSort = null ; } }
注意:ListCollectionView支持自定义和排序,其他的不支持。
1.7 集合缺省视图类型Default Collection View1.8 过滤 Filter
依然是我熟悉的表达方式:代码:
private void btnFilter_Click( object sender, RoutedEventArgs e) { ListCollectionView view = ( ListCollectionView )GetView(); if (view.Filter == null ) { view.Filter = delegate ( object item) { return (( Person )item).Age > 17; }; } else { view.Filter = null ; } }1.9 分组Grouping
分组的意思大家很明白就是按照某一个或几个关键属性进行分类。
进行分组很简单和sort类似,只需要以下几行代码:
ICollectionView view = GetView(); if (view.GroupDescriptions.Count == 0) { view.GroupDescriptions.Add( new PropertyGroupDescription ( "Age" )); } else { view.GroupDescriptions.Clear(); }
但这在UI层面并没有任何影响,这需要我们对ItemsControl类的控件(例如ListBox)设置GroupStyle属性,GroupStyle类缺省地提供了一个静态的属性实现,我们可以如下设置:
ListBox Grid.Row ="3" Grid.Column ="1" Name ="lstbox" ItemsSource ="{ Binding }" IsSynchronizedWithCurrentItem ="True"> ListBox.GroupStyle > x : Static Member ="GroupStyle.Default"/> ListBox.GroupStyle > ListBox.ItemTemplate > DataTemplate > TextBlock Text ="{ Binding Path =Name}"> 的年龄是 TextBlock Text ="{ Binding Path =Age}"> TextBlock > TextBlock > DataTemplate > ListBox.ItemTemplate > ListBox >
但也许这并不是我们所喜欢的界面,简单得让人生厌,还好微软提供了这个对象的一个属性:HeaderTemplate用于定义分组的栏目的外观,微软总是为大家想得那么周到,养活那么多天才是需要钱的,希望大家不要老是讲微软的坏话。
ListBox.GroupStyle > GroupStyle > GroupStyle.HeaderTemplate > DataTemplate > StackPanel Background ="Green" Orientation ="Horizontal"> TextBlock Text ="{ Binding Name }"/> TextBlock Text ="("/> TextBlock Text ="{ Binding ItemCount }"/> TextBlock Text =")"/> StackPanel > DataTemplate > GroupStyle.HeaderTemplate > GroupStyle > ListBox.GroupStyle >
有这模板属性一切由你发挥,真是好也,然而即使这样解决了UI问题,但是如果我们还希望更进一步,能否实现范围内分组呢?呵呵,然也:
这时我们不需要去想着如何继承GroupStyle类,而是采用围魏救赵的方式,定义一个IValueConverter,
public class AgeRangeConvert : IValueConverter { public object Convert( object value, Type targetType, object parameter, CultureInfo culture) { int _value = ( int )value; if (_value return "10岁以下" ; else if (_value return "20岁以下" ; else return "20岁以上" ; } public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException (); } }
简单调整前面分组代码:
ICollectionView view = GetView(); if (view.GroupDescriptions.Count == 0) { view.GroupDescriptions.Add( new PropertyGroupDescription ( "Age" , new AgeRangeConvert ())); } else { view.GroupDescriptions.Clear(); }
一切搞定,享受成果吧:
既然GroupDescripions是个集合类型,我们不妨看下面代码究竟是什么效果:
ICollectionView view = GetView(); if (view.GroupDescriptions.Count == 0) { view.GroupDescriptions.Add( new PropertyGroupDescription ( "Age" , new AgeRangeConvert ())); view.GroupDescriptions.Add( new PropertyGroupDescription ( "Age" )); } else { view.GroupDescriptions.Clear(); }
运行如下:
呵呵,这不正是有时你需要的效果吗?至于界面如何优化,模板如何定义更好看我们以后话题再
查看更多关于WPF数据绑定(2绑定列表数据BindingtoListData)的详细内容...