WPF中DataGrid控件的過濾(Filter)性能分析及優化

DataGrid控件是一個列表控件, 可以進行過濾,排序等。本文主要針對DataGrid的過濾功能進行分析, 並提供優化方案。

1)DataGrid的過濾過程:
     用戶輸入過濾條件
     調用DataGrid的CollectionViewSource的View.Refresh()功能
     DataGrid控件內部調用CollectionView的RefreshOverride方法
     CollectionView會調用CollectionViewSource的Filter回調函數來過濾符合自定義過濾條件的數據
     CollectionView調用內部的OnCollectionChanged和OnCurrentChanged分別更新界面上的數據和當前選中的Item


2)通過分析發現(10W條數據, 實時過濾時UI非常卡,導致用戶輸入過濾字符丟失),調用CollectionViewSource的View.Refresh()的性能損耗主要集中於:
     CollectionViewSource.Filter註冊的方法,以及OnCollectionChanged(每次更新都導致ItemContainerGenerator重新構造UI元素)
     
     
3)優化方向:
     減少CollectionViewSource.Filter註冊的方法的耗時(在實時過濾中每個條件的更改都會調用Refresh從而調用Filter方法)
     減少OnCollectionChanged調用的次數。


4)具體優化措施:
     實例化3個Timer, 分別用於獲取過濾後的數組(調用Filter)、調用OnCollectionChanged、OnCurrentItemChanged。3個timer分別由前一個timer完成時啓動, 形成一個順序操作。每次調用Timer時,先停止後續Timer的執行, 這樣保證在合理的時間間隔裏只有一次Refresh完整完成。


5)實現:
     下面代碼重載了ObservableCollection, 然後創建自定義的ListCollectionview.使用時只要用CustomCollection聲明列表數據,包裝爲CollectionViewSource, 綁定到DataGrid的ItemSource即可。

//聲明數組數據
  public CustomCollection<StudyInfoModel> StudyList
        {
            get { return studyList; }
        }

//包裝爲CollectionView
     <CollectionViewSource Source="{Binding StudyList}" x:Key="StudyListView">
                <CollectionViewSource.SortDescriptions>
                    <ComponentModel:SortDescription PropertyName="DateTime" Direction="Descending"/>
                </CollectionViewSource.SortDescriptions>
            </CollectionViewSource>

//綁定到DataGrid
<DataGrid ItemsSource="{Binding Mode=OneWay, Source={StaticResource StudyListView}}" />







       public class CustomCollectionView<T> : ListCollectionView
     {         private readonly DispatcherTimer _timerRefreshCalculate = new DispatcherTimer();         private readonly DispatcherTimer _timerRefreshUI = new DispatcherTimer();         private readonly DispatcherTimer _timerRefreshCurrentItem = new DispatcherTimer();         private bool _isRefreshingCalculate = false;         private object _oldSelectedItem = null;           public CustomCollectionView(IList list)             : base(list)         {             _timerRefreshUI.Interval = new TimeSpan(0, 0, 0, 0, 300);             _timerRefreshCurrentItem.Interval = new TimeSpan(0, 0, 0, 0, 500);             _timerRefreshCalculate.Interval = new TimeSpan(0, 0, 0, 0, 200);         }           #region Override Method           protected override void OnPropertyChanged(PropertyChangedEventArgs e)         {             if (_isRefreshingCalculate)             {                 return;             }               base.OnPropertyChanged(e);         }           protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)         {             if (_isRefreshingCalculate)             {                 return;             }               base.OnCollectionChanged(args);         }           protected override void OnCurrentChanged()         {             if (_isRefreshingCalculate)             {                 return;             }               base.OnCurrentChanged();         }           protected override void RefreshOverride()         {             CancelAllRefreshRequest();               StartRefresh();         }           #endregion             #region Public Method           public void CancelAllRefreshRequest()         {             _timerRefreshCurrentItem.Stop();             _timerRefreshCurrentItem.Tick -= TimerCurrentItem;               _timerRefreshUI.Stop();             _timerRefreshUI.Tick -= TimerUI;               _timerRefreshCalculate.Stop();             _timerRefreshCalculate.Tick -= TimerCalculate;               if (null != this.CurrentItem)             {                 _oldSelectedItem = this.CurrentItem;             }               SetCurrent(null, -1);         }           #endregion             #region Private Method           private void StartRefresh()         {             _timerRefreshCurrentItem.Stop();             _timerRefreshCurrentItem.Tick -= TimerCurrentItem;               _timerRefreshUI.Stop();             _timerRefreshUI.Tick -= TimerUI;               _timerRefreshCalculate.Stop();             _timerRefreshCalculate.Tick -= TimerCalculate;               //begin to refresh from calculate, so set flag by true.             //and it shielded any collection action during the calculating time.              //this logic will avoid items are not correct at UI during refresh.             _isRefreshingCalculate = true;               _timerRefreshCalculate.Tick += TimerCalculate;             _timerRefreshCalculate.Start();         }           private void RefreshCalculate(CancellationToken? token)         {             _timerRefreshCalculate.Tick -= TimerCalculate;               if (null != token && null != token.Value)             {                 token.Value.ThrowIfCancellationRequested();             }               _isRefreshingCalculate = true;               base.RefreshOverride();               _isRefreshingCalculate = false;               if (null != token && null != token.Value)             {                 token.Value.ThrowIfCancellationRequested();             }         }           private void RefreshUI()         {             try             {                 //detach timer                 _timerRefreshUI.Tick -= TimerUI;                   base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));                   //set timer to refresh current item                 _timerRefreshCurrentItem.Tick -= TimerCurrentItem;                 _timerRefreshCurrentItem.Tick += TimerCurrentItem;                 _timerRefreshCurrentItem.Start();             }             catch (OperationCanceledException)             {                 return;             }         }           private void RefreshCurrentItem()         {             _timerRefreshCurrentItem.Tick -= TimerCurrentItem;               if (null == this.InternalList || this.InternalList.Count <= 0)             {                 return;             }               if (null != _oldSelectedItem)             {                 var index = this.InternalList.IndexOf(_oldSelectedItem);                 if (index != -1)                 {                     SetCurrent(_oldSelectedItem, index);                 }                 else                 {                     SetCurrent(this.InternalList[0], 0);                 }             }             else             {                 SetCurrent(this.InternalList[0], 0);             }               //Set event to update UI             base.OnCurrentChanged();               this.OnPropertyChanged("IsCurrentAfterLast");             this.OnPropertyChanged("IsCurrentBeforeFirst");             this.OnPropertyChanged("CurrentPosition");             this.OnPropertyChanged("CurrentItem");         }           private void TimerCalculate(object sender, EventArgs e)         {             _timerRefreshCurrentItem.Stop();             _timerRefreshCurrentItem.Tick -= TimerCurrentItem;               _timerRefreshUI.Stop();             _timerRefreshUI.Tick -= TimerUI;               try             {                 RefreshCalculate(null);             }             catch (OperationCanceledException)             {             }               _timerRefreshUI.Interval = new TimeSpan(0, 0, 0, 0, 50 + Math.Min((int)(this.InternalCount / 80), 300));             _timerRefreshUI.Tick += TimerUI;             _timerRefreshUI.Start();         }           private void TimerUI(object sender, EventArgs e)         {             RefreshUI();         }           private void TimerCurrentItem(object sender, EventArgs e)         {             RefreshCurrentItem();         }           private void OnPropertyChanged(string propertyName)         {             OnPropertyChanged(new PropertyChangedEventArgs(propertyName));         }           #endregion     }       public class CustomCollection<T> : ObservableCollection<T>, ICollectionViewFactory     {         public CustomCollection()         {         }           public CustomCollection(List<T> list)             : base(list)         {         }           public CustomCollection(IEnumerable<T> collection)             : base(collection)         {         }           public ICollectionView CreateView()         {             return new CustomCollectionView<T>(this);         }     } 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章