8. 集合视图
当绑定到一个集合对象时,WPF 总是默认提供一个视图 (CollectionViewSource)。视图会关联到源集合上,并自动将相关的操作在目标对象上显示出来。
(1) 排序
向 CollectionViewSource.SortDescriptions 属性中插入一个或多个排序条件 (SortDescription) 即可实现单个或多个条件排序。
Window1.xaml

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

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>

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

Window1.xaml.cs

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

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 }

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

 

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_xml_05

对 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));
}

 

 

 

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Text_06

当然,我们可以直接在 XAML 中设置,而不是编写程序代码。

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

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>

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

需要注意的地方包括:

  • 引入了 xmlns:model="clr-namespace:System.ComponentModel;assembly=WindowsBase" 命名空间。
  • 使用 CollectionViewSource 在资源中定义视图排序条件,注意使用 Source 属性绑定到 personals 资源。
  • 目标对象数据源(DataContext)绑定到视图(cvs)而不是数据源(personals)。

(2) 分组
CollectionViewSource.GroupDescriptions 属性用来控制对数据源进行分组。
Window1.xaml.cs

 

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

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 }

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

按性别进行分组,只是输出结果没啥直观效果。

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_xml_11


要看到效果,我们还必须为 ListBox 添加一个 GroupStyle,基于一贯偷懒的理由,我们可以直接使用系统内置的 GroupStyle.Default。


Window1.xaml

 

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

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>

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

 

这回看上去好多了。

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_xml_14

当然,GroupDescriptions 同样也可以写在 XAML 里。

Window1.xaml

 

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

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>

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

 

注意引入 xmlns:data="clr-namespace:System.Windows.Data;assembly=PresentationFramework" 命名空间。
(3) 过滤
利用 CollectionViewSource.Filter 委托属性,我们可以对数据源做出过滤处理。比如过滤掉全部女性。

Window1.xaml.cs

 

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

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 }

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

 

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal_19

(MSDN 文档好像对不上)
(4) 导航
这个功能很常用,尤其是在数据库系统开发中。不过需要注意的是我们必须确保 IsSynchronizedWithCurrentItem = true。

Window1.xaml

 

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

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>

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

 

 

Window1.xaml.cs

 

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

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));
  }
}

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal

 

 

wpf itemscontrol 怎么确定每行的控件 wpf binding collection[index]_Personal_24


这很有趣,或许你也注意到了 Label 的 Binding 语法。

<Label Content="{Binding /}" /> 表示绑定到当前选择项。
<Label Content="{Binding /Name}" /> 表示绑定到当前选择项的 Name 属性。