zoukankan      html  css  js  c++  java
  • 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);
            }
        }
    
  • 相关阅读:
    USACO Grass Planting
    洛谷 P3178 [HAOI2015]树上操作
    史上最全NOIP初赛知识点
    史上最全的CSP-J/S 第一轮知识点
    洛谷 P1886 滑动窗口
    背包九讲—简单背包
    NOIP 2005 采药
    洛谷 P2357 守墓人
    NOI 2015 软件包管理器
    洛谷 P3384 【模板】树链剖分
  • 原文地址:https://www.cnblogs.com/muzizongheng/p/3357082.html
Copyright © 2011-2022 走看看