Search code examples
c#wpfobservablecollectioncollectionviewsourceicollectionview

Sorting ObservableCollection with ICollectionView does't work correctly


To generate the bug, select any item in TopDataGrid. As a result, the collection of items will be loaded into BottomDataGrid. This collection is sorted by Name property as I specified! Then select any other item in the TopDataGrid. The result is that ItemsSource of BottomDataGrid will be reloaded. And now the collection is unsorted! The collection looks as I specified it in code. Moreover, if I check _customerView with the debugger I see the sorted collection.

I know I can use List with OrderBy and INotifyPropertyChanged to explicitly command the UI to update itself instead of ObservableCollection and ICollectionView. But I think this is not the proper approach.

Win 7, .Net 4.0. Just copy and paste.

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <DataGrid Grid.Row="0" 
                AutoGenerateColumns="False"
                ItemsSource="{Binding Items}"
                SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
                CanUserAddRows="False"
                CanUserDeleteRows="False"
                CanUserResizeRows="False"
                CanUserSortColumns="False"
                SelectionMode="Single"
                SelectionUnit="FullRow">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Name}"></DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>
    <DataGrid Grid.Row="1" 
                AutoGenerateColumns="False"
                ItemsSource="{Binding SelectedItem.MyCollectionView}"
                CanUserAddRows="False"
                CanUserDeleteRows="False"
                CanUserResizeRows="False"
                CanUserSortColumns="False"
                SelectionMode="Single"
                SelectionUnit="FullRow">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Name}"></DataGridTextColumn>
            <DataGridTextColumn Binding="{Binding Index}"></DataGridTextColumn>
        </DataGrid.Columns>

    </DataGrid>

    <Grid Grid.Row="2">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBox Grid.Column="0" 
                    Text="{Binding Name1}"></TextBox>
        <TextBox Grid.Column="1" 
                    Text="{Binding Index}"></TextBox>
    </Grid>
</Grid>

code

public class TopGridItem
{
    private ObservableCollection<BottomGridItem> _collection;
    public ObservableCollection<BottomGridItem> Collection
    {
        get { return _collection; }
    }

    public String Name { get; set; }

    public ICollectionView MyCollectionView
    {
        get
        {
            ICollectionView _customerView = CollectionViewSource.GetDefaultView(Collection);
            _customerView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));

            return _customerView;
        }
    }

    public TopGridItem()
    {
        _collection = new ObservableCollection<BottomGridItem>();
        _collection.Add(new BottomGridItem { Name = "bbbbbb" });
        _collection.Add(new BottomGridItem { Name = "aaaaa" });
        _collection.Add(new BottomGridItem { Name = "aaaaa" });
        _collection.Add(new BottomGridItem { Name = "ccccc" });
        _collection.Add(new BottomGridItem { Name = "dddddd" });
    }

}

public class BottomGridItem
{
    public String Name { get; set; }
    public String Index { get; set; }
}

/// <summary>
/// Логика взаимодействия для NewWindow.xaml
/// </summary>
public partial class ProgressWindow : INotifyPropertyChanged
{
    public TopGridItem _selectedItem;

    public String Name1 { get; set; }
    public String Index { get; set; }
    public ObservableCollection<TopGridItem> Items { get; set; }

    public TopGridItem SelectedItem 
    {
        get { return _selectedItem; }

        set
        {
            _selectedItem = value;
            OnPropertyChanged("SelectedItem");

        }
    }

    public ProgressWindow()
    {
        InitializeComponent();
        DataContext = this;

        Items = new ObservableCollection<TopGridItem>();
        Items.Add(new TopGridItem {Name = "One"});
        Items.Add(new TopGridItem {Name = "Two"});
        Items.Add(new TopGridItem {Name = "Three"});
    }

    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

UPDATE

private void ClearSortDescriptionsOnItemsSourceChange()
    {
      this.Items.SortDescriptions.Clear();
      this._sortingStarted = false;
      List<int> descriptionIndices = this.GroupingSortDescriptionIndices;
      if (descriptionIndices != null)
        descriptionIndices.Clear();
      foreach (DataGridColumn dataGridColumn in (Collection<DataGridColumn>) this.Columns)
        dataGridColumn.SortDirection = new ListSortDirection?();
    }

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

It seems like in ClearSortDescriptionsOnItemsSourceChange method sorting is cleared and not respecify again. I think this is the matter.


Solution

  • After some investigations and attempts I think I can suggest at least two solutions.

    First:

    public TopGridItem SelectedItem 
    {
        get { return _selectedItem; }
    
        set
        {
            _selectedItem = value;
            OnPropertyChanged("SelectedItem");
    
            // _dataGrid - link to BottomDataGrid  
            _dataGrid.Items.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
    
        }
    }
    

    Second:

    public TopGridItem SelectedItem 
    {
        get { return _selectedItem; }
    
        set
        {
            _selectedItem = value;
            OnPropertyChanged("SelectedItem");
    
            if (_selectedItem != null)
            _selectedItem.MyCollectionView.Refresh();
        }
    }