我之前写过我非常需要并且非常喜欢 WPF 列表(ListBox、ListView..)。现在我想分享一种方法来在简单地绑定到集合而不触及该集合时操作视图。它可以在 xaml 中完成,如此处所示
,
但我不太喜欢这种方法,因为在我看来,排序、分组等操作属于视图模型,而不是直接视图,如果它们在视图中,则没有可用的测试。
例子
我们有一个 ListBox 视图,其中 ItemsSource 绑定到 ObservableCollection。在我的示例中,我有一个 True Blood 角色列表。每个生物都有名字、姓氏、年龄(组成)和种类(可以是吸血鬼、人类或神秘人物)。一个生物模型是(很简单,只是有一些数据):
public class CreatureModel
{
public CreatureModel( string firstName, string lastName, int age,
CreatureKind kind )
{
this.FirstName = firstName;
this.LastName = lastName;
this.Age = age;
this.Kind = kind;
}
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public CreatureKind Kind { get; set; }
public override string ToString()
{
return string.Format( "{0} {1} {2} {3}", this.FirstName,
this.LastName, this.Age, this.Kind );
}
}
View 包含一个 ListBox 和三个按钮:
<ListBox ItemsSource="{Binding Path=CreaturesCollection}" />
<Button Content="Sort by age"
Command="{Binding Path=SortByAgeCommand}"/>
<Button Content="Filter Vampires"
Command="{Binding Path=FilterVampiresCommand}"/>
<Button Content="Group by kind"
Command="{Binding Path=GroupByKindCommand}"/>
这个问题的视图模型也很简单——我们需要一组生物和命令来进行排序、过滤和分组:
public ObservableCollection<CreatureModel> CreaturesCollection
{ get; private set; }
public ICommand SortByAgeCommand
{ get { return new DelegateCommand(this.SortByAge ); } }
public ICommand FilterVampiresCommand
{ get { return new DelegateCommand( this.FilterVampires ); } }
public ICommand GroupByKindCommand
{ get { return new DelegateCommand(this.GroupByKind ); } }
现在实现三个方法所需的所有魔法——DelegateCommand 构造函数中的参数就是这个方法:
private ListCollectionView GetListCollectionView()
{
return (ListCollectionView) CollectionViewSource
.GetDefaultView( this.CreaturesCollection );
}
查看集合源
CollectionViewSource – 当您将绑定设置为集合时,WPF 会将其绑定到该集合的默认视图。CollectionViewSource 有一个静态方法可以让您获取此默认视图:CollectionViewSource.GetDefaultView 并且可以轻松应用视图过滤、分组、排序 ListCollectionView 是实现 IList 的集合的默认视图。
排序 – ListCollectionView.CustomSort
要应用排序,需要 IComperer 的实现,它可以是此接口的任何实现。我将使用一种简单地按年龄对生物进行分类的
public class SortCreaturesByAge : IComparer
{
public int Compare( object x, object y )
{
if( x as CreatureModel == null && y as CreatureModel == null )
{
throw new ArgumentException( "SortCreatures can
only sort CreatureModel objects." );
}
if( ((CreatureModel) x).Age > ((CreatureModel) y).Age )
{
return 1;
}
return -1;
}
}
现在我可以通过在 ListCollectionView 上设置 CustomSort 来实现用于 SortByAgeCommand 的方法:
public ICommand SortByAgeCommand
{ get { return new DelegateCommand(this.SortByAge ); } }
private void SortByAge()
{
this.GetListCollectionView().CustomSort = new SortCreaturesByAge();
}
未排序和排序的集合:
过滤——ListCollectionView。筛选
我想实现一个过滤器,它只显示生物列表中的吸血鬼。过滤器是一个Predicate<bool>。甚至比排序更简单,我们只需要
一个返回 bool 的方法来判断参数对象是否是我们需要的:
private bool IsAVampire( object obj )
{
if(obj as CreatureModel != null
&& (obj as CreatureModel).Kind == CreatureKind.Vampire)
{
return true;
}
return false;
}
点击“过滤”
分组
我想按种类对列表中的所有生物进行分组。Kind 是一个枚举:
public enum CreatureKind
{
Human,
Vampire,
Mystery
}
ListCollectionView 再次提供了一个解决方案。所需要做的就是添加一个分组所基于的属性名称。我这个例子 – 种类:
public ICommand GroupByKindCommand
{ get { return new DelegateCommand(this.GroupByKind ); } }
private void GroupByKind()
{
this.GetListCollectionView().GroupDescriptions.Add(
new PropertyGroupDescription { PropertyName = "Kind" } );
}
现在可以对列表进行分组:
总结
WPF 很好地支持分组、过滤和排序,并且易于应用。我觉得我的示例非常基础,并且像往常一样在 WPF 中可以在这里完成很多自定义。接下来我计划发现一种巧妙而巧妙的方法来为这类问题设置数据模板和样式。