CollectionViewSource仅在第一次绑定到源时进行排序

时间:2010-08-31 15:16:39

标签: wpf binding collectionviewsource

我正在使用绑定到CollectionViewSource(播放器)的DataGrid,它本身绑定到ListBox的当前所选项目( levels ),每个项目包含一个集合要在DataGrid中进行排序/显示:

<ListBox Name="lstLevel"
         DisplayMemberPath="Name" 
         IsSynchronizedWithCurrentItem="True" />

...

<!-- DataGrid source, as a CollectionViewSource to allow for sorting and/or filtering -->
<CollectionViewSource x:Key="Players" 
                      Source="{Binding ElementName=lstLevel, 
                                       Path=SelectedItem.Players}">
  <CollectionViewSource.SortDescriptions>
    <scm:SortDescription PropertyName="Name" />
  </CollectionViewSource.SortDescriptions>
</CollectionViewSource>

...

  <DataGrid Name="lstPlayers" AutoGenerateColumns="False" 
            CanUserSortColumns="False"
            ItemsSource="{Binding Source={StaticResource Players}}">
    <DataGrid.Columns>
      <DataGridTextColumn Header="Name"
                          Binding="{Binding Path=Name, Mode=TwoWay}"
                          Width="*" />
      <DataGridTextColumn Header="Age"
                          Binding="{Binding Path=Age, Mode=TwoWay}"
                          Width="80">
      </DataGridTextColumn>
    </DataGrid.Columns>
  </DataGrid>

(整个C#代码here,XAML代码here,整个测试项目here - 除了DataGrid我还为玩家添加了一个简单的ListBox,以确保它不是DataGrid问题)

问题是玩家在第一次显示时会被排序,但是一旦我从ListBox中选择另一个级别,它们就不再排序了。此外,在第一次显示玩家时修改名称将根据更改对其进行排序,但一旦更改级别,就不再对其进行排序。

所以看起来改变CollectionViewSource的源代码会以某种方式破坏排序功能,但我不知道为什么,也不知道如何修复它。有谁知道我做错了什么?

(我使用过滤器进行了测试,但是那个按预期工作了)

框架是.NET 4。

3 个答案:

答案 0 :(得分:12)

很棒的问题和有趣的观察。经过仔细检查,似乎DataGrid在设置新的ItemsSource之前清除之前ItemsSource的排序描述。这是OnCoerceItemsSourceProperty的代码:

private static object OnCoerceItemsSourceProperty(DependencyObject d, object baseValue)
{
    DataGrid grid = (DataGrid) d;
    if ((baseValue != grid._cachedItemsSource) && (grid._cachedItemsSource != null))
    {
        grid.ClearSortDescriptionsOnItemsSourceChange();
    }
    return baseValue;
}

此行为仅发生在DataGrid上。如果您使用了ListBox(以显示上面的“Players”集合),行为将会有所不同,并且在从父数据网格中选择不同的项目后,SortDescriptions仍将保留。

所以我猜这个解决方案是,只要父DataGrid中的选定项(即“lstLevel”)发生变化,就会以某种方式重新应用Players集合的排序描述。

但是,我对此并不是100%肯定,可能需要更多测试/调查。我希望我能够贡献一些东西。 =)

修改

作为建议的解决方案,您可以在设置lstLevel.ItemsSource属性之前在构造函数中放置lstLevel.SelectionChanged的处理程序。像这样:

lstLevel.SelectionChanged +=
    (sender, e) =>
    {
        levels.ToList().ForEach((p) =>
        {
            CollectionViewSource.GetDefaultView(p.Players)
                .SortDescriptions
                .Add(new SortDescription("Name", ListSortDirection.Ascending));
        });
    };

lstLevel.ItemsSource = levels;

<强> EDIT2:

为了解决您在键盘导航方面遇到的问题,我建议您不要处理“CurrentChanged”事件,而是处理lstLevel.SelectionChanged事件。我发布了您需要在下面进行的必要更新。只需复制粘贴到您的代码中,看看它是否正常工作。

XAML:

<!-- Players data, with sort on the Name column -->
<StackPanel Grid.Column="1">
    <Label>DataGrid:</Label>
    <DataGrid Name="lstPlayers" AutoGenerateColumns="False"
        CanUserSortColumns="False"
        ItemsSource="{Binding ElementName=lstLevel, Path=SelectedItem.Players}">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Name"
                        Binding="{Binding Path=Name, Mode=TwoWay}"
                        Width="*" />
            <DataGridTextColumn Header="Age"
                        Binding="{Binding Path=Age, Mode=TwoWay}"
                        Width="80">
            </DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>
</StackPanel>

<StackPanel Grid.Column="2">
    <Label>ListBox:</Label>
    <ListBox ItemsSource="{Binding ElementName=lstLevel, Path=SelectedItem.Players}" DisplayMemberPath="Name" />
</StackPanel>

代码隐藏(构造函数):

lstLevel.SelectionChanged +=
    (sender, e) =>
    {
        levels.ToList().ForEach((p) =>
        {
            CollectionViewSource.GetDefaultView(p.Players)
                .SortDescriptions
                .Add(new SortDescription("Name", ListSortDirection.Ascending));
        });
    };
lstLevel.ItemsSource = levels;

答案 1 :(得分:5)

我能够通过在公开视图的属性上调用PropertyChanged,让视图刷新(并清除排序)然后添加排序描述来解决这个问题。

答案 2 :(得分:4)

更好的解决方法: CollectionViewSource sorting only the first time it is bound to a source

  

实现您自己的DataGrid:

public class SDataGrid : DataGrid
{
    static SDataGrid()
    {
        ItemsControl.ItemsSourceProperty.OverrideMetadata(typeof(SDataGrid), new FrameworkPropertyMetadata((PropertyChangedCallback)null, (CoerceValueCallback)null));
    }
}
     

当前实现中唯一强制回调的是   清除排序说明。您可以简单地“剪切”此代码   覆盖元数据。在Silverlight上不可行:OverrideMetadata API   不公开。虽然我不确定Silverlight会受此影响   错误。其他风险和副作用可能适用。