WPF DataGrid игнорирует SortDescription

У меня возникла странная проблема с сортировкой WPF DataGrid (System.Windows.Controls.DataGrid в .NET 4.0).

Его ItemsSource привязан к свойству объекта datacontext:

<DataGrid HeadersVisibility="Column" SelectedIndex="0" MinHeight="30" ItemsSource="{Binding FahrtenView}" AutoGenerateColumns="False" x:Name="fahrtenDG">

ФартенВью выглядит так:

    public ICollectionView FahrtenView
    {
        get
        {
            var view = CollectionViewSource.GetDefaultView(_fahrten);
            view.SortDescriptions.Add(new SortDescription("Index", ListSortDirection.Ascending));
            return view;
        }
    }

DataGrid сортируется. Однако он сортируется только в первый раз, когда ему назначается DataContext. После этого изменение DataContext (путем выбора другого «родительского» объекта в иерархии данных) по-прежнему вызывает оценку свойства FahrtenView (я могу вставить BP, и отладчик остановится на этом), но добавленное описание сортировки полностью игнорируется, поэтому сортировка не выполняется. больше не работает.

Даже вызов fahrtenDG.Items.Refresh() для каждого DataContextChange не помогает.

Я почти уверен, что это правильный путь, когда дело доходит до сортировки WPF DataGrid, не так ли? Так почему же он так упрямо отказывается работать после того, как отлично выполнил свою работу в самый первый раз, когда его вызвали?

Любая идея? Я был бы очень признателен.

Привет, Хендрик


person Hendrik Wiese    schedule 24.06.2012    source источник
comment
вы должны добавить свое обновление в качестве ответа, а затем принять его (когда сможете)   -  person John Gardner    schedule 26.07.2012
comment
Аналогично stackoverflow.com/questions/9560528/issue-sorting-datagrid & stackoverflow.com/questions/6176771/   -  person Tony Pulokas    schedule 24.11.2016


Ответы (7)


Я унаследовал от DataGrid, чтобы мельком взглянуть на его внутренности. Я обнаружил, что по каким-то загадочным причинам, хотя при первом вызове OnItemsSourceChanged все выглядит нормально, при каждом последующем вызове OnItemsSourceChanged список SortDescription для ItemsSource представление коллекции пусто.

По этой причине я добавил пользовательское событие SetupSortDescription, которое вызывается в конце OnItemsSourceChanged. Теперь я добавляю описания сортировки в функцию обработчика событий, которая работает как шарм.

Я считаю это ошибкой в ​​наборе инструментов WPF DataGrid.

Вот мой переопределенный OnItemsSourceChanged

    protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
    {
        if (SetupSortDescriptions != null && (newValue != null)) 
            SetupSortDescriptions(this, new ValueEventArgs<CollectionView>((CollectionView)newValue)); 

        base.OnItemsSourceChanged(oldValue, newValue);
    }
person Hendrik Wiese    schedule 06.08.2012
comment
Большое спасибо за пост, Хендрик! Действительно, эту ошибку невозможно обойти только с помощью моделей представлений — нужно реализовать собственный DataGrid. Я сделал небольшую модификацию, используя прикрепленное свойство вместо события (код ниже). - person kat; 18.03.2014
comment
Спасибо! Я немного улучшил это, чтобы использовать MVVM, а не событие, предоставив привязку для списка SortDescriptions, который вы настраиваете только один раз. Смотрите мой другой ответ. - person Jon Barker; 18.08.2015
comment
Спасибо! Это сводило меня с ума! Я изменил ваш код, чтобы удовлетворить мои потребности. Он в основном сохраняет описания сортировки и восстанавливает их всякий раз, когда они сдуваются. Это может помочь другим: см. ниже. - person adamjhilton; 03.11.2017

Я немного улучшил ответ Хендрика, чтобы использовать MVVM, а не событие.

    public static readonly DependencyProperty SortDescriptionsProperty = DependencyProperty.Register("SortDescriptions", typeof(List<SortDescription>), typeof(ReSolverDataGrid), new PropertyMetadata(null));

    /// <summary>
    /// Sort descriptions for when grouped LCV is being used. Due to bu*g in WCF this must be set otherwise sort is ignored.
    /// </summary>
    /// <remarks>
    /// IN YOUR XAML, THE ORDER OF BINDINGS IS IMPORTANT! MAKE SURE SortDescriptions IS SET BEFORE ITEMSSOURCE!!! 
    /// </remarks>
    public List<SortDescription> SortDescriptions
    {
        get { return (List<SortDescription>)GetValue(SortDescriptionsProperty); }
        set { SetValue(SortDescriptionsProperty, value); }
    }

    protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
    {
        //Only do this if the newValue is a listcollectionview - in which case we need to have it re-populated with sort descriptions due to DG bug
        if (SortDescriptions != null && ((newValue as ListCollectionView) != null))
        {
            var listCollectionView = (ListCollectionView)newValue;
            listCollectionView.SortDescriptions.AddRange(SortDescriptions);
        }

        base.OnItemsSourceChanged(oldValue, newValue);
    }
