【问题标题】:WPF DataGrid ignores SortDescriptionWPF DataGrid 忽略 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 进行排序时要走的路,不是吗?那么为什么它在第一次被调用时就完美地完成了它的工作,却如此顽固地拒绝工作呢?

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

干杯, 亨德里克

【问题讨论】:

标签: wpf sorting datagrid collectionview


【解决方案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);
        }
        

        【讨论】:

          猜你喜欢
          • 2012-06-06
          • 1970-01-01
          • 1970-01-01
          • 2012-01-23
          • 1970-01-01
          • 2013-10-09
          • 2019-12-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多