Сортировка 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 влияет на это жук. Другой могут применяться риски и побочные эффекты.