DataGrid grouping and sorting disappears when observablecollection is changed

Dec 2, 2008 at 9:16 PM
I  have recently been working on a wpf flashcard application that is using the new wpf datagrid to display the flashcards that the user has entered. I'm using a CollectionViewSource to do custom sorting and grouping of the rows, which works fine the first time it loads. However, when I add a card to the observablecollection of cards (or change the observablecollection in any other way), the sorting and grouping disappears from the datagrid.

I have created a simplified sample that illustrates this problem here: http://www.ultralingua.com/downloads/tmp/datagridtest.zip

Some snippets of the code are below:
<!-- Data Provider to wrap stored cards -->
            <ObjectDataProvider x:Key="StoredCardsProvider"
                                ObjectType="{x:Type local:FlashcardsData}"
                                MethodName="StoredCards"/>

            <CollectionViewSource x:Key="cvs" Source="{Binding Source={StaticResource StoredCardsProvider}}">
                <CollectionViewSource.GroupDescriptions>
                    <PropertyGroupDescription PropertyName="Group"/>
                </CollectionViewSource.GroupDescriptions>
                <CollectionViewSource.SortDescriptions>
                    <scm:SortDescription PropertyName="DisplayOrder" />
                </CollectionViewSource.SortDescriptions>
            </CollectionViewSource>

<dg:DataGrid Grid.Row="1" Background="White" Name="cardsListView"
                     CanUserSortColumns="False" HorizontalScrollBarVisibility="Auto"
                     AutoGenerateColumns="False" AlternationCount="2"
                     ItemsSource="{Binding Source={StaticResource cvs}}"
                     BorderThickness="0" SelectionMode="Single"
                     SelectionUnit="FullRow" GridLinesVisibility="All"
                     VerticalGridLinesBrush="DarkGray">
            <dg:DataGrid.GroupStyle>
                <GroupStyle>
                    <GroupStyle.Panel>
                        <ItemsPanelTemplate>
                            <dg:DataGridRowsPresenter/>
                        </ItemsPanelTemplate>
                    </GroupStyle.Panel>
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <StackPanel>
                                <TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
                            </StackPanel>
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                    <GroupStyle.ContainerStyle>
                        <Style TargetType="{x:Type GroupItem}">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="{x:Type GroupItem}">
                                        <Expander IsExpanded="True" BorderBrush="#FFA4B97F" BorderThickness="0,0,0,1">
                                            <Expander.Header>
                                                <DockPanel TextBlock.FontWeight="Bold">
                                                    <TextBlock Text="{Binding Path=Name}" Margin="5,0,5,0" Width="100"/>
                                                    <TextBlock Text="{Binding Path=ItemCount}"/>
                                                    <TextBlock FontWeight="Bold" Text=" " />
                                                    <TextBlock FontWeight="Bold" Text=" Cards" />
                                                </DockPanel>
                                            </Expander.Header>
                                            <ItemsPresenter />
                                        </Expander>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </GroupStyle.ContainerStyle>
                </GroupStyle>
            </dg:DataGrid.GroupStyle>
            <dg:DataGrid.Columns>
                <dg:DataGridTextColumn Header="Side 1"
                                       MinWidth="120" Binding="{Binding Path=Side1, Mode=TwoWay}" />
                <dg:DataGridTextColumn Header="Side 2"
                                       Width="auto" Binding="{Binding Path=Side2, Mode=TwoWay}" />
            </dg:DataGrid.Columns>
        </dg:DataGrid>

The code to add a card to the observablecollection is below:
void addCardButton_Click(object sender, RoutedEventArgs e)
{
            ULFlashcard card = new ULFlashcard("Group1", "side1 text", "side2 text", false, count, "English-French");
            FlashcardsData.Add(card);
            (this.FindResource(@"StoredCardsProvider") as ObjectDataProvider).Refresh();
            ICollectionView view = CollectionViewSource.GetDefaultView(cardsListView.ItemsSource);
            view.Refresh();
            count++;
}

Am I doing something wrong? I assumed that refreshing the ODP and CollectionView would refresh the groupings?

Thanks!
-Bret
Coordinator
Dec 2, 2008 at 11:05 PM
Hi Bret,

    DataGrid removes the SortDescriptions and GroupDescriptions whenever its ItemsSource changes. This is necessary because unlike other ItemsControls, DataGrid itself adds SortDescriptions when the user clicks the column header and leaving them as is may crash if they are not compatible with the new ItemsSource.

    I ran your application and the culprit seems to be 
        (this.FindResource(@"StoredCardsProvider") as ObjectDataProvider).Refresh();
 which is based on method FlashCardsData.StoreCards. This method returns a new instance of ObservableCollection every time it gets called and hence resulting in a new ItemsSource whenever you do a Refresh.

The options are...
  1. Re-add the Sort and Group descriptions after changing the ItemsSource.
  2. Or Implement the method FlashCardsData.StoreCards such that it doesnt return a new instance every time it is called.
  3. Or devise a solution such that you dont have to call Refresh every time (adding to the StoredCardsProvider directly or indirectly but without having to re-construct it)

In general replacing the entire ItemsSource of any ItemsControl when not needed is not recommended for various perf reasons.