有界数据更改后重新排序 WPF DataGrid

Posted

技术标签:

【中文标题】有界数据更改后重新排序 WPF DataGrid【英文标题】:Re-sort WPF DataGrid after bounded Data has changed 【发布时间】:2012-07-15 08:35:51 【问题描述】:

当基础数据更改时,我正在寻找一种方法来重新排序我的DataGrid

(设置非常标准:DataGrid 的 ItemSource 属性绑定到 ObservableCollection;列是 DataGridTextColumns;DataGrid 内的数据对 ObservableCollection 内的更改做出正确反应;单击时排序工作正常鼠标)

有什么想法吗?

【问题讨论】:

你探索过CollectionViewSource吗? @WPF-it 现在我做到了;) 你给了我正确的提示,我能够用CollectionViewSource 解决我的问题。看看我自己的答案,看看我做了什么。谢谢! 【参考方案1】:

sellmeadog 的回答要么过于复杂,要么已经过时。超级简单。您所要做的就是:

<UserControl.Resources>
    <CollectionViewSource 
        Source="Binding MyCollection" 
        IsLiveSortingRequested="True" 
        x:Key="MyKey" />
</UserControl.Resources>

<DataGrid ItemsSource="Binding Source=StaticResource MyKey " >...

【讨论】:

请注意,IsLiveSortingRequested 需要 .NET 4.5 它如何知道按哪一列排序?它不需要某个地方的 CollectionViewSource SortDescription 吗? 其实你没有提到它是如何识别要排序的列的.. 根据问题,用户通过点击列标题选择排序。在这种情况下,无需在 CollectionViewSource 中定义排序。【参考方案2】:

我花了整个下午,但我终于找到了一个解决方案,它出奇地简单高效

要控制相关 UI 控件的行为(这里是 DataGrid),可以简单地使用 CollectionViewSource。它充当 ViewModel 中 UI 控件的一种代表,而不会完全破坏 MVMM 模式。

在 ViewModel 中声明一个 CollectionViewSource 和一个普通的 ObservableCollection&lt;T&gt; 并将 CollectionViewSource 包裹在 ObservableCollection 周围:

// Gets or sets the CollectionViewSource
public CollectionViewSource ViewSource  get; set; 

// Gets or sets the ObservableCollection
public ObservableCollection<T> Collection  get; set; 

// Instantiates the objets.
public ViewModel () 

    this.Collection = new ObservableCollection<T>();
    this.ViewSource = new CollectionViewSource();
    ViewSource.Source = this.Collection;

然后在应用程序的 View 部分中,您无需将 CollectionControlItemsSource 绑定到 CollectionViewSource 的 View 属性,而不是直接绑定到 ObservableCollection

<DataGrid ItemsSource="Binding ViewSource.View" />

从此时起,您可以在 ViewModel 中使用 CollectionViewSource 对象直接操作 View 中的 UI 控件。

例如排序——这是我的主要问题——看起来像这样:

// Specify a sorting criteria for a particular column
ViewSource.SortDescriptions.Add(new SortDescription ("columnName", ListSortDirection.Ascending));

// Let the UI control refresh in order for changes to take place.
ViewSource.View.Refresh();

你看,非常非常简单和直观。希望这可以帮助其他喜欢它帮助我的人。

【讨论】:

这很棒。唯一的问题是,一旦将 ViewSource.Source 设置为 ObservableCollection,就不能再在 BackgroundWorker 中更改 ObservableCollection。或者至少我不能。 如果您将控件直接绑定到 ObservableCollection,也会发生这种情况。在这两种情况下,您实际上都可以对集合进行更改,只需在应用程序调度程序上调用一个操作。 嗨 Marc Wellman,我正在尝试使用 WPF 数据网格进行排序,但数据网格甚至没有显示我绑定到它的数据,更不用说排序了。在我使用 gridName.ItemsSource = myObservableCollection; 进行基本的非排序数据网格之前加上 gridName.Items.Refresh();并且网格使用这种方法正确加载数据,但是当我切换到你的数据网格时,数据网格根本没有显示任何数据。我正在考虑开一个新帖子,这样我就可以展示我的代码,然后参考这篇帖子,但不确定这是否是 *** 世界中的失礼。有什么建议吗? 它正在工作,但刷新需要很长时间。我在 DataGrid 中只有 43 条记录 我和@C有同样的问题【参考方案3】:

对于遇到此问题的其他人,这可能会让您开始... 如果你有一个 INotifyPropertyChanged 项目的集合,你可以使用它而不是 ObservableCollection - 它会在集合中的单个项目发生变化时刷新: 注意:由于这会将项目标记为已删除然后已读取(即使它们实际上并未被删除和添加),选择可能会不同步。对于我的小型个人项目来说已经足够了,但它还没有准备好发布给客户......

public class ObservableCollection2<T> : ObservableCollection<T>

    public ObservableCollection2()
    
        this.CollectionChanged += ObservableCollection2_CollectionChanged;
    

    void ObservableCollection2_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    
        if (e.OldItems != null)
            foreach (object o in e.OldItems)
                remove(o);
        if (e.NewItems != null)
            foreach (object o in e.NewItems)
                add(o);
    
    void add(object o)
    
        INotifyPropertyChanged ipc = o as INotifyPropertyChanged;
        if(ipc!=null)
            ipc.PropertyChanged += Ipc_PropertyChanged;
    
    void remove(object o)
    
        INotifyPropertyChanged ipc = o as INotifyPropertyChanged;
        if (ipc != null)
            ipc.PropertyChanged -= Ipc_PropertyChanged;
    
    void Ipc_PropertyChanged(object sender, PropertyChangedEventArgs e)
    
        NotifyCollectionChangedEventArgs f;

        f = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, sender);
        base.OnCollectionChanged(f);
        f = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, sender);
        base.OnCollectionChanged(f);
    

【讨论】:

【参考方案4】:

这更多是为了澄清而不是答案,但 WPF 总是 绑定到 ICollectionView 而不是源集合。 CollectionViewSource 只是一种用于创建/检索集合视图的机制。

这里有一个很好的资源,可以帮助您更好地利用 WPF 中的集合视图:http://bea.stollnitz.com/blog/?p=387

在 XAML 中使用 CollectionViewSource 实际上可以简化一些代码:

<Window.Resources>
    <CollectionViewSource Source="Binding MySourceCollection" x:Key="cvs">
      <CollectionViewSource.SortDescriptions>
        <scm:SortDescription PropertyName="ColumnName" />
      </CollectionViewSource.SortDescriptions>
    </CollectionViewSource>
</Window.Resources>

...

<DataGrid ItemsSource="Binding Source=StaticResource cvs">
</DataGrid>

有些人认为,在遵循 MVVM 模式时,视图模型应该始终公开集合视图,但在我看来,这仅取决于用例。如果视图模型永远不会直接与集合视图交互,那么在 XAML 中配置它会更容易。

【讨论】:

非常感谢您的评论——确实对我帮助很大。 这是一个很好的建议——CollectionViewSource 应该是 View 背后代码中的一个属性(并且可以反过来绑定到 VM 上的 ObservableCollection),或者将其声明为本地静态资源正如你所做的那样。 这是最好的解决方案,您的 XAML 中只有一个错误 - 它是 SortDescription 元素中的“PropertyName”而不是“Property”。 谢谢!这有帮助。我只是很难弄清楚 scm 命名空间。这里是为试图找到它的人准备的:xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase" 那个链接失效了,但是我在The Internet Archive.找到了文字【参考方案5】:

我看不到任何明显简单的方法,所以我会尝试附加行为。这有点混蛋,但会给你你想要的:

public static class DataGridAttachedProperty

     public static DataGrid _storedDataGrid;
     public static Boolean GetResortOnCollectionChanged(DataGrid dataGrid)
     
         return (Boolean)dataGrid.GetValue(ResortOnCollectionChangedProperty);
     

     public static void SetResortOnCollectionChanged(DataGrid dataGrid, Boolean value)
     
         dataGrid.SetValue(ResortOnCollectionChangedProperty, value);
     

    /// <summary>
    /// Exposes attached behavior that will trigger resort
    /// </summary>
    public static readonly DependencyProperty ResortOnCollectionChangedProperty = 
         DependencyProperty.RegisterAttached(
        "ResortOnCollectionChangedProperty", typeof (Boolean),
         typeof(DataGridAttachedProperty),
         new UIPropertyMetadata(false, OnResortOnCollectionChangedChange));

    private static void OnResortOnCollectionChangedChange
        (DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    
      _storedDataGrid = dependencyObject as DataGrid;
      if (_storedDataGrid == null)
        return;

      if (e.NewValue is Boolean == false)
        return;

      var observableCollection = _storedDataGrid.ItemsSource as ObservableCollection;
      if(observableCollection == null)
        return;
      if ((Boolean)e.NewValue)
        observableCollection.CollectionChanged += OnCollectionChanged;
      else
        observableCollection.CollectionChanged -= OnCollectionChanged;
    

    private static void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    
      if (e.OldItems == e.NewItems)
        return;

      _storedDataGrid.Items.Refresh()
    

然后,您可以通过以下方式附加它:

<DataGrid.Style>
  <Style TargetType="DataGrid">
    <Setter 
      Property="AttachedProperties:DataGridAttachedProperty.ResortOnCollectionChangedProperty" 
                                    Value="true" 
      />
   </Style>
 </DataGrid.Style>

【讨论】:

非常感谢您的详细回答,但不幸的是我无法使其工作。我设法将您的属性附加到我的 DataGrid 并在我的基础集合的每个 CollectionChange 事件上调用 _storedDataGrid.ItemsCollection.Refresh()。但由于某种原因,DataGrid 不会排序。我不确定,但可能是因为我使用了多个线程,并且在将刷新调用传播到 UIThread 时遇到了一些问题。无论如何,我找到了另一个很好的幸运解决方案 - 请参阅我自己的答案。不过,再次感谢您!

以上是关于有界数据更改后重新排序 WPF DataGrid的主要内容,如果未能解决你的问题,请参考以下文章

有界ComboBox自动完成排序

在 DataTable 更改 WPF 后更新绑定到 DataTable 的 DataGrid

如何在wpf中动态选择TextBlock动画类型?

重新排序单元格后如何更改获取结果控制器数组中对象的顺序

在 Material-Table 中重新排序后是不是可以获得当前页面数据?

排序算法堆排序及有界堆排序Java实现及分析