Сортировка CollectionViewSource только при первой привязке к источнику

Я использую DataGrid, привязанный к CollectionViewSource (игроки), обязано текущий выбранный элемент из ListBox (уровень), каждый элемент, содержащий коллекцию для сортировки/отображения в 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 я добавил простой список для игроков, чтобы убедиться, что это не проблема DataGrid)

проблема в том, что игроки сортируются при первом отображении, но как только я выбираю другой уровень из списка, они больше не сортируются. Кроме того, изменение имен при первом отображении игроков сортирует их в соответствии с изменениями, но больше не после изменения уровня.

таким образом, похоже, что изменение источника CollectionViewSource каким-то образом нарушает функция сортировки, но я понятия не имею, почему и как ее исправить. Кто-нибудь знает что я делаю неправильно?

(Я сделал тест с фильтром, но тот продолжал работать как ожидалось)

платформа .NET 4.

3 ответов


Отличный вопрос и интересное наблюдение. При ближайшем рассмотрении оказывается, что DataGrid очищает описания сортировки предыдущего 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 (для отображения коллекции "игроки" выше), поведение будет отличаться, и SortDescriptions все равно останутся после выбора разных элементов из родительский элемент DataGrid.

поэтому я думаю, что решение этого заключается в том, чтобы как-то повторно применить описания сортировки коллекции игроков всякий раз, когда выбранный элемент в Родительском DataGrid (т. е. "lstLevel") изменяется.

однако я не на 100% уверен в этом и, вероятно, нуждается в большем тестировании/исследовании. Надеюсь, я смог внести свой вклад. =)

EDIT:

как предложенное решение, можно поставить обработчик lstLevel.SelectionChanged в конструкторе, перед установкой lstLevel.Что ItemsSource собственности. Что-то вроде этого:--5-->

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;

Я смог исправить это, просто вызвав PropertyChanged на свойстве, которое предоставляет представление, позволяя обновить представление (и очистить сортировку), а затем добавив описания сортировки.


лучшее решение: сортировка CollectionViewSource только при первой привязке к источнику

реализуйте свой собственный DataGrid:

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

единственное, что делает обратный вызов coerce в текущей реализации, это очистить описания сортировки. Вы можете просто "вырезать" этот код переопределение метаданных. Не жизнеспособен в Silverlight: API OverrideMetadata не является публичным. Хотя я не уверен, что Silverlight влияет на это жук. Другой могут применяться риски и побочные эффекты.