将 LayoutTransform 应用于 DataGridCell 内容时的性能问题

Posted

技术标签:

【中文标题】将 LayoutTransform 应用于 DataGridCell 内容时的性能问题【英文标题】:Performance issue while LayoutTransform is applied to DataGridCell content 【发布时间】:2016-04-24 23:19:09 【问题描述】:

我有一个自定义数据网格,其单元格的样式如下

<Style x:Key="CellStyleBase"
           TargetType="x:Type DataGridCell">
        <Setter Property="Visibility"
                Value="Visible" />
        <Setter Property="Background"
                Value="Binding RelativeSource=RelativeSource Self, Path=Column.Header.CellBackground" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="x:Type DataGridCell">
                    <Grid x:Name="BackgroundGrid"
                          Background="TemplateBinding Background">
                        <TextBlock Text="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Content.Text"
                                   HorizontalAlignment="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Column.Header.CellHorzontalAlignment"
                                   VerticalAlignment="Center"
                                   FontWeight="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Column.Header.CellFontWeight"
                                   Margin="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Content.Margin"
                                   Padding="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Content.Padding" />
                        <Grid.LayoutTransform>
                            <TransformGroup>
                                <RotateTransform Angle="-90" />
                            </TransformGroup>
                        </Grid.LayoutTransform>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

数据网格的样式如下

<Style TargetType="x:Type local:CustomDataGrid">
        <Setter Property="BorderThickness"
                Value="1" />
        <!-- This is needed to force DG to have a non-default value.  Otherwise the DGR.DetailsVisibility cannot have a value of VisibleWhenSelected by default. -->
        <Setter Property="RowDetailsVisibilityMode"
                Value="VisibleWhenSelected" />
        <Setter Property="ScrollViewer.CanContentScroll"
                Value="true" />
        <Setter Property="VirtualizingPanel.IsVirtualizing"
                Value="True" />
        <Setter Property="VirtualizingPanel.VirtualizationMode"
                Value="Recycling" />
        <Setter Property="ScrollViewer.IsDeferredScrollingEnabled"
                Value="True" />
        <Setter Property="EnableColumnVirtualization"
                Value="True" />
        <Setter Property="EnableRowVirtualization"
                Value="True" />
        <Setter Property="LayoutTransform">
            <Setter.Value>
                <TransformGroup>
                    <RotateTransform Angle="90" />
                </TransformGroup>
            </Setter.Value>
        </Setter>
        <Setter Property="CellStyle"
                Value="StaticResource CellStyleBase" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="x:Type local:CustomDataGrid">
                    <Border Background="TemplateBinding Background"
                            BorderBrush="TemplateBinding BorderBrush"
                            BorderThickness="TemplateBinding BorderThickness"
                            SnapsToDevicePixels="True"
                            Padding="TemplateBinding Padding">
                        <ScrollViewer Focusable="false"
                                      Name="DG_ScrollViewer">
                            <ScrollViewer.Template>
                                <ControlTemplate TargetType="x:Type ScrollViewer">
                                    <Grid>
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="Auto" />
                                            <RowDefinition Height="Auto" />
                                            <RowDefinition Height="*" />
                                            <RowDefinition Height="Auto" />
                                        </Grid.RowDefinitions>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="Auto" />
                                            <ColumnDefinition Width="*" />
                                            <ColumnDefinition Width="Auto" />
                                        </Grid.ColumnDefinitions>

                                        <!--Left Column Header Corner -->
                                        <Border BorderBrush="Binding RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type local:CustomDataGrid, Path=HeaderBorderBrush"
                                                BorderThickness="0,0,1,0"
                                                Background="Binding RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type local:CustomDataGrid, Path=HeaderBackground"
                                                Width="Binding RelativeSource=RelativeSource AncestorType=x:Type DataGrid, Path=CellsPanelHorizontalOffset"
                                                Visibility="Collapsed" />
                                        <!--Column Headers-->
                                        <DataGridColumnHeadersPresenter Grid.Column="1"
                                                                        Name="PART_ColumnHeadersPresenter"
                                                                        Visibility="Visible">
                                            <DataGridColumnHeadersPresenter.Style>
                                                <Style TargetType="x:Type DataGridColumnHeadersPresenter">
                                                    <Setter Property="Template">
                                                        <Setter.Value>
                                                            <ControlTemplate TargetType="x:Type DataGridColumnHeadersPresenter">
                                                                <Border Background="Binding RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type local:CustomDataGrid, Path=NameHeaderBackground">
                                                                    <ItemsPresenter />
                                                                </Border>
                                                            </ControlTemplate>
                                                        </Setter.Value>
                                                    </Setter>
                                                </Style>
                                            </DataGridColumnHeadersPresenter.Style>
                                        </DataGridColumnHeadersPresenter>

                                        <!--Column Header Splitter-->
                                        <GridSplitter Grid.Row="1"
                                                      Grid.Column="0"
                                                      Grid.ColumnSpan="2"
                                                      Height="4"
                                                      HorizontalAlignment="Stretch"
                                                      Background="Binding RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type local:CustomDataGrid, Path=NameHeaderBackground"
                                                      Foreground="Transparent"
                                                      Cursor="SizeWE" />

                                        <!-- Line separates the column header with the content-->
                                        <Canvas Grid.Row="1"
                                                Grid.Column="0"
                                                Grid.ColumnSpan="2"
                                                Height="1.5"
                                                HorizontalAlignment="Stretch"
                                                VerticalAlignment="Bottom"
                                                Background="Binding RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type local:CustomDataGrid, Path=HorizontalGridLinesBrush" />


                                        <!--DataGrid content-->
                                        <ScrollContentPresenter x:Name="PART_ScrollContentPresenter"
                                                                Grid.Row="2"
                                                                Grid.ColumnSpan="2"
                                                                CanContentScroll="TemplateBinding CanContentScroll" />

                                        <ScrollBar Grid.Row="0"
                                                   Grid.RowSpan="3"
                                                   Grid.Column="2"
                                                   Name="PART_VerticalScrollBar"
                                                   Orientation="Vertical"
                                                   Maximum="TemplateBinding ScrollableHeight"
                                                   ViewportSize="TemplateBinding ViewportHeight"
                                                   Value="Binding Path=VerticalOffset, RelativeSource=RelativeSource TemplatedParent, Mode=OneWay"
                                                   Visibility="TemplateBinding ComputedVerticalScrollBarVisibility"
                                                   Style="StaticResource ScrollBarStyle" />

                                        <Grid Grid.Row="3"
                                              Grid.Column="1">
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="Binding RelativeSource=RelativeSource AncestorType=x:Type DataGrid, Path=NonFrozenColumnsViewportHorizontalOffset" />
                                                <ColumnDefinition Width="*" />
                                            </Grid.ColumnDefinitions>
                                            <ScrollBar Grid.Column="1"
                                                       Name="PART_HorizontalScrollBar"
                                                       Orientation="Horizontal"
                                                       Maximum="TemplateBinding ScrollableWidth"
                                                       ViewportSize="TemplateBinding ViewportWidth"
                                                       Value="Binding Path=HorizontalOffset, RelativeSource=RelativeSource TemplatedParent, Mode=OneWay"
                                                       Visibility="TemplateBinding ComputedHorizontalScrollBarVisibility"
                                                       Style="StaticResource ScrollBarStyle" />
                                        </Grid>
                                    </Grid>
                                </ControlTemplate>
                            </ScrollViewer.Template>
                            <ItemsPresenter SnapsToDevicePixels="TemplateBinding SnapsToDevicePixels" />
                        </ScrollViewer>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger SourceName="DG_ScrollViewer"
                                 Property="ComputedVerticalScrollBarVisibility"
                                 Value="Visible">
                            <Setter Property="IsShowingHorizontalScrollBar"
                                    Value="True" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

在数据网格中,行将使用后台线程动态添加,它可以包含数千个数据。数据网格存在性能问题。 CPU 使用率不断增加。

我发现原因是LayoutTransform。如果我设置DataGridCellHeightGridWidth 这是单元格的内容,CPU 使用率会降低。但我不能硬编码。应根据内容的长度设置宽度。

设置硬编码高度:

<Setter Property="Height"
            Value="50" />

设置硬编码宽度:

<Grid x:Name="BackgroundGrid"
    Background="TemplateBinding Background"
    Width="50">
    <TextBlock Text="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Content.Text"
        HorizontalAlignment="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Column.Header.CellHorzontalAlignment"
        VerticalAlignment="Center"
        FontWeight="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Column.Header.CellFontWeight"
        Margin="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Content.Margin"
        Padding="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Content.Padding" />
    <Grid.LayoutTransform>
        <TransformGroup>
            <RotateTransform Angle="-90" />
        </TransformGroup>
    </Grid.LayoutTransform>
</Grid>

首先,我尝试绑定一个转换器,向其传递内容并计算宽度并返回到GridWidth 属性。这会导致在添加新数据时出现闪烁,并且会增加 CPU 使用率。

<Grid x:Name="BackgroundGrid"
    Background="TemplateBinding Background"
    Width="Binding Path=Content.Text,RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Converter=StaticResource WidthConverter">
    <TextBlock Text="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Content.Text"
        HorizontalAlignment="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Column.Header.CellHorzontalAlignment"
        VerticalAlignment="Center"
        FontWeight="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Column.Header.CellFontWeight"
        Margin="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Content.Margin"
        Padding="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type DataGridCell,Path=Content.Padding" />
    <Grid.LayoutTransform>
        <TransformGroup>
            <RotateTransform Angle="-90" />
        </TransformGroup>
    </Grid.LayoutTransform>
</Grid>

其次,我将LayoutTransform 替换为RenderTransform。单元格的宽度不会根据内容而增加,这将导致仅显示部分数据。

第三,我尝试将一个属性绑定到DataGridCellHeight,该属性会以DataGridCell 中值的最高宽度动态更新。这也会消耗 CPU 使用率。

除了RenderTransform之外,还有没有其他方法可以达到LayoutTransform的结果?

期待任何形式的意见/建议。谢谢。

【问题讨论】:

你的单元格文本是 90 度的吗?如果是这样,为什么不对整个网格而不是每个单独的单元格应用一个变换? @Chris W:感谢您的回复。如果您查看数据网格的样式,您会发现它也发生了变化。只有当我同时转换数据网格和单元格时才能达到要求。 LayoutTransform 导致高 CPU 的原因是因为布局的任何更改都需要整个布局传递 - 所有单元格都必须重新计算它们的宽度和高度。如果您将宽度/高度固定为特定大小,则涉及的计算更少(不必测量内容等)。 DataGrid 实现在布局传递方面非常慢。 RenderTransform 将简单地以不同方式渲染像素,不涉及布局更改,因此性能更好。至于你的实际问题 - 如何实现相同的结果 - 我不知道...... @Marko:感谢您的回复。我也找不到替代解决方案。但是,通过设置高度/宽度可以在一定程度上降低 CPU 使用率。 【参考方案1】:

我会尝试在渲染逻辑本身中进行转换。我自己还没有尝试过,但我认为值得一试,看看这是否会提供与使用 RenderTransform 类似的性能,同时如果您使用 RotatedText 代替 @ 也可以为您提供您正在寻找的结果987654323@ 与LayoutTransform

public class RotatedText : FrameworkElement

    public string Text  get; set; 

    protected override void OnRender(DrawingContext drawingContext)
    
        base.OnRender(drawingContext);

        var ft = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Calibri"), 12, Brushes.Black);

        drawingContext.PushTransform(new RotateTransform(-90, 0, ft.Width));
        drawingContext.DrawText(ft, new Point(0, ft.Width));
        drawingContext.Pop();

        Width = ft.Height;
        Height = ft.Width;
    

【讨论】:

以上是关于将 LayoutTransform 应用于 DataGridCell 内容时的性能问题的主要内容,如果未能解决你的问题,请参考以下文章

高级动画

WPF——点击按钮后此按钮变大换色其它按钮恢复原样

磁带格式

[R]:根据条件行位置将函数应用于列

2021-09-17 WPF上位机 24-动态图形变换

有没有办法将 Spark 数据帧写入 .dat 文件?