我们希望能够直接将对象和对象的集合绑定到 Avalon UI 元素。作为一个示例,以下代码显示了我们用于探究绑定在 Avalon 中数据的 Person 类。
class Person : IPropertyChange {
public event PropertyChangedEventHandler PropertyChanged;
void FirePropertyChanged(string propertyName) {
if( this.PropertyChanged != null ) {
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
string name;
public string Name {
get { return this.name; }
set {
this.name = value;
FirePropertyChanged("Name");
}
}
int age;
public int Age {
get { return this.age; }
set {
this.age = value;
FirePropertyChanged("Age");
}
}
...
}
IPropertyChange 接口由 Person 类实现,以通知绑定到实例的任意控件,其中一个属性已经更改。相反,公共属性让绑定控件的数据可以访问每个属性的当前值,并应用 UI 中发起的变化。图 1 中的 Name 和 Age TextBox 控件显示了 Person 对象的一个实例,该对象绑定到每个控件的 TextContent 属性。
当前项目
在 Name 和 Age TextBox 控件绑定到单个对象时,Persons ListBox 控件绑定到 Person 对象的集合中。由于 ListBox 中的选择发生了变化,当前项目 也发生变化,所有绑定控件的数据按照它们认为合适的方式进行处理。例如,如图 1 所示,通过突出显示其列表中的对象,ListBox 反映了当前项目,同时 TextBox 将仅显示当前项目的绑定属性值。当前,跟踪哪个项目是由数据的视图 来管理的。视图是一个位于数据和共享数据视图的控件之间的对象,管理着像当前项目、筛选和排序这样的操作。实际上,在 Avalon 中,完全不需要绑定到数据,而是使用程序员或 Avalon 提供的数据视图。
class Window1 : Window {
ArrayListDataCollection persons =
new ArrayListDataCollection();
void Window1_Loaded(object sender, EventArgs e) {
persons.Add(new Person("John", 10));
persons.Add(new Person("Tom", 8));
this.DataContext = this.persons;
showButton.Click += showButton_Click;
birthdayButton.Click += birthdayButton_Click;
addPersonButton.Click += addPersonButton_Click;
}
void showButton_Click(object sender, ClickEventArgs e) {
ListCollectionView view =
(ListCollectionView)Binding.GetView(persons);
Person person = (Person)view.CurrentItem.Current;
MessageBox.Show(
string.Format("Name is '{0}' and you are {1} years old",
person.Name,
person.Age));
}
...
}
这个 Show 按钮单击处理程序代码调用 Binding 对象上的静态 GetView 方法,该对象会返回与 person 数据相关联的默认视图。回忆 persons 字段是 ArrayListDataCollection 的实例(您将会想到我的上一篇文章),它是 ArrayList 类的子类,该类添加 ICollectionChange 接口的实现,以便绑定到集合的控件(如 Persons ListBox)可以注册集合本身更改时的通知。
如果已经获得要绑定的项目集合,从 GetView 方法返回的视图对象的类型将成为 ListCollectionView 类的派生,它将进一步向下延续基类 CollectionView 的继承链:
namespace System.Windows.Data {
public class ListCollectionView :
ContextAffinityCollectionView, ICurrentItem, IComparer {
public override SortDescription[] Sort { get; set; }
public override bool Contains(object item);
public ListCollectionView(System.Collections.IList list);
public override int Count { get; }
public override void Refresh();
public override bool ContainsItem(object item);
public override IEnumerator GetEnumerator();
public override int IndexOf(object item);
public IContains CustomFilter { get; set; }
public override bool CanSort { get; }
public IComparer CustomSort { get; set; }
}
public abstract class ContextAffinityCollectionView :
CollectionView {
}
}
namespace System.ComponentModel {
public abstract class CollectionView :
IEnumerable, ICollectionChange {
public virtual ICurrentItem CurrentItem { get; }
...
}
}
当用户更改绑定 ListBox 中的选择时,CollectionView 基类中的 CurrentItem 属性发生变化,然后绑定控件的其他数据使用该属性来显示它们的内容。图 2 显示了这种关系。
图 2. 项目、当前项目、视图和绑定控件
该视图还用于比只维护当前项目更不常用的任务,例如排序和筛选。
排序
由于视图始终位于绑定控件的数据和数据本身之间。这意味着可能会贸然出现我们不希望显示的数据(这称为筛选,且它将被直接覆盖),并且可能会更改数据显示的顺序(排序)。最简单的排序方法就是设置视图的 Sort 属性:
void sortButton_Click(object sender, ClickEventArgs e) {
ListCollectionView view =
(ListCollectionView)Binding.GetView(persons);
if( view.Sort.Length == 0 ) {
view.Sort = new SortDescription[] {
new SortDescription("Name", ListSortDirection.Ascending),
new SortDescription("Age", ListSortDirection.Descending),
};
}
else {
view.Sort = new SortDescription[0];
}
view.Refresh();
}
请注意由要进行排序的属性名称和顺序(升序或降序)构建的 SortDescription 对象数组的使用。还要注意对视图对象上的 Refresh 的调用。当前,这要求使用视图的新属性来刷新绑定控件(尽管希望在 Longhorn 的将来的版本中不要求对 Refresh 显式调用)。
SortDescription 对象的数组应该涵盖大多数情况,但是如果想要更多的控件,可以通过实现 IComparer 接口为视图提供自定义排序对象。
void sortButton_Click(object sender, ClickEventArgs e) {
ListCollectionView view =
(ListCollectionView)Binding.GetView(persons);
if( view.CustomSort == null ) {
view.CustomSort = new PersonSorter();
}
else {
view.CustomSort = null;
}
view.Refresh();
}
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;
int ageCompare = 0;
if( lhs.Age < rhs.Age ) ageCompare = -1;
else if( lhs.Age rhs.Age ) ageCompare = 1;
return ageCompare;
}
}
这个自定义排序实现碰巧与以前排序说明的集合具有相同的行为,但您可以完成任何想要进行的操作来确定对象在数据绑定控件中的存储方式。此外,将视图的 Sort 属性设置为 SortDescription 对象的空数组,并且将视图的 CustomSort 属性设置为 null,可以关闭排序。
筛选
仅仅因为所有对象按照令您高兴的某个顺序显示并不意味着您希望显示所有对象。对于出现在数据中但不属于该视图的那些恶意对象,我们需要为视图提供一个 IContains 接口的实现:
void filterButton_Click(object sender, ClickEventArgs e) {
ListCollectionView view =
(ListCollectionView)Binding.GetView(persons);
if( view.CustomFilter == null ) {
view.CustomFilter = new PersonFilter();
}
else {
view.CustomFilter = null;
}
view.Refresh();
}
class PersonFilter : IContains {
public bool Contains(object item) {
Person person = (Person)item;
// Filter adult Persons
return person.Age = 18;
}
}
这种筛选