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

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

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

有什么想法吗?

【问题讨论】:

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

标签: c# wpf data-binding datagrid 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();并且网格使用这种方法正确加载数据,但是当我切换到你的数据网格时,数据网格根本没有显示任何数据。我正在考虑开一个新帖子,这样我就可以展示我的代码,然后参考这篇帖子,但不确定这是否是 stackoverflow 世界中的失礼。有什么建议吗?
  • 它正在工作,但刷新需要很长时间。我在 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 时遇到了一些问题。无论如何,我找到了另一个很好的幸运解决方案 - 请参阅我自己的答案。不过,再次感谢您!
    猜你喜欢
    • 2010-11-29
    • 2017-11-30
    • 1970-01-01
    • 2016-06-24
    • 2010-11-17
    • 2014-01-23
    • 1970-01-01
    • 2013-05-13
    • 2014-08-28
    相关资源
    最近更新 更多