person Jon Barker    schedule 18.08.2015
comment
У меня работает хорошо, если я предоставляю CollectionViewSource для сетки данных, а не ObservableCollection. У меня есть CollectionViewSource, определенный в ресурсах элемента управления, и еще один список SortDescription в статическом классе, который я привязываю к этому новому свойству. - person Benoit Dufresne; 22.03.2018

Я использовал интерактивный DataGrid из kat, чтобы создать поведение для WPF DataGrid.

Поведение сохраняет исходные SortDescriptions и применяет их при каждом изменении ItemsSource. Вы также можете указать IEnumerable<SortDescription>, который будет вызывать курорт при каждом изменении.

Поведение

public class DataGridSortBehavior : Behavior<DataGrid>
{
    public static readonly DependencyProperty SortDescriptionsProperty = DependencyProperty.Register(
        "SortDescriptions",
        typeof (IEnumerable<SortDescription>),
        typeof (DataGridSortBehavior),
        new FrameworkPropertyMetadata(null, SortDescriptionsPropertyChanged));

    /// <summary>
    ///     Storage for initial SortDescriptions
    /// </summary>
    private IEnumerable<SortDescription> _internalSortDescriptions;

    /// <summary>
    ///     Property for providing a Binding to Custom SortDescriptions
    /// </summary>
    public IEnumerable<SortDescription> SortDescriptions
    {
        get { return (IEnumerable<SortDescription>) GetValue(SortDescriptionsProperty); }
        set { SetValue(SortDescriptionsProperty, value); }
    }


    protected override void OnAttached()
    {
        var dpd = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof (DataGrid));
        if (dpd != null)
        {
            dpd.AddValueChanged(AssociatedObject, OnItemsSourceChanged);
        }
    }

    protected override void OnDetaching()
    {
        var dpd = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof (DataGrid));
        if (dpd != null)
        {
            dpd.RemoveValueChanged(AssociatedObject, OnItemsSourceChanged);
        }
    }

    private static void SortDescriptionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is DataGridSortBehavior)
        {
            ((DataGridSortBehavior) d).OnItemsSourceChanged(d, EventArgs.Empty);                
        }
    }

    public void OnItemsSourceChanged(object sender, EventArgs eventArgs)
    {
        // save description only on first call, SortDescriptions are always empty after ItemsSourceChanged
        if (_internalSortDescriptions == null)
        {
            // save initial sort descriptions
            var cv = (AssociatedObject.ItemsSource as ICollectionView);
            if (cv != null)
            {
                _internalSortDescriptions = cv.SortDescriptions.ToList();
            }
        }
        else
        {
            // do not resort first time - DataGrid works as expected this time
            var sort = SortDescriptions ?? _internalSortDescriptions;

            if (sort != null)
            {
                sort = sort.ToList();
                var collectionView = AssociatedObject.ItemsSource as ICollectionView;
                if (collectionView != null)
                {
                    using (collectionView.DeferRefresh())
                    {
                        collectionView.SortDescriptions.Clear();
                        foreach (var sorter in sort)
                        {
                            collectionView.SortDescriptions.Add(sorter);
                        }
                    }
                }
            }
        }
    }
}

XAML с необязательным параметром SortDescriptions

<DataGrid  ItemsSource="{Binding View}" >
    <i:Interaction.Behaviors>
        <commons:DataGridSortBehavior SortDescriptions="{Binding SortDescriptions}"/>
    </i:Interaction.Behaviors>
</DataGrid>

Настройка ViewModel ICollectionView

View = CollectionViewSource.GetDefaultView(_collection);
View.SortDescriptions.Add(new SortDescription("Sequence", ListSortDirection.Ascending));

Необязательно: свойство ViewModel для предоставления изменяемых описаний сортировки

public IEnumerable<SortDescription> SortDescriptions
{
    get
    {
        return new List<SortDescription> {new SortDescription("Sequence", ListSortDirection.Ascending)};
    }
}
person Juergen    schedule 16.07.2015
comment
Работает удовольствие. Спасибо. - person ifinlay; 13.01.2019

Если вы вызываете CollectionViewSource.GetDefaultView(..) для той же коллекции, вы возвращаете тот же объект collectionview, что может объяснить, почему добавление идентичной структуры sortdescription не вызывает изменения.

fahrtenDG.Items.Refresh() не может работать, так как вы не обновляете связанную коллекцию.

CollectionViewSource.GetDefaultView(_fahrten).Refresh() должен работать — я бы оставил ссылку на него.

Из вашего объяснения я не совсем понимаю изменение контекста данных - вы меняете его на новый объект? Если это так, все ваши привязки должны быть переоценены. Всегда ли это одна и та же коллекция, и ваше свойство Index в элементах списка изменяется, и именно поэтому вы ожидаете изменения - если это так, вашему элементу списка может потребоваться реализация INotifyPropertyChanged, потому что, если коллекция не изменяется, тогда нет необходимости прибегнуть.

Ваша реализация OnItemsSourceChanged(..) выглядит как хак :)

person Rune Andersen    schedule 03.08.2012

Я попытался обойти эту проблему с помощью модели представления — воссоздав ICollectionView в геттере и лихорадочно вызывая DeferRefresh(). Однако я могу подтвердить, что решение Хендрика — единственное, которое надежно работает. Я хотел опубликовать полный код ниже, если он кому-то поможет.

ПОСМОТРЕТЬ

<controls:SortableDataGrid
    ItemsSource="{Binding InfoSorted}"
    PermanentSort="{Binding PermanentSort}"
    CanUserSortColumns="False" />

ПОСМОТРЕТЬ МОДЕЛЬ

public ObservableCollection<Foo> Info { get; private set; }
public ICollectionView InfoSorted { get; private set; }
public IEnumerable<SortDescription> PermanentSort { get; private set; }

ТАМОЖЕННЫЙ КОНТРОЛЬ

public class SortableDataGrid : DataGrid
    {
        public static readonly DependencyProperty PermanentSortProperty = DependencyProperty.Register(
            "PermanentSort",
            typeof(IEnumerable<SortDescription>),
            typeof(SortableDataGrid),
            new FrameworkPropertyMetadata(null));

        public IEnumerable<SortDescription> PermanentSort
        {
            get { return (IEnumerable<SortDescription>)this.GetValue(PermanentSortProperty); }
            set { this.SetValue(PermanentSortProperty, value); }
        }

        protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
        {
            var sort = this.PermanentSort;
            if (sort != null)
            {
                sort = sort.ToList();
                var collectionView = newValue as ICollectionView;
                if (collectionView != null)
                {
                    using (collectionView.DeferRefresh())
                    {
                        collectionView.SortDescriptions.Clear();
                        foreach (SortDescription sorter in sort)
                        {
                            collectionView.SortDescriptions.Add(sorter);
                        }
                    }
                }
            }

            base.OnItemsSourceChanged(oldValue, newValue);
        }
    }
person kat    schedule 18.03.2014

Я поддерживаю подход Юргена к использованию привязанного поведения. Однако, поскольку моя версия этой проблемы возникла, когда я объявил объект CollectionViewSource в классе модели представления, я счел более прямым решение проблемы, добавив обработчик событий SortDescriptions_CollectionChanged, как показано в коде ниже. Этот код полностью находится в классе модели представления.

public CollectionViewSource FilteredOptionsView
{
    get
    {
        if (_filteredOptionsView == null)
        {
            _filteredOptionsView = new CollectionViewSource
            {
                Source = Options,
                IsLiveSortingRequested = true
            };
            SetOptionsViewSorting(_filteredOptionsView);
            _filteredOptionsView.View.Filter = o => ((ConstantOption)o).Value != null;
        }
        return _filteredOptionsView;
    }
}
private CollectionViewSource _filteredOptionsView;

protected void SetOptionsViewSorting(CollectionViewSource viewSource)
{
    // define the sorting
    viewSource.SortDescriptions.Add(_optionsViewSortDescription);
    // subscribe to an event in order to handle a bug caused by the DataGrid that may be
    // bound to the CollectionViewSource
    ((INotifyCollectionChanged)viewSource.View.SortDescriptions).CollectionChanged
                                    += SortDescriptions_CollectionChanged;
}

protected static SortDescription _optionsViewSortDescription
                    = new SortDescription("SortIndex", ListSortDirection.Ascending);

void SortDescriptions_CollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
{
    var collection = sender as SortDescriptionCollection;
    if (collection == null) return;
    // The SortDescriptions collection should always contain exactly one SortDescription.
    // However, when DataTemplate containing the DataGrid bound to the ICollectionView
    // is unloaded, the DataGrid erroneously clears the collection.
    if (collection.None())
        collection.Add(_optionsViewSortDescription);
}
person Tony Pulokas    schedule 24.11.2016

Спасибо! Это сводило меня с ума! Я изменил ваш код, чтобы удовлетворить мои потребности. Он в основном сохраняет описания сортировки и восстанавливает их всякий раз, когда они сдуваются. Это может помочь другим:

private List<SortDescription> SortDescriptions = null;

protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
 if (newValue is CollectionView collectionView)
  if (SortDescriptions == null)
   SortDescriptions = new List<SortDescription>(collectionView.SortDescriptions);
  else
   foreach (SortDescription sortDescription in SortDescriptions)
    collectionView.SortDescriptions.Add(sortDescription);

 base.OnItemsSourceChanged(oldValue, newValue);
}
person adamjhilton    schedule 03.11.2017