WPF DataGrid 和 ScrollViewer 导致性能变慢

8

我有一个数据表格的样式:

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type DataGrid}">
            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
                <ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
                    <ScrollViewer.Template>
                        <ControlTemplate TargetType="{x:Type ScrollViewer}">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="*"/>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>

                                <DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter"
                                                                Grid.Column="1"
                                                                Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>

                                <ScrollContentPresenter x:Name="PART_ScrollContentPresenter"
                                                        CanContentScroll="{TemplateBinding CanContentScroll}"
                                                        Grid.ColumnSpan="2"
                                                        Grid.Row="1" />

                                <ScrollBar x:Name="PART_VerticalScrollBar"
                                           Grid.Column="2"
                                           Maximum="{TemplateBinding ScrollableHeight}"
                                           Orientation="Vertical"
                                           Grid.Row="1"
                                           Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
                                           ViewportSize="{TemplateBinding ViewportHeight}"/>

                                <Grid Grid.Column="1" Grid.Row="2">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                        <ColumnDefinition Width="*"/>
                                    </Grid.ColumnDefinitions>

                                    <ScrollBar x:Name="PART_HorizontalScrollBar"
                                               Grid.Column="1"
                                               Maximum="{TemplateBinding ScrollableWidth}"
                                               Orientation="Horizontal"
                                               Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}"/>
                                </Grid>
                            </Grid>
                        </ControlTemplate>
                    </ScrollViewer.Template>

                    <Grid>
                        <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                        Grid.Row="0" />

                        <Canvas Width="128"
                                VerticalAlignment="Stretch"
                                HorizontalAlignment="Left"
                                Grid.Row="0"
                                x:Name="Image" />
                    </Grid>
                </ScrollViewer>
            </Border>
        </ControlTemplate>
    </Setter.Value>
</Setter>

我知道如果在数据表格上加载大量数据,性能会受到影响。我可以使用虚拟化来减轻这种性能损失,但是,一旦将网格放入自定义滚动查看器中,虚拟化就会丢失。
我正在尝试恢复它,但是我不确定如何做到这一点,同时仍保留XAML中命名为“Image”的元素。
基本上,我想让一个图像与数据表格内容一起滚动,上面的代码很好用,只是我不知道如何启用虚拟化。这可能有可能吗?
更新:看起来我找到了一个问题。模板中的最后一个Grid会导致问题:
<Grid>
    <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                    Grid.Row="0" />

    <Canvas Width="128"
            VerticalAlignment="Stretch"
            HorizontalAlignment="Left"
            Grid.Row="0"
            x:Name="Image" />
</Grid>

只要我将CanvasGrid移除,只保留ItemsPresenter,那么速度就会变快。如何保持Canvas的同时使它更快呢?
更新2:我该如何将这个策略应用于上面显示的Grid?我尝试了这个方法:(ScrollViewer slow perfomance with DataGrid)
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Rectangle Name="sizingElement" Grid.Row="0" Fill="Transparent" Margin="1"/>

    <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                    Grid.Row="0"
                    Height="{Binding ElementName=sizingElement, Path=ActualHeight, FallbackValue=1}" />

    <Canvas Width="128"
            VerticalAlignment="Stretch"
            HorizontalAlignment="Left"
            Grid.Row="0"
            x:Name="Image" />
</Grid>

然而,现在滚动条消失了?

我意识到我不能虚拟化一个Canvas,也不需要这样做。实际上,整个Canvas都被绘制出来,我没有将其分割成更小的部分的逻辑。只要我能保持行虚拟化,将图像完整地渲染出来就完全没问题。


1
我记得关于DataGrid中的ScrollViewer会破坏DataGrid虚拟化,但我找不到相关信息。它是只读的吗?如果是,那么尝试使用GridView - 它更快。 - paparazzo
@Blam 如果我删除额外的Canvas元素,我对DataGrid没有任何问题。因此,这不是DataGrid本身的问题,实际上即使在这种情况下,ListView也很慢。 - Tower
@Blam显然,滚动查看器会对虚拟化造成问题,因为它要求子元素占满整个高度,以便查看器可以测量其大小并相应地显示滚动条,但一旦子元素(数据表格)占满整个高度,虚拟化就会消失。 - Tower
@Meleak 也许我可以在另一个滚动查看器中创建画布,并将滚动位置与数据网格滚动查看器的位置同步? - Tower
Canvas 究竟是用来做什么的?你能否使用 adorners 替代它?每个 ScrollViewer 都会隐式创建一个 AdornerLayer,因此在 DataGrid 的可滚动区域内定位 adorners 应该很容易,无需自定义模板。 - Mike Strobel
显示剩余3条评论
2个回答

2

我已经成功为基于TreeView(.Net 4.0)的自定义控件实现了虚拟化。我稍微修改了一下样式,以匹配DataGrid,希望它对你的情况起作用:

  <Style TargetType="{x:Type DataGrid}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True" />
<Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="ItemsPanel">
  <Setter.Value>
    <ItemsPanelTemplate>
      <VirtualizingStackPanel IsItemsHost="True" VirtualizingStackPanel.IsVirtualizing="True"
                              VirtualizingStackPanel.VirtualizationMode="Recycling" />
    </ItemsPanelTemplate>
  </Setter.Value>
</Setter>
<Setter Property="Template">
  <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGrid}">
      <Border x:Name="Border" Grid.Column="0" Background="{TemplateBinding Background}"
              BorderBrush="{StaticResource SolidBorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2">
        <DockPanel>

            <ScrollViewer x:Name="PART_Body_Scroll" Background="White" HorizontalScrollBarVisibility="Auto"
                        VerticalScrollBarVisibility="Auto" CanContentScroll="True">

              <ItemsPresenter x:Name="ItemsHost" VirtualizingStackPanel.IsVirtualizing="True"
                            VirtualizingStackPanel.VirtualizationMode="Recycling" />

          </ScrollViewer>

        </DockPanel>
      </Border>
    </ControlTemplate>
  </Setter.Value>
</Setter>


1
问题在于虚拟化仅在 ScrollViewer 的内容支持 IScrollInfo / VirtualizingPanel 时才起作用。
据我所见,您想要将项目与画布放在下面 - 全部位于滚动区域内。您真的想要一种特殊类型的行吗?如果是这样,您可以采用这种方式并插入一个特殊行。 或者您可以将其移出数据网格 - 或尝试使用 RowDetails - 我知道它不是相同的外观 - 但更容易使用。
要使虚拟化与您的画布正常工作,必须将其放置在虚拟化面板中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接