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

你可能感兴趣的:(timer,datagrid,filter,performance,WPF)