Подтвердить что ты не робот

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>

(весь код С# здесь, код XAML здесь, весь тестовый проект здесь - в дополнение к DataGrid я добавил простой ListBox для игроков, чтобы убедиться, что это не DataGrid выпуск)

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

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

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

Структура .NET 4.

4b9b3361

Ответ 1

Отличный вопрос и интересное наблюдение. При ближайшем рассмотрении выясняется, что 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. Что-то вроде этого:

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>

Code-behind (конструктор):

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

Ответ 2

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

Ответ 3

Лучшее обходное решение: CollectionViewSource сортирует только в первый раз, когда привязан к источнику

Внедрите свой собственный DataGrid:

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

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