我正在使用绑定到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。
答案 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会受此影响 错误。其他风险和副作用可能适用。