多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
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="{BindingMode=OneWay,Source={StaticResourceStudyListView}}" /> ~~~ 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); } } ~~~