WPF DataGrid 忽略 SortDescription

Posted

技术标签:

【中文标题】WPF DataGrid 忽略 SortDescription【英文标题】:WPF DataGrid ignores SortDescription 【发布时间】:2012-06-26 00:01:38 【问题描述】:

关于 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(通过在数据层次结构中选择另一个“父”对象)仍然会导致属性 FahrtenView 被评估(我可以放入一个 BP 并且调试器在那里停止)但是添加的排序描述被完全忽略,因此排序确实不再工作了。

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

我很确定这是对 WPF DataGrid 进行排序时要走的路,不是吗?那么为什么它在第一次被调用时就完美地完成了它的工作,却如此顽固地拒绝工作呢?

有什么想法吗?我将不胜感激。

干杯, 亨德里克

【问题讨论】:

你应该添加你的更新作为答案,然后接受它(如果可以的话) 类似于***.com/questions/9560528/issue-sorting-datagrid & ***.com/questions/6176771/… 【参考方案1】:

我从 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);
    

【讨论】:

非常感谢您的帖子,亨德里克!仅通过视图模型确实不可能解决此错误 - 必须实现自定义 DataGrid。我使用附加属性而不是事件(下面的代码)进行了小修改。 谢谢!我改进了这一点以使用 MVVM 而不是事件 - 通过为您设置一次的 SortDescriptions 列表提供绑定。请参阅我的其他答案。 谢谢!这让我发疯!我修改了您的代码以满足我的需要。它基本上保留了排序描述,并在它们被吹走时恢复它们。这可能对其他人有所帮助:见下文。【参考方案2】:

我对 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);
    

【讨论】:

对我来说效果很好,只要我向数据网格提供 CollectionViewSource 而不是 ObservableCollection。我在控件的资源中定义了我的CollectionViewSource,并在我绑定到这个新属性的静态类中定义了另一个SortDescription 列表。【参考方案3】:

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

该行为会保存初始的 SortDescriptions 并将它们应用于 ItemsSource 的每次更改。 您还可以提供IEnumerable&lt;SortDescription&gt;,这将在每次更改时触发。

行为

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 属性,用于提供可更改的 SortDescriptions

public IEnumerable<SortDescription> SortDescriptions

    get
    
        return new List<SortDescription> new SortDescription("Sequence", ListSortDirection.Ascending);
    

【讨论】:

工作愉快。谢谢。【参考方案4】:

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

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

CollectionViewSource.GetDefaultView(_fahrten).Refresh() 应该可以工作 - 我会保留对它的引用。

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

您的 OnItemsSourceChanged(..) 实现看起来像一个 hack :)

【讨论】:

【参考方案5】:

我试图通过视图模型解决这个问题——通过在 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; 

自定义控制

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

【讨论】:

【参考方案6】:

我赞同 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);

【讨论】:

【参考方案7】:

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

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

【讨论】:

以上是关于WPF DataGrid 忽略 SortDescription的主要内容,如果未能解决你的问题,请参考以下文章

wpf datagrid怎么得到焦点?用datagrid.focus()不行

wpf datagrid 多行表头

wpf datagrid 怎么增加数据行

wpf datagrid cell 设置焦点

wpf 怎么给datagrid 右键菜单加多个选项?

wpf datagrid 默认选择为最新的一行,不知道为啥设置不了