如何加快垂直滚动条标记的渲染速度

我有一个自定义的垂直滚动条,显示DataGrid中所选项目的标记。

在此处输入图像描述

我面临的问题是,当存在大量项目(例如可能是5000到50000)时,在渲染标记时存在滞后。

使用以下代码,它基本上按照所选项目索引,项目数和轨道高度进行渲染。 显然这是低效的,我正在寻找其他解决方案。

这是我自定义的垂直滚动条

                                                                        

这是我的转换器,如果DataGrid高度发生变化,它会转换Y位置并相应地缩放。

 public class MarkerPositionConverter: IMultiValueConverter { //Performs the index to translate conversion public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { try { //calculated the transform values based on the following object o = (object)values[0]; DataGrid dg = (DataGrid)values[1]; double itemIndex = dg.Items.IndexOf(o); double trackHeight = (double)values[2]; int itemCount = (int)values[3]; double translateDelta = trackHeight / itemCount; return itemIndex * translateDelta; } catch (Exception ex) { Console.WriteLine("MarkerPositionConverter error : " + ex.Message); return false; } } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } 

[RE-EDIT]我试图为标记canvas创建一个单独的类,用于ObservableCollection。 请注意,目前,这不起作用。

XAML仍然和昨天一样:

  

Canvas类,ObservableCollection改为使用对象而不是double,在MarkerCollectionCanvas_CollectionChanged中有一个永远不会被调用的console.writeline:

 class MarkerCollectionCanvas : Canvas { public DataGrid Grid { get { return (DataGrid)GetValue(GridProperty); } set { SetValue(GridProperty, value); } } public static readonly DependencyProperty GridProperty = DependencyProperty.Register("Grid", typeof(DataGrid), typeof(MarkerCollectionCanvas), new PropertyMetadata(null)); public ObservableCollection MarkerCollection { get { return (ObservableCollection)GetValue(MarkerCollectionProperty); } set { SetValue(MarkerCollectionProperty, value); } } public static readonly DependencyProperty MarkerCollectionProperty = DependencyProperty.Register("MarkerCollection", typeof(ObservableCollection), typeof(MarkerCollectionCanvas), new PropertyMetadata(null, OnCollectionChanged)); private static void OnCollectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { MarkerCollectionCanvas canvas = d as MarkerCollectionCanvas; if (e.NewValue != null) { (e.NewValue as ObservableCollection).CollectionChanged += canvas.MarkerCollectionCanvas_CollectionChanged; } if (e.OldValue != null) { (e.NewValue as ObservableCollection).CollectionChanged -= canvas.MarkerCollectionCanvas_CollectionChanged; } } void MarkerCollectionCanvas_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { Console.WriteLine("InvalidateVisual"); InvalidateVisual(); } public Brush MarkerBrush { get { return (Brush)GetValue(MarkerBrushProperty); } set { SetValue(MarkerBrushProperty, value); } } public static readonly DependencyProperty MarkerBrushProperty = DependencyProperty.Register("MarkerBrush", typeof(Brush), typeof(MarkerCollectionCanvas), new PropertyMetadata(Brushes.DarkOrange)); protected override void OnRender(System.Windows.Media.DrawingContext dc) { base.OnRender(dc); if (Grid == null || MarkerCollection == null) return; //Get all items object[] items = new object[Grid.Items.Count]; Grid.Items.CopyTo(items, 0); //Get all selected items object[] selection = new object[MarkerCollection.Count]; MarkerCollection.CopyTo(selection, 0); Dictionary indexes = new Dictionary(); for (int i = 0; i < selection.Length; i++) { indexes.Add(selection[i], 0); } int itemCounter = 0; for (int i = 0; i = selection.Length) break; } double translateDelta = ActualHeight / (double)items.Length; double width = ActualWidth; double height = Math.Max(translateDelta, 4); Brush dBrush = MarkerBrush; double previous = 0; IEnumerable sortedIndex = indexes.Values.OrderBy(v => v); foreach (int itemIndex in sortedIndex) { double top = itemIndex * translateDelta; if (top < previous) continue; dc.DrawRectangle(dBrush, null, new Rect(0, top, width, height)); previous = (top + height) - 1; } } } 

这是我的单一类,里面有SearchMarkers:

 public class MyClass : INotifyPropertyChanged { public static ObservableCollection m_searchMarkers = new ObservableCollection(); public ObservableCollection SearchMarkers { get { return m_searchMarkers; } set { m_searchMarkers = value; NotifyPropertyChanged(); } } private static MyClass m_Instance; public static MyClass Instance { get { if (m_Instance == null) { m_Instance = new MyClass(); } return m_Instance; } } private MyClass() { } public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } 

这是一个文本框文本改变的行为。 这是ObservableCollection SearchMarkers的填充位置。

 public class FindTextChangedBehavior : Behavior { protected override void OnAttached() { base.OnAttached(); AssociatedObject.TextChanged += OnTextChanged; } protected override void OnDetaching() { AssociatedObject.TextChanged -= OnTextChanged; base.OnDetaching(); } private void OnTextChanged(object sender, TextChangedEventArgs args) { var textBox = (sender as TextBox); if (textBox != null) { DataGrid dg = DataGridObject as DataGrid; string searchValue = textBox.Text; if (dg.Items.Count > 0) { var columnBoundProperties = new List<KeyValuePair>(); IEnumerable visibleColumns = dg.Columns.Where(c => c.Visibility == System.Windows.Visibility.Visible); foreach (var col in visibleColumns) { if (col is DataGridTextColumn) { var binding = (col as DataGridBoundColumn).Binding as Binding; columnBoundProperties.Add(new KeyValuePair(col.DisplayIndex, binding.Path.Path)); } else if (col is DataGridComboBoxColumn) { DataGridComboBoxColumn dgcbc = (DataGridComboBoxColumn)col; var binding = dgcbc.SelectedItemBinding as Binding; columnBoundProperties.Add(new KeyValuePair(col.DisplayIndex, binding.Path.Path)); } } Type itemType = dg.Items[0].GetType(); if (columnBoundProperties.Count > 0) { ObservableCollection tempItems = new ObservableCollection(); var itemsSource = dg.Items as IEnumerable; Task.Factory.StartNew(() => { ClassPropTextSearch.init(itemType, columnBoundProperties); if (itemsSource != null) { foreach (object o in itemsSource) { if (ClassPropTextSearch.Match(o, searchValue)) { tempItems.Add(o); } } } }) .ContinueWith(t => { Application.Current.Dispatcher.Invoke(new Action(() => MyClass.Instance.SearchMarkers = tempItems)); }); } } } } public static readonly DependencyProperty DataGridObjectProperty = DependencyProperty.RegisterAttached("DataGridObject", typeof(DataGrid), typeof(FindTextChangedBehavior), new UIPropertyMetadata(null)); public object DataGridObject { get { return (object)GetValue(DataGridObjectProperty); } set { SetValue(DataGridObjectProperty, value); } } } 

你走了,我试着为你尝试。

  • 创建了一个MarkerCanvas类,派生Canvas,其属性与数据网格绑定
  • 附加SelectionChanged以侦听任何更改并请求canvas通过InvalidateVisual重绘自身
  • 覆盖OnRender的方法来控制绘图并进行必要的检查和计算
  • 最后使用给定的画笔在计算的坐标上渲染矩形

MarkerCanvas类

 class MarkerCanvas : Canvas { public DataGrid Grid { get { return (DataGrid)GetValue(GridProperty); } set { SetValue(GridProperty, value); } } // Using a DependencyProperty as the backing store for Grid. This enables animation, styling, binding, etc... public static readonly DependencyProperty GridProperty = DependencyProperty.Register("Grid", typeof(DataGrid), typeof(MarkerCanvas), new PropertyMetadata(null, OnGridChanged)); private static void OnGridChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { MarkerCanvas canvas = d as MarkerCanvas; if (e.NewValue != null) { (e.NewValue as DataGrid).SelectionChanged += canvas.MarkerCanvas_SelectionChanged; } if (e.OldValue != null) { (e.NewValue as DataGrid).SelectionChanged -= canvas.MarkerCanvas_SelectionChanged; } } void MarkerCanvas_SelectionChanged(object sender, SelectionChangedEventArgs e) { InvalidateVisual(); } public Brush MarkerBrush { get { return (Brush)GetValue(MarkerBrushProperty); } set { SetValue(MarkerBrushProperty, value); } } // Using a DependencyProperty as the backing store for MarkerBrush. This enables animation, styling, binding, etc... public static readonly DependencyProperty MarkerBrushProperty = DependencyProperty.Register("MarkerBrush", typeof(Brush), typeof(MarkerCanvas), new PropertyMetadata(Brushes.SlateGray)); protected override void OnRender(System.Windows.Media.DrawingContext dc) { base.OnRender(dc); if (Grid==null || Grid.SelectedItems == null) return; object[] markers = Grid.SelectedItems.OfType().ToArray(); double translateDelta = ActualHeight / (double)Grid.Items.Count; double width = ActualWidth; double height = Math.Max(translateDelta, 4); Brush dBrush = MarkerBrush; for (int i = 0; i < markers.Length; i++) { double itemIndex = Grid.Items.IndexOf(markers[i]); double top = itemIndex * translateDelta; dc.DrawRectangle(dBrush, null, new Rect(0, top, width, height)); } } } 

我也调整了标记的高度,以便在项目较少时增长,您可以根据需要选择将其修复为特定值

在XAML中,使用绑定到网格的新标记canvas替换项控件

  

helper:指的是我创建类的WpfAppDataGrid.Helpers,你可以选择自己的命名空间

你也可以将MarkerBrush属性绑定到你想要的effet,它默认为SlateGray

渲染现在非常快,也许可以通过对indexof方法做一些工作来使其更快。

另外,要跳过一些要渲染的重叠矩形,您可以更改此方法。 little buggy as of now

 protected override void OnRender(System.Windows.Media.DrawingContext dc) { base.OnRender(dc); if (Grid==null || Grid.SelectedItems == null) return; object[] markers = Grid.SelectedItems.OfType().ToArray(); double translateDelta = ActualHeight / (double)Grid.Items.Count; double width = ActualWidth; double height = Math.Max(translateDelta, 4); Brush dBrush = MarkerBrush; double previous = 0; for (int i = 0; i < markers.Length; i++) { double itemIndex = Grid.Items.IndexOf(markers[i]); double top = itemIndex * translateDelta; if (top < previous) continue; dc.DrawRectangle(dBrush, null, new Rect(0, top, width, height)); previous = (top + height) - 1; } } 

性能优化

我尝试使用稍微不同的方法来优化性能,特别是对于全选按钮

 protected override void OnRender(System.Windows.Media.DrawingContext dc) { base.OnRender(dc); if (Grid == null || Grid.SelectedItems == null) return; object[] items = new object[Grid.Items.Count]; Grid.Items.CopyTo(items, 0); object[] selection = new object[Grid.SelectedItems.Count]; Grid.SelectedItems.CopyTo(selection, 0); Dictionary indexes = new Dictionary(); for (int i = 0; i < selection.Length; i++) { indexes.Add(selection[i], 0); } int itemCounter = 0; for (int i = 0; i < items.Length; i++) { object item = items[i]; if (indexes.ContainsKey(item)) { indexes[item] = i; itemCounter++; } if (itemCounter >= selection.Length) break; } double translateDelta = ActualHeight / (double)items.Length; double width = ActualWidth; double height = Math.Max(translateDelta, 4); Brush dBrush = MarkerBrush; double previous = 0; IEnumerable sortedIndex = indexes.Values.OrderBy(v => v); foreach (int itemIndex in sortedIndex) { double top = itemIndex * translateDelta; if (top < previous) continue; dc.DrawRectangle(dBrush, null, new Rect(0, top, width, height)); previous = (top + height) - 1; } } 

反思方法

在这里我试图获取基础选择列表并尝试从相同的选择索引中检索所选索引,并在执行全部选择时添加更多优化

 protected override void OnRender(System.Windows.Media.DrawingContext dc) { base.OnRender(dc); if (Grid == null || Grid.SelectedItems == null) return; List indexes = new List(); double translateDelta = ActualHeight / (double)Grid.Items.Count; double height = Math.Max(translateDelta, 4); int itemInOneRect = (int)Math.Floor(height / translateDelta); itemInOneRect -= (int)(itemInOneRect * 0.2); if (Grid.SelectedItems.Count == Grid.Items.Count) { for (int i = 0; i < Grid.Items.Count; i += itemInOneRect) { indexes.Add(i); } } else { FieldInfo fi = Grid.GetType().GetField("_selectedItems", BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance); IEnumerable internalSelectionList = fi.GetValue(Grid) as IEnumerable; PropertyInfo pi = null; int lastIndex = int.MinValue; foreach (var item in internalSelectionList) { if (pi == null) { pi = item.GetType().GetProperty("Index", BindingFlags.Instance | BindingFlags.NonPublic); } int newIndex = (int)pi.GetValue(item); if (newIndex > (lastIndex + itemInOneRect)) { indexes.Add(newIndex); lastIndex = newIndex; } } indexes.Sort(); } double width = ActualWidth; Brush dBrush = MarkerBrush; foreach (int itemIndex in indexes) { double top = itemIndex * translateDelta; dc.DrawRectangle(dBrush, null, new Rect(0, top, width, height)); } }