19

我正在使用绑定到 CollectionViewSource ( players ) 的 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# 代码,这里是 XAML 代码,这里是整个测试项目- 除了 DataGrid,我还为玩家添加了一个简单的 ListBox,以确保它不是 DataGrid 问题)

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

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

(我用过滤器做了一个测试,但那个过滤器一直按预期工作)

该框架是.NET 4。

4

3 回答 3

15

很好的问题和有趣的观察。经过仔细检查,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;

编辑2:

针对您在键盘导航方面遇到的问题,我建议您不要处理“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;
于 2010-08-31T16:24:29.937 回答
5

更好的解决方法: CollectionViewSource 仅在第一次绑定到源时排序

实现您自己的 DataGrid:

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

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

于 2012-04-04T20:56:20.450 回答
5

我可以通过简单地在公开视图的属性上调用 PropertyChanged 来解决此问题,让视图刷新(并清除排序),然后添加排序描述。

于 2013-06-12T15:28:44.487 回答