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);
}
}
~~~
- 前言
- win32與WPF的混合編程
- WPF: 一個可以用StoryBoard動態改變Grid行寬/列高的類
- MFC中調用WPF教程
- Expression Blend操作: 使用behavior來控制Storyboard
- WPF DatePicker 的textbox的焦點
- WPF 使用MultiBinding ,TwoWay ,ValidationRule ,需要注意的事項
- WPF TreeView 后臺C#選中指定的Item, 需要遍歷
- WPF GridViewColumn Sort DataTemplate
- DataGridColum的bug
- WPF Get Multibinding Expression, Update Source,
- WPF 后臺觸發 Validate UI‘s Element
- WPF ValidationRule 觸發ErrorTemplate 的注意事項
- WPF DelegateCommand CanExecute
- WPF TextBox PreviewTextInput handle IME (chinese)
- No overload for &#39;OnStartup&#39; matches delegate &#39;System.Windows.StartupEventHandler&#39;
- WPF error: does not contain a static &#39;Main&#39; method suitable for an entry point
- WPF GridView中的CellTemplate失效的原因
- DataGrid 顯示選中的item
- 如何得到WPF中控件綁定的EventTrigger
- 選中DataGrid的Cell而不是row
- ContextMenu的自定義
- 輸入框只能輸入英文
- TextBox的OnTextboxChanged事件里對Text重新賦值帶中文, 導致崩潰
- DataGrid當列寬超出當前寬度時,沒有數據也恒有滾動條
- wpf如何獲取control template里的元素
- Set connectionId threw an exception.
- WPF中Visible設為Collapse時,VisualTreeHelper.GetChildrenCount為0
- XAML 編碼規范 (思考)
- 如何為現有控件的DependencyProperty添加Value Changed事件?
- TreeView滾動TreeViewItem
- 為BindingList添加Sort
- WPF Background的設置有坑
- 自定義Panel中添加依賴屬性需要注意的問題
- TextBlock截斷字符顯示為....
- DataGrid 支持字符截斷顯示
- TreeView控件實踐
- WPF如何更改系統控件的默認高亮顏色 (Highlight brush)
- ViewModel中C# Property自動添加OnPropertyChanged處理的小工具, 以及相應Python知識點
- WPF中Xaml編譯正常而Designer Time時出錯的解決辦法
- 關于Snoop的用法
- wpf中為DataGrid添加checkbox支持多選全選
- WPF中DataGrid控件的過濾(Filter)性能分析及優化
- wpf控件提示Value ‘’ can not convert
- DropShadowEffect導致下拉框控件抖動
- 再論WPF中的UseLayoutRounding和SnapsToDevicePixels
- WPF案例:如何設計歷史記錄查看UI
- WPF案例:如何設計搜索框(自定義控件的原則和方法)
- WPF基本概念入門
- WPF開發中Designer和碼農之間的合作
- 聊聊WPF中的Dispatcher
- 聊聊WPF中字體的設置
- Bug:DataGridCell的顯示不完整
- WPF中ToolTip的自定義
- WPF中ItemsControl綁定到Google ProtocolBuffer的結構體時的性能問題
- TreeView的性能問題
- Xaml中string(字符串)常量的定義以及空格的處理
- 依賴屬性
- WPF中的CheckBox的_ (underscore / 下劃線)丟失
- WPF錯誤:必須使“Property”具有非 null 值。
- WPF中ItemsControl應用虛擬化時找到子元素的方法
- WPF毫秒級桌面時鐘的實現-C#中Hook(鉤子)的應用
- KB2464222導致IsNonIdempotentProperty方法找不見
- WPF中PreviewMouseDownEvent的系統處理:TabItem的PreviewMouseDown 事件彈框后不切換的問題調查
- WPF文字渲染相關的問題及解決
- wpf中的默認右鍵菜單中的復制、粘貼、剪貼等沒有本地化的解決方案
- WPF內部DeliverEvent讀鎖和PrivateAddListener寫鎖導致死鎖
- Windbg調試WPF的依賴屬性
- WPF 后臺Render線程崩潰, Exception from HRESULT: 0x88980406
- WPF中DependencyObject與DependencyProperty的源碼簡單剖析
- 禁用WPF中DataGrid默認的鼠標左鍵拖動多選行的效果
- wpf工程中在Xaml文件下添加多個cs文件
- ScrollViewer滾動到底來觸發加載數據的Behavior