8. 集合视图
当绑定到一个集合对象时,WPF 总是默认提供一个视图 (CollectionViewSource)。视图会关联到源集合上,并自动将相关的操作在目标对象上显示出来。
(1) 排序
向 CollectionViewSource.SortDescriptions 属性中插入一个或多个排序条件 (SortDescription) 即可实现单个或多个条件排序。
Window1.xaml
1 <Window x:Class="Learn.WPF.Window1"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:my="clr-namespace:Learn.WPF"
5 Title="Window1">
6 <Window.Resources>
7 <my:PersonalList x:Key="personals" >
8 <my:Personal Name="Tom" Age="15" Sex="Male" />
9 <my:Personal Name="Mary" Age="11" Sex="Female" />
10 <my:Personal Name="Jack" Age="12" Sex="Male" />
11 </my:PersonalList>
12 </Window.Resources>
13 <Grid>
14 <StackPanel DataContext="{StaticResource personals}">
15 <ListBox x:Name="listbox1" ItemsSource="{Binding}">
16 <ListBox.ItemTemplate>
17 <DataTemplate>
18 <StackPanel Orientation="Horizontal">
19 <TextBlock Text="{Binding Path=Name}" />
20 <TextBlock>,</TextBlock>
21 <TextBlock Text="{Binding Path=Age}" />
22 <TextBlock>,</TextBlock>
23 <TextBlock Text="{Binding Path=Sex}" />
24 </StackPanel>
25 </DataTemplate>
26 </ListBox.ItemTemplate>
27 </ListBox>
28 </StackPanel>
29 </Grid>
30 </Window>
Window1.xaml.cs
1 public partial class Window1 : Window
2 {
3 public Window1()
4 {
5 InitializeComponent();
6
7 var personals = this.FindResource("personals");
8 var view = CollectionViewSource.GetDefaultView(personals);
9 view.SortDescriptions.Add(new SortDescription("Age", ListSortDirection.Ascending));
10 }
11 }
对 CollectionViewSource.SortDescriptions 的修改会直接反应在界面显示上。
protected void ButtonClick(object sender, RoutedEventArgs e)
{
var personals = this.FindResource("personals");
var view = CollectionViewSource.GetDefaultView(personals);
var direction = sender == btnDesc ? ListSortDirection.Descending : ListSortDirection.Ascending;
view.SortDescriptions.Clear();
view.SortDescriptions.Add(new SortDescription("Age", direction));
}
当然,我们可以直接在 XAML 中设置,而不是编写程序代码。
1 <Window x:Class="Learn.WPF.Window1"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:my="clr-namespace:Learn.WPF"
5 xmlns:model="clr-namespace:System.ComponentModel;assembly=WindowsBase"
6 Title="Window1">
7 <Window.Resources>
8 <my:PersonalList x:Key="personals" >
9 <my:Personal Name="Tom" Age="15" Sex="Male" />
10 <my:Personal Name="Mary" Age="11" Sex="Female" />
11 <my:Personal Name="Jack" Age="12" Sex="Male" />
12 </my:PersonalList>
13
14 <CollectionViewSource x:Key="cvs" Source="{StaticResource personals}">
15 <CollectionViewSource.SortDescriptions>
16 <model:SortDescription PropertyName="Age" />
17 <model:SortDescription PropertyName="Sex" Direction="Descending" />
18 </CollectionViewSource.SortDescriptions>
19 </CollectionViewSource>
20
21 </Window.Resources>
22 <Grid>
23 <StackPanel DataContext="{StaticResource cvs}">
24 <ListBox x:Name="listbox1" ItemsSource="{Binding}">
25 <ListBox.ItemTemplate>
26 <DataTemplate>
27 <StackPanel Orientation="Horizontal">
28 <TextBlock Text="{Binding Path=Name}" />
29 <TextBlock>,</TextBlock>
30 <TextBlock Text="{Binding Path=Age}" />
31 <TextBlock>,</TextBlock>
32 <TextBlock Text="{Binding Path=Sex}" />
33 </StackPanel>
34 </DataTemplate>
35 </ListBox.ItemTemplate>
36 </ListBox>
37 </StackPanel>
38 </Grid>
39 </Window>
需要注意的地方包括:
- 引入了 xmlns:model="clr-namespace:System.ComponentModel;assembly=WindowsBase" 命名空间。
- 使用 CollectionViewSource 在资源中定义视图排序条件,注意使用 Source 属性绑定到 personals 资源。
- 目标对象数据源(DataContext)绑定到视图(cvs)而不是数据源(personals)。
(2) 分组
CollectionViewSource.GroupDescriptions 属性用来控制对数据源进行分组。
Window1.xaml.cs
1 public partial class Window1 : Window
2 {
3 public Window1()
4 {
5 InitializeComponent();
6
7 var personals = this.FindResource("personals");
8 var view = CollectionViewSource.GetDefaultView(personals);
9
10 view.GroupDescriptions.Add(new PropertyGroupDescription("Sex"));
11 }
12 }
按性别进行分组,只是输出结果没啥直观效果。
要看到效果,我们还必须为 ListBox 添加一个 GroupStyle,基于一贯偷懒的理由,我们可以直接使用系统内置的 GroupStyle.Default。
Window1.xaml
1 <Window x:Class="Learn.WPF.Window1"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:my="clr-namespace:Learn.WPF"
5 Title="Window1">
6 <Window.Resources>
7 <my:PersonalList x:Key="personals" >
8 <my:Personal Name="Tom" Age="15" Sex="Male" />
9 <my:Personal Name="Mary" Age="11" Sex="Female" />
10 <my:Personal Name="Jack" Age="12" Sex="Male" />
11 </my:PersonalList>
12 </Window.Resources>
13 <Grid>
14 <StackPanel DataContext="{StaticResource personals}">
15 <ListBox x:Name="listbox1" ItemsSource="{Binding}">
16
17 <ListBox.GroupStyle>
18 <x:Static Member="GroupStyle.Default"/>
19 </ListBox.GroupStyle>
20
21 <ListBox.ItemTemplate>
22 <DataTemplate>
23 <StackPanel Orientation="Horizontal">
24 <TextBlock Text="{Binding Path=Name}" />
25 <TextBlock>,</TextBlock>
26 <TextBlock Text="{Binding Path=Age}" />
27 <TextBlock>,</TextBlock>
28 <TextBlock Text="{Binding Path=Sex}" />
29 </StackPanel>
30 </DataTemplate>
31 </ListBox.ItemTemplate>
32 </ListBox>
33 </StackPanel>
34 </Grid>
35 </Window>
这回看上去好多了。
当然,GroupDescriptions 同样也可以写在 XAML 里。
Window1.xaml
1 <Window x:Class="Learn.WPF.Window1"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:my="clr-namespace:Learn.WPF"
5 xmlns:data="clr-namespace:System.Windows.Data;assembly=PresentationFramework"
6 Title="Window1">
7 <Window.Resources>
8 <my:PersonalList x:Key="personals" >
9 <my:Personal Name="Tom" Age="15" Sex="Male" />
10 <my:Personal Name="Mary" Age="11" Sex="Female" />
11 <my:Personal Name="Jack" Age="12" Sex="Male" />
12 </my:PersonalList>
13
14 <CollectionViewSource x:Key="cvs" Source="{StaticResource personals}">
15 <CollectionViewSource.GroupDescriptions>
16 <data:PropertyGroupDescription PropertyName="Sex" />
17 </CollectionViewSource.GroupDescriptions>
18 </CollectionViewSource>
19
20 </Window.Resources>
21 <Grid>
22 <StackPanel DataContext="{StaticResource cvs}">
23 <ListBox x:Name="listbox1" ItemsSource="{Binding}">
24 <ListBox.GroupStyle>
25 <x:Static Member="GroupStyle.Default"/>
26 </ListBox.GroupStyle>
27 <ListBox.ItemTemplate>
28 <DataTemplate>
29 <StackPanel Orientation="Horizontal">
30 <TextBlock Text="{Binding Path=Name}" />
31 <TextBlock>,</TextBlock>
32 <TextBlock Text="{Binding Path=Age}" />
33 <TextBlock>,</TextBlock>
34 <TextBlock Text="{Binding Path=Sex}" />
35 </StackPanel>
36 </DataTemplate>
37 </ListBox.ItemTemplate>
38 </ListBox>
39 </StackPanel>
40 </Grid>
41 </Window>
注意引入 xmlns:data="clr-namespace:System.Windows.Data;assembly=PresentationFramework" 命名空间。
(3) 过滤
利用 CollectionViewSource.Filter 委托属性,我们可以对数据源做出过滤处理。比如过滤掉全部女性。
Window1.xaml.cs
1 public partial class Window1 : Window
2 {
3 public Window1()
4 {
5 InitializeComponent();
6
7 var personals = this.FindResource("personals");
8 var view = CollectionViewSource.GetDefaultView(personals);
9
10 view.Filter = o =>
11 {
12 return (o as Personal).Sex != Sex.Female;
13 };
14 }
15 }
(MSDN 文档好像对不上)
(4) 导航
这个功能很常用,尤其是在数据库系统开发中。不过需要注意的是我们必须确保 IsSynchronizedWithCurrentItem = true。
Window1.xaml
1 <Window x:Class="Learn.WPF.Window1"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:my="clr-namespace:Learn.WPF"
5 Title="Window1">
6 <Window.Resources>
7 <my:PersonalList x:Key="personals" >
8 <my:Personal Name="Tom" Age="15" Sex="Male" />
9 <my:Personal Name="Mary" Age="11" Sex="Female" />
10 <my:Personal Name="Jack" Age="12" Sex="Male" />
11 <my:Personal Name="Smith" Age="10" Sex="Male" />
12 <my:Personal Name="Li." Age="8" Sex="Female" />
13 </my:PersonalList>
14 </Window.Resources>
15 <Grid>
16 <StackPanel DataContext="{StaticResource personals}">
17 <ListBox x:Name="listbox1" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" >
18 <ListBox.ItemTemplate>
19 <DataTemplate>
20 <StackPanel Orientation="Horizontal">
21 <TextBlock Text="{Binding Path=Name}" />
22 <TextBlock>,</TextBlock>
23 <TextBlock Text="{Binding Path=Age}" />
24 <TextBlock>,</TextBlock>
25 <TextBlock Text="{Binding Path=Sex}" />
26 </StackPanel>
27 </DataTemplate>
28 </ListBox.ItemTemplate>
29 </ListBox>
30
31 <Label Content="{Binding /Name}" />
32 <Label Content="{Binding /Age}"/>
33
34 <Button x:Name="btnFirst" Click="ButtonClick" Content="First" />
35 <Button x:Name="btnPrev" Click="ButtonClick" Content="Prev" />
36 <Button x:Name="btnNext" Click="ButtonClick" Content="Next" />
37 <Button x:Name="btnLast" Click="ButtonClick" Content="Last" />
38 <Button x:Name="btnPostion" Click="ButtonClick" Content="Position: 2" Tag="2" />
39 </StackPanel>
40 </Grid>
41 </Window>
Window1.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
protected void ButtonClick(object sender, RoutedEventArgs e)
{
var personals = this.FindResource("personals") as PersonalList;
var view = CollectionViewSource.GetDefaultView(personals);
if (sender == btnFirst)
view.MoveCurrentToFirst();
else if (sender == btnPrev && view.CurrentPosition > 0)
view.MoveCurrentToPrevious();
else if (sender == btnNext && view.CurrentPosition < personals.Count - 1)
view.MoveCurrentToNext();
else if (sender == btnLast)
view.MoveCurrentToLast();
else if (sender == btnPostion)
view.MoveCurrentToPosition(Convert.ToInt32(btnPostion.Tag));
}
}
这很有趣,或许你也注意到了 Label 的 Binding 语法。
<Label Content="{Binding /}" /> 表示绑定到当前选择项。
<Label Content="{Binding /Name}" /> 表示绑定到当前选择项的 Name 属性。