简体   繁体   English

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

[英]Re-sort WPF DataGrid after bounded Data has changed

I am looking for a way to re-sort my DataGrid when the underlying data has changed . 我正在寻找一种方法来在基础数据发生变化重新排序我的DataGrid

(The setting is quite standard: The DataGrid's ItemSource property is bound to an ObservableCollection ; The columns are DataGridTextColumns ; The data inside the DataGrid reacts correctly on changes inside the ObservableCollection; Sorting works fine when clicked with the mouse) (设置非常标准:DataGrid的ItemSource属性绑定到ObservableCollection ;列是DataGridTextColumns ; DataGridTextColumns的数据对ObservableCollection内的更改做出正确反应;使用鼠标单击时排序工作正常)

Any ideas ? 有任何想法吗 ?

It took me the whole afternoon but I finally found a solution that is surprisingly simple , short and efficient : 整个下午花了我一个 小时 ,但我终于找到了一个 简单简洁高效 的解决方案

To control the behaviors of the UI control in question (here a DataGrid ) one might simply use a CollectionViewSource . 要控制所讨论的UI控件的行为(这里是一个DataGrid ),可以简单地使用CollectionViewSource It acts as a kind of representative for the UI control inside your ViewModel without completely breaking the MVMM pattern. 它可以作为ViewModel中UI控件的一种代表,而不会完全破坏MVMM模式。

In the ViewModel declare both a CollectionViewSource and an ordinary ObservableCollection<T> and wrap the CollectionViewSource around the ObservableCollection : 在ViewModel中声明CollectionViewSource和普通的ObservableCollection<T>并围绕ObservableCollection包装CollectionViewSource

// 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;
}

Then in the View part of the application you have nothing else to do as to bind the ItemsSource of the CollectionControl to the View property of the CollectionViewSource instead of directly to the ObservableCollection : 然后在应用程序的视图部分你没有事情做,因为到绑定ItemsSource的的CollectionControl到的视图属性CollectionViewSource ,而不是直接到ObservableCollection

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

From this point on you can use the CollectionViewSource object in your ViewModel to directly manipulate the UI control in the View. 从这一点开始,您可以使用ViewModel中的CollectionViewSource对象直接操作View中的UI控件。

Sorting for example - as has been my primary problem - would look like this: 例如排序 - 这是我的主要问题 - 看起来像这样:

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

You see, very very simple and intuitive. 你看,非常非常简单和直观。 Hope that this helps other people like it helped me. 希望这有助于其他人喜欢它帮助我。

This is more for clarification than it is an answer, but WPF always binds to an ICollectionView and not the source collection. 这更像是一个澄清而不是答案,但WPF 总是绑定到ICollectionView而不是源集合。 CollectionViewSource is just a mechanism used to create/retrieve the collection view. CollectionViewSource只是一种用于创建/检索集合视图的机制。

Here's a great resource about the topic which should help you make better use of collection views in WPF: http://bea.stollnitz.com/blog/?p=387 以下是有关该主题的优秀资源,可帮助您更好地利用WPF中的集合视图: http//bea.stollnitz.com/blog/? p = 387

Using CollectionViewSource in XAML can actually simplify your code some: 在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>

Some people argue that when following the MVVM pattern, the view model should always expose the collection view but in my opinion, it just depends on the use case. 有些人认为,在遵循MVVM模式时,视图模型应该始终公开集合视图,但在我看来,它只取决于用例。 If the view model is never going to directly interact with the collection view, it's just easier to configure it in XAML. 如果视图模型永远不会直接与集合视图交互,那么在XAML中配置它会更容易。

The answer by sellmeadog is either overly complicated or out of date. sellmeadog的答案要么过于复杂,要么过时。 It's super simple. 这非常简单。 All you have to do is: 你所要做的就是:

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

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

I cannot see any obviously easy ways, so I would try an Attached Behavior. 我看不出任何明显简单的方法,所以我会尝试附加行为。 It is a bit of a bastardization, but will give you what you want: 这有点像一个混蛋,但会给你你想要的东西:

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()
    }
}

Then, you can attach it via: 然后,您可以通过以下方式附加它:

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

For anyone else having this problem, this may get you started... If you have a collection of INotifyPropertyChanged items, you can use this instead of ObservableCollection - it will refresh when individual items in the collection change: note: since this flags the items as removed then readded (even though they aren't actually removed and added,) selections may get out of sync. 对于其他有这个问题的人来说,这可能会让你开始...如果你有一个INotifyPropertyChanged项目的集合,你可以使用它而不是ObservableCollection - 当集合中的各个项目改变时它会刷新:注意:因为这会标记项目删除然后重新读取(即使它们实际上没有删除和添加),选择可能会不同步。 It's good enough for my small personal projects, but it's not ready to release to customers... 这对我的小型个人项目来说已经足够了,但它还没有准备好发布给客户......

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM