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);
        }
    }
posted @ 2013-10-08 14:47  muzizongheng  阅读(1966)  评论(0编辑  收藏  举报
如果我们时时忙着展现自己的知识, 将何从忆起成长所需的无知?