10

关于 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 进行排序时要走的路,不是吗?那么为什么它在第一次被调用时就完美地完成了它的工作,却如此顽固地拒绝工作呢?

任何的想法?我将不胜感激。

干杯,亨德里克

4

7 回答 7

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);
    }
于 2012-08-06T14:01:49.363 回答
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);
    }
于 2015-08-18T17:36:39.160 回答
4

我使用来自 kat 的 interited DataGrid 为 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);
                        }
                    }
                }
            }
        }
    }
}

带有可选 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)};
    }
}
于 2015-07-16T22:14:20.073 回答
1

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

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

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

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

您的 OnItemsSourceChanged(..) 实现似乎是一个 hack :)

于 2012-08-03T11:07:47.233 回答
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; }

自定义控制

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);
        }
    }
于 2014-03-18T18:47:45.963 回答
0

我赞同Juergen使用附加行为的方法。但是,由于我在视图模型类中声明 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);
}
于 2016-11-24T00:47:59.190 回答
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);
}
于 2017-11-03T20:13:09.457 回答