Сортировка ObservableCollection с ICollectionView работает неправильно

Чтобы сгенерировать ошибку, выберите любой элемент в TopDataGrid. В результате коллекция элементов будет загружена в BottomDataGrid. Эта коллекция отсортирована по свойству Name, как я указал! Затем выберите любой другой элемент в TopDataGrid. В результате ItemsSource из BottomDataGrid будут перезагружены. И теперь коллекция несортированная! Коллекция выглядит так, как я указал в коде. Более того, если я проверю _customerView отладчиком, то увижу отсортированную коллекцию.

Я знаю, что могу использовать List с OrderBy и INotifyPropertyChanged, чтобы явно дать команду пользовательскому интерфейсу обновить себя вместо ObservableCollection и ICollectionView. Но я думаю, что это не правильный подход.

Вин 7, .Нет 4.0. Просто скопируйте и вставьте.

<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>

код

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
}

ОБНОВЛЕНИЕ

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;
    }

Вроде как в ClearSortDescriptionsOnItemsSourceChange методе сортировка очищается и не переопределяется заново. Я думаю, в этом дело.


person monstr    schedule 29.01.2015    source источник
comment
поскольку вы создаете _collection только один раз, создайте экземпляр ListCollectionView из _collection в конструкторе TopGridItemSortDescriptions) и всегда возвращать это в MyCollectionView   -  person dkozl    schedule 29.01.2015
comment
@dkozl не работает. все так же   -  person monstr    schedule 29.01.2015
comment
Вы хотите, чтобы список _collection был отсортирован или не отсортирован, когда пользователь щелкает верхний элемент сетки?   -  person SwDevMan81    schedule 29.01.2015
comment
@SwDevMan81 явно отсортирован. сейчас не отсортировано   -  person monstr    schedule 29.01.2015
comment
@monstr Кажется, это какая-то проблема с элементом управления DataGrid. Я не могу воспроизвести проблему, если сетка данных отключена для чего-то вроде ListBox. Хотя я не знаю, почему это происходит. Вы определенно привязаны к использованию DataGrid для нижнего элемента управления?   -  person Steven Rands    schedule 29.01.2015
comment
@Steven Rands К сожалению, это требование пользовательского интерфейса. Я знаю обходной путь, как я сказал выше, но мне он не нравится.   -  person monstr    schedule 29.01.2015
comment
@monstr Это странно. Вот кто-то еще сообщение о такой же проблеме еще в 2011 году, но, к сожалению, и по этому вопросу нет решения.   -  person Steven Rands    schedule 29.01.2015
comment
@ Стивен Рэндс, посмотрите обновление, пожалуйста   -  person monstr    schedule 29.01.2015


Ответы (2)


После некоторых исследований и попыток я думаю, что могу предложить по крайней мере два решения.

Первый:

public TopGridItem SelectedItem 
{
    get { return _selectedItem; }

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

        // _dataGrid - link to BottomDataGrid  
        _dataGrid.Items.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));

    }
}

Второй:

public TopGridItem SelectedItem 
{
    get { return _selectedItem; }

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

        if (_selectedItem != null)
        _selectedItem.MyCollectionView.Refresh();
    }
}
person monstr    schedule 30.01.2015

Есть хакерский обходной путь :O)

Измените свой класс TopGridItem, чтобы он реализовывал INotifyPropertyChanged, затем измените свойство MyCollectionView следующим образом:

public ICollectionView MyCollectionView
{
    get
    {
        return _myCollectionView;
    }
    set
    {
        _myCollectionView = value;
        OnPropertyChanged("MyCollectionView");
    }
}
ICollectionView _myCollectionView;

Добавьте публичный метод в TopGridItem вот так:

public void ResetView()
{
    MyCollectionView = null;  // This is the key to making it work
    ICollectionView customerView = CollectionViewSource.GetDefaultView(_collection);
    customerView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
    MyCollectionView = customerView;
}

Это требует, чтобы _collection было сделано полем класса, а не переменной, которая используется только в конструкторе. Говоря о которых:

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" });

    ResetView();
}

Обратите внимание на вызов ResetView.

Теперь все, что вам нужно сделать в установщике свойств SelectedItem для ProgressWindow, — это вызвать этот метод ResetView при изменении выбора:

public TopGridItem SelectedItem 
{
    get { return _selectedItem; }

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

        if (_selectedItem != null)
        {
            _selectedItem.ResetView();
        }
    }
}

Я тестировал это здесь, и, похоже, это работает. Если это не так, то я, должно быть, где-то сделал опечатку или, возможно, что-то пропустил в коде, который я опубликовал.


Рабочий вариант кода

XAML:

<Window x:Class="WpfApplication4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Height="350"
        Width="525">
    <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>
</Window>

Код программной части:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

namespace WpfApplication4
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            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" });
        }

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

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

        public event PropertyChangedEventHandler PropertyChanged;

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

    public class TopGridItem : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

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

        public String Name { get; set; }

        public ICollectionView MyCollectionView
        {
            get
            {
                return _myCollectionView;
            }
            set
            {
                _myCollectionView = value;
                OnPropertyChanged("MyCollectionView");
            }
        }
        ICollectionView _myCollectionView;

        public void ResetView()
        {
            MyCollectionView = null;
            ICollectionView _customerView = CollectionViewSource.GetDefaultView(_collection);
            _customerView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
            MyCollectionView = _customerView;
        }

        ObservableCollection<BottomGridItem> _collection;

        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" });
            ResetView();
        }
    }

    public class BottomGridItem
    {
        public String Name { get; set; }
        public String Index { get; set; }
    }
}
person Steven Rands    schedule 29.01.2015
comment
@monstr Я добавил полный рабочий код в конец своего поста. Это было скопировано и вставлено из написанного мной тестового приложения, основанного на вашем коде. Как я уже сказал, это, кажется, работает для меня. Если это не сработает для вас, то я не знаю, что еще посоветовать, извините. - person Steven Rands; 30.01.2015
comment
@ Стивен Рэндс, скопировано. Все еще не работает для меня. Коллекция вообще не сортируется. Я уже нашел лучшие решения, спасибо :) - person monstr; 30.01.2015
comment
Возможно, другой пользователь SO мог бы создать новый проект WPF, используя стандартный шаблон Visual Studio, скопировать и вставить код и сообщить мне, работает он или нет. Может быть проблема с версией .NET Framework? (Это было протестировано в версии 4.5 с использованием VS2012). - person Steven Rands; 30.01.2015
comment
@ Стивен Рэндс, может быть. Я работаю над 4.0, VS2010 и уже сталкивался со сценариями, которые работают в 4.0 и не работают в 4.5. - person monstr; 30.01.2015