wpf - CollectionViewSource clasifica solo la primera vez que está enlazado a una fuente
binding (3)
Gran pregunta y una observación interesante. Tras una inspección más detallada, parece que el DataGrid borra las descripciones de ordenación de un ItemsSource anterior antes de que se establezca uno nuevo. Aquí está su código para OnCoerceItemsSourceProperty:
private static object OnCoerceItemsSourceProperty(DependencyObject d, object baseValue)
{
DataGrid grid = (DataGrid) d;
if ((baseValue != grid._cachedItemsSource) && (grid._cachedItemsSource != null))
{
grid.ClearSortDescriptionsOnItemsSourceChange();
}
return baseValue;
}
Este comportamiento solo ocurre en un DataGrid. Si usó un ListBox en su lugar (para mostrar la colección de "Jugadores" más arriba), el comportamiento será diferente y las Descripciones de clasificación permanecerán después de seleccionar diferentes elementos de la base de datos principal.
Así que supongo que la solución a esto es volver a aplicar de alguna manera las descripciones de clasificación de la colección de Jugadores siempre que cambie el elemento seleccionado en el DataGrid principal (es decir, "lstLevel").
Sin embargo, no estoy 100% seguro de esto y probablemente necesite más pruebas / investigación. Espero haber podido aportar algo. =)
EDITAR:
Como solución sugerida, puede colocar un controlador para lstLevel.SelectionChanged en su constructor, antes de establecer la propiedad lstLevel.ItemsSource. Algo como esto:
lstLevel.SelectionChanged +=
(sender, e) =>
{
levels.ToList().ForEach((p) =>
{
CollectionViewSource.GetDefaultView(p.Players)
.SortDescriptions
.Add(new SortDescription("Name", ListSortDirection.Ascending));
});
};
lstLevel.ItemsSource = levels;
EDIT2:
En respuesta a los problemas que tiene con respecto a la navegación con el teclado, sugiero que en lugar de manejar el evento "CurrentChanged", maneje el evento lstLevel.SelectionChanged en su lugar. Estoy publicando las actualizaciones necesarias que necesita hacer a continuación. Simplemente copie y pegue en su código y vea si funciona bien.
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>
Código detrás (constructor):
lstLevel.SelectionChanged +=
(sender, e) =>
{
levels.ToList().ForEach((p) =>
{
CollectionViewSource.GetDefaultView(p.Players)
.SortDescriptions
.Add(new SortDescription("Name", ListSortDirection.Ascending));
});
};
lstLevel.ItemsSource = levels;
Estoy usando un DataGrid enlazado a un CollectionViewSource ( jugadores ), también vinculado al elemento seleccionado actualmente de un ListBox ( niveles ), cada elemento que contiene una colección que se ordenará / mostrará en el 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>
(todo el código de C # here , el código XAML here , el proyecto de prueba completo here , además del DataGrid, he agregado un ListBox simple para los jugadores, para asegurarse de que no fuera un problema del DataGrid)
El problema es que los jugadores se ordenan la primera vez que se muestran, pero tan pronto como selecciono otro nivel del ListBox, ya no están ordenados. Además, al modificar los nombres la primera vez que se muestran los jugadores, se ordenarán de acuerdo con los cambios, pero no una vez que se haya cambiado el nivel.
Así que parece que cambiar la fuente de CollectionViewSource de alguna manera rompe la función de clasificación, pero no tengo ni idea de por qué, ni cómo solucionarlo. ¿Alguien sabe lo que estoy haciendo mal?
(Hice una prueba con un filtro, pero esa siguió funcionando como se esperaba)
El framework es .NET 4.
Pude arreglar esto simplemente llamando a PropertyChanged en la propiedad que expone la vista, permitiendo que la vista se actualice (y borre la clasificación) y luego agregue las descripciones de clasificación.
Una mejor solución: CollectionViewSource clasifica solo la primera vez que está enlazado a una fuente
Implemente su propio DataGrid:
public class SDataGrid : DataGrid { static SDataGrid() { ItemsControl.ItemsSourceProperty.OverrideMetadata(typeof(SDataGrid), new FrameworkPropertyMetadata((PropertyChangedCallback)null, (CoerceValueCallback)null)); } }
Lo único que hace la devolución de llamada forzada en la implementación actual es borrar las descripciones de ordenación. Simplemente puede "cortar" este código anulando los metadatos. No es viable en Silverlight: OverrideMetadata API no es pública. Aunque no estoy seguro de que Silverlight esté afectado por este error. Otros riesgos y efectos secundarios pueden aplicar.