【问题标题】:CollectionViewSource sorting only the first time it is bound to a sourceCollectionViewSource 仅在第一次绑定到源时排序
【发布时间】:2010-08-31 15:16:38
【问题描述】:

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

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

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

(我使用过滤器进行了测试,但该过滤器一直按预期工作)

框架是 .NET 4。

【问题讨论】:

  • 我以前也经历过同样的事情——不是每次都创建一个新对象,你能删除并重新插入它的内容吗?
  • 除了额外的工作,这会通过创建/释放对象不必要地分割托管堆,如果可以的话,我宁愿避免它。

标签: wpf binding collectionviewsource


【解决方案1】:

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

【讨论】:

  • ++,非常有见地!我添加了一个单独的 ListBox,因为我不信任 DataGrid,但我没有尝试在没有 DataGrid 的情况下单独测试 ListBox。我使用这个控件的次数越多,它就越古怪(过去 2 天已经报告了 3 个错误)。我将从建议开始,让你知道。谢谢!
  • 改编了一些位(没有按原样工作,但足够接近),更新了问题的解决方法。再次感谢!
  • 很高兴为您提供帮助。是的,我发现 DataGrid 在幕后清除 SortDescriptions 非常令人惊讶。我不太确定为什么,但它这样做可能有充分的理由......或者是的,也许这是一个错误。 =)
  • 小更新,解决方法似乎破坏(或无法修复)列表中的键盘导航,只能通过鼠标选择某些行来访问它们...跨度>
  • 参见 EDIT2。我认为您向 CurrentChanged 事件添加处理程序的解决方案效果不佳,因为每次更改子 DataGrid(即 lstPlayers)和 .您真的只想在父 DataGrid 中的选定项目更改 (lstLevel) 时更新 SortDescriptions。
【解决方案2】:

更好的解决方法: 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 是否受此影响 漏洞。可能存在其他风险和副作用。

【讨论】:

  • 尽管有报道称此解决方案不起作用,但它似乎在我的代码中发挥了作用。
  • 与 Microsoft 一样,该链接不再有效。 ://
【解决方案3】:

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

【讨论】:

  • +1。这似乎也对我有用。最简单的解决方案,没有任何代码隐藏的麻烦。谢谢!
猜你喜欢
  • 1970-01-01
  • 2018-06-06
  • 1970-01-01
  • 2013-10-24
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多