繁体   English   中英

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

[英]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类,该类派生Canvas具有与数据网格绑定的属性
  • 附加了SelectionChanged以侦听任何更改,并请求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<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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM