将 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
。如果我设置DataGridCell
的Height
或Grid
的Width
这是单元格的内容,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>
首先,我尝试绑定一个转换器,向其传递内容并计算宽度并返回到Grid
的Width
属性。这会导致在添加新数据时出现闪烁,并且会增加 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
。单元格的宽度不会根据内容而增加,这将导致仅显示部分数据。
第三,我尝试将一个属性绑定到DataGridCell
的Height
,该属性会以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 内容时的性能问题的主要内容,如果未能解决你的问题,请参考以下文章