2012-06-24 41 views
8

我有一個奇怪的問題,這裏有關於WPF DataGrid(.NET 4.0中的System.Windows.Controls.DataGrid)的排序。WPF DataGrid忽略SortDescription

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

數據網格被排序。然而,它只在第一次被分配一個DataContext時才被排序。之後,更改DataContext(通過在數據層次結構中選擇另一個「父級」對象)仍然會導致FahrtenView屬性被評估(我可以放入一個BP並停止調試器),但添加的sortdescription完全被忽略,不工作了。

即使對每個DataContextChange調用fahrtenDG.Items.Refresh()也沒有幫助。

我很確定這是要排序WPF DataGrid的方式,不是嗎?那麼,爲什麼在第一次被稱呼時完全完成工作之後,它會拒絕如此頑固地工作?

有什麼想法?我會很感激。

乾杯, 亨德里克

+0

你應該添加您的更新作爲一個答案,然後接受它(如果你能) –

+0

YEPP,你說得對。完成! –

+0

類似於http://stackoverflow.com/questions/9560528/issue-sorting-datagrid&http://stackoverflow.com/questions/6176771/wpf-datagrid-icollectionview-sorting-bug –

回答

8

我從DataGrid的繼承趕上其膽量短暫的一瞥。我發現的是,對於某些神祕的原因,雖然第一次OnItemsSourceChanged被調用,一切都看起來不錯,在每以下調用OnItemsSourceChanged的的ItemsSource集合視圖的SortDescription列表爲空

因此,我添加了一個自定義的SetupSortDescription在OnItemsSourceChanged末尾調用的事件。現在我在事件處理函數中添加排序描述,這個功能就像一個魅力一樣。

我認爲這是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); 
    } 
+1

非常感謝你的文章,亨德里克!通過單獨的視圖模型來解決這個bug實際上是不可能的 - 人們必須實現一個自定義的DataGrid。我使用附加屬性進行了小修改而不是事件(下面的代碼)。 – kat

+0

不客氣,朋友。 –

+0

謝謝!我改進了這一點,以使用MVVM而不是事件 - 通過爲SortDescription列表提供一個綁定,您只需設置一次。看到我的其他答案。 –

1

如果在同一個集合調用CollectionViewSource.GetDefaultView(..)你會得到同樣的CollectionView對象返回,這可以解釋爲什麼添加相同sortdescription結構不會觸發的變化。

fahrtenDG.Items.Refresh()無法工作,因爲您沒有刷新綁定的集合。

CollectionViewSource.GetDefaultView(_fahrten).Refresh()應該工作 - 我會保留對它的引用。

從你的解釋我不太瞭解datacontext的變化 - 你是否將它更改爲一個新的對象?如果是這樣,你所有的綁定應該重新評估。它是否始終是相同的集合,並且您的監聽器上的Index屬性發生更改,這就是爲什麼您期望發生更改 - 如果是這樣,您的列表元素可能需要INotifyPropertyChanged實現,因爲如果集合沒有更改,那麼就不需要採取。

你OnItemsSourceChanged(..)的實現似乎是一個黑客:)

1

我試圖解決這個問題,視圖模型 - 通過在吸氣重建ICollectionView,並在瘋狂地打電話DeferRefresh()。但是我可以證實亨德里克的解決方案是唯一可靠運行的解決方案。我想在下面發佈完整的代碼以防有人幫忙。

VIEW

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

我用interited的DataGrid從吉來爲WPF數據網格中的行爲。

該行爲保存了最初的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); 
         } 
        } 
       } 
      } 
     } 
    } 
} 

XAML與可選SortDescriptions參數

<DataGrid ItemsSource="{Binding View}" > 
    <i:Interaction.Behaviors> 
     <commons:DataGridSortBehavior SortDescriptions="{Binding SortDescriptions}"/> 
    </i:Interaction.Behaviors> 
</DataGrid> 

視圖模型ICollectionView設置

View = CollectionViewSource.GetDefaultView(_collection); 
View.SortDescriptions.Add(new SortDescription("Sequence", ListSortDirection.Ascending)); 

可選:視圖模型屬性提供多變SortDescriptions

public IEnumerable<SortDescription> SortDescriptions 
{ 
    get 
    { 
     return new List<SortDescription> {new SortDescription("Sequence", ListSortDirection.Ascending)}; 
    } 
} 
4

我對亨德里克的答案改進了一下,使用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); 
    } 
+1

爲我工作很好,很好的解決方案。 – willem

0

我認可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); 
} 
0

謝謝!這讓我bat!不安!我修改了您的代碼以滿足我的需求。它基本上堅持排序說明,並在它們被吹走時恢復它們。這可以幫助別人:

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