WPF DataGrid忽略SortDescription

时间:2012-06-24 12:14:28

标签: wpf sorting datagrid collectionview

我在这里有一个奇怪的问题,关于WPF DataGrid的排序(.NET 4.0中的System.Windows.Controls.DataGrid)。

其ItemsSource绑定到datacontext对象的属性:

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

FahrtenView看起来像这样:

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

DataGrid已排序。但是它只在第一次分配DataContext时才进行排序。之后,更改DataContext(通过在数据层次结构中选择另一个“parental”对象)仍然会导致对FahrtenView属性进行评估(我可以放入BP并调试器停在那里)但添加的sortdescription完全被忽略,因此排序会不再工作了。

甚至在每个DataContextChange上调用fahrtenDG.Items.Refresh()也无济于事。

我很确定这是排序WPF DataGrid的方法,不是吗?那么为什么它在第一次被调用后完全完成工作后如此顽固地拒绝工作呢?

有什么想法吗?我会非常感激。

干杯, 亨德里克

7 个答案:

答案 0 :(得分:8)

我继承了DataGrid,以便对其内容进行简要介绍。我发现的是,出于一些神秘的原因,虽然第一次调用 OnItemsSourceChanged ,但在以后的每一次调用 OnItemsSourceChanged 的ItemsSource的SortDescription列表中,一切看起来都很好。集合视图为空

出于这个原因,我添加了一个在OnItemsSourceChanged末尾调用的自定义 SetupSortDescription 事件。现在我在事件处理函数中添加排序描述,它就像一个魅力。

我认为这是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);
    }

答案 1 :(得分:6)

我对Hendrik的回答有所改进,使用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);
    }

答案 2 :(得分:4)

我使用来自kat的interited DataGrid为WPF DataGrid创建一个Behavior。

该行为会保存初始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);
                        }
                    }
                }
            }
        }
    }
}
带有可选SortDescriptions参数的

XAML

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

答案 3 :(得分:1)

如果在同一个集合上调用CollectionViewSource.GetDefaultView(..),则会返回相同的collectionview对象,这可以解释为什么添加相同的sortdescription结构不会触发更改。

fahrtenDG.Items.Refresh()无法正常工作,因为您没有刷新绑定集合。

CollectionViewSource.GetDefaultView(_fahrten).Refresh()应该有效 - 我会保留对它的引用。

根据您的解释,我不太了解datacontext的更改 - 您是否将其更改为新对象?如果是这样,所有绑定都应该重新评估。总是它是相同的集合,并且您的元素上的Index属性发生了变化,这就是您期望更改的原因 - 如果是这样,您的列表元素可能需要INotifyPropertyChanged实现,因为如果集合没有更改那么就没有必要胜地。

你的OnItemsSourceChanged(..)实现看起来像是一个黑客:)

答案 4 :(得分:1)

我试图通过视图模型解决这个问题 - 在getter中重新创建ICollectionView并疯狂地调用DeferRefresh()。但是我可以确认Hendrik的解决方案是唯一可靠的解决方案。我想在下面发布完整代码,以防有人帮忙。

查看

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

CUSTOM CONTROL

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

答案 5 :(得分:0)

我支持Juergen's approach使用附加行为。但是,由于我在视图模型类中声明了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);
}

答案 6 :(得分:0)

谢谢!这让我很伤心!我修改了你的代码来满足我的需求。它基本上保留了排序描述,并在它们被吹走时恢复它们。这可能对其他人有所帮助:

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