[英]How to speed up rendering of vertical scrollbar markers
我有一個自定義的垂直滾動條,它顯示DataGrid中所選項目的標記。
我面臨的問題是,當有大量項目(例如可能為5000至50000)時,在渲染標記時會出現滯后。
使用以下代碼,它基本上根據選定的項目索引,項目數和軌道高度進行渲染。 顯然這是低效的,正在尋找其他解決方案。
這是我自定義的垂直滾動條
<helpers:MarkerPositionConverter x:Key="MarkerPositionConverter"/>
<ControlTemplate x:Key="VerticalScrollBar" TargetType="{x:Type ScrollBar}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="18" />
<RowDefinition Height="0.00001*" />
<RowDefinition MaxHeight="18" />
</Grid.RowDefinitions>
<Border Grid.RowSpan="3"
CornerRadius="2"
Background="#F0F0F0" />
<RepeatButton Grid.Row="0"
Style="{StaticResource ScrollBarLineButton}"
Height="18"
Command="ScrollBar.LineUpCommand"
Content="M 0 4 L 8 4 L 4 0 Z" />
<!--START-->
<ItemsControl VerticalAlignment="Stretch" x:Name="ItemsSelected"
ItemsSource="{Binding ElementName=GenericDataGrid, Path=SelectedItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="SlateGray" Width="9" Height="4">
<Rectangle.RenderTransform>
<TranslateTransform>
<TranslateTransform.Y>
<MultiBinding Converter="{StaticResource MarkerPositionConverter}" FallbackValue="-1000">
<Binding/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}" />
<Binding Path="ActualHeight" ElementName="ItemsSelected"/>
<Binding Path="Items.Count" ElementName="GenericDataGrid"/>
</MultiBinding>
</TranslateTransform.Y>
</TranslateTransform>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas ClipToBounds="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<!--END-->
<Track x:Name="PART_Track" Grid.Row="1" IsDirectionReversed="true">
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}"
Command="ScrollBar.PageUpCommand" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource ScrollBarThumb}" Margin="1,0,1,0">
<Thumb.BorderBrush>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource BorderLightColor}" Offset="0.0" />
<GradientStop Color="{DynamicResource BorderDarkColor}" Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Thumb.BorderBrush>
<Thumb.Background>
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource ControlLightColor}" Offset="0.0" />
<GradientStop Color="{DynamicResource ControlMediumColor}" Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Thumb.Background>
</Thumb>
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageDownCommand" />
</Track.IncreaseRepeatButton>
</Track>
<RepeatButton Grid.Row="3" Style="{StaticResource ScrollBarLineButton}" Height="18" Command="ScrollBar.LineDownCommand" Content="M 0 0 L 4 4 L 8 0 Z" />
</Grid>
</ControlTemplate>
這是我的轉換器,可以轉換Y位置並在DataGrid高度更改時相應地縮放。
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();
}
}
[重新編輯]我試圖為標記畫布創建一個單獨的類,以與ObservableCollection一起使用。 請注意,目前這不起作用。
XAML與昨天相同:
<helpers:MarkerCollectionCanvas
x:Name="SearchMarkerCanvas"
Grid.Row="1"
Grid="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
MarkerCollection="{Binding Source={x:Static helpers:MyClass.Instance}, Path=SearchMarkers}"/>
Canvas類的ObservableCollection更改為使用object而不是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<object> MarkerCollection
{
get { return (ObservableCollection<object>)GetValue(MarkerCollectionProperty); }
set { SetValue(MarkerCollectionProperty, value); }
}
public static readonly DependencyProperty MarkerCollectionProperty =
DependencyProperty.Register("MarkerCollection", typeof(ObservableCollection<object>), 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<object>).CollectionChanged += canvas.MarkerCollectionCanvas_CollectionChanged;
}
if (e.OldValue != null)
{
(e.NewValue as ObservableCollection<object>).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<object, int> indexes = new Dictionary<object, int>();
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<int> 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<object> m_searchMarkers = new ObservableCollection<object>();
public ObservableCollection<object> 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<TextBox>
{
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<int, string>>();
IEnumerable<DataGridColumn> 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<int, string>(col.DisplayIndex, binding.Path.Path));
}
else if (col is DataGridComboBoxColumn)
{
DataGridComboBoxColumn dgcbc = (DataGridComboBoxColumn)col;
var binding = dgcbc.SelectedItemBinding as Binding;
columnBoundProperties.Add(new KeyValuePair<int, string>(col.DisplayIndex, binding.Path.Path));
}
}
Type itemType = dg.Items[0].GetType();
if (columnBoundProperties.Count > 0)
{
ObservableCollection<Object> tempItems = new ObservableCollection<Object>();
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類
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<object>().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中,將您的項目控件替換為具有綁定到網格的新標記畫布
<helpers:MarkerCanvas Grid.Row="1" Grid="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
helpers:指的是WpfAppDataGrid.Helpers,在我創建類的地方,您可以選擇自己的名稱空間
您還可以為所需的效果綁定MarkerBrush屬性,默認為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<object>().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<object, int> indexes = new Dictionary<object, int>();
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<int> 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<int> indexes = new List<int>();
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<object> internalSelectionList = fi.GetValue(Grid) as IEnumerable<object>;
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));
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.