views:

73

answers:

1

I'm using a DataGrid bound to a CollectionViewSource (players), itself bound to the currently selected item of a ListBox (levels), each item containing a collection to be sorted/displayed in the 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>

(whole C# code here, XAML code here, entire test project here - in addition to the DataGrid I've added a simple ListBox for the players, to make sure it wasn't a DataGrid issue)

The problem is that the players are sorted the first time they are shown, but as soon as I select another level from the ListBox, they are not sorted anymore. Also, modifying names the first time players are shown will sort them accordingly to the changes, but not anymore once the level has been changed.

So it looks like changing the source of the CollectionViewSource somehow breaks the sort feature, but I have no idea why, nor how to fix it. Does anyone know what I'm doing wrong?

(I did a test with a filter, but that one kept working as expected)

The framework is .NET 4.

+1  A: 

Great question and an interesting observation. Upon closer inspection, it appears that the DataGrid clears sort descriptions of a previous ItemsSource before a new one is set. Here is its code for OnCoerceItemsSourceProperty:

private static object OnCoerceItemsSourceProperty(DependencyObject d, object baseValue)
{
    DataGrid grid = (DataGrid) d;
    if ((baseValue != grid._cachedItemsSource) && (grid._cachedItemsSource != null))
    {
        grid.ClearSortDescriptionsOnItemsSourceChange();
    }
    return baseValue;
}

This behavior only happens on a DataGrid. If you used a ListBox instead (to display the "Players" collection above), the behavior will be different and the SortDescriptions will still remain after selecting different items from the parent datagrid.

So I guess the solution to this is to somehow re-apply the sort descriptions of the Players collection whenever the selected item in the parent DataGrid (i.e. "lstLevel") changes.

However, I'm not 100% sure about this and probably needs more testing/investigation. I hope I was able to contribute something though. =)

EDIT:

As a suggested solution, you can put a handler for lstLevel.SelectionChanged in your constructor, before setting the lstLevel.ItemsSource property. Something like this:

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

lstLevel.ItemsSource = levels;

EDIT2:

In response to the problems you're encountering with regards to keyboard navigation, I suggest that instead of handling the "CurrentChanged" event, you handle the lstLevel.SelectionChanged event instead. I'm posting the necessary updates you need to make below. Just copy-paste to your code and see if it works fine.

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 (constructor):

lstLevel.SelectionChanged +=
    (sender, e) =>
    {
        levels.ToList().ForEach((p) =>
        {
            CollectionViewSource.GetDefaultView(p.Players)
                .SortDescriptions
                .Add(new SortDescription("Name", ListSortDirection.Ascending));
        });
    };
lstLevel.ItemsSource = levels;
karmicpuppet
++, very insightful! I added a separate ListBox because I didn't trust the DataGrid, but I didn't try to test the ListBox alone without the DataGrid. The more I'm using this control, the more quirky it proves to be (3 bugs already reported the last 2 days). I'll start from the suggestion and let you know. Thanks!
RedGlyph
Adapted a few bits (didn't work as is, but close enough), updated question with workaround. Thanks again!
RedGlyph
Glad to help. Yeah, I found it to be quite surprising that the DataGrid was clearing the SortDescriptions behind the scenes. I'm not really sure why but there's probably a good reason it's doing that... or yeah, maybe it's a bug. =)
karmicpuppet
Little update, the workaround seems to break (or fails to fix) the keyboard navigation in the list, it's only possible to get to some of the rows by selecting them with the mouse...
RedGlyph
See EDIT2. I don't think your solution of adding a handler to the CurrentChanged event works well because that will get fired every time you change the selected item in your child DataGrid (i.e. lstPlayers) and . You really only want to update the SortDescriptions when the selected item in the parent DataGrid changes (lstLevel).
karmicpuppet
Right, it must have been late when I wrote that ;-) You code works great, thanks again! So working on the ListCollectionView instead of the intermediate CollectionViewSource seems to behave must better.
RedGlyph