使用WPF DataGrid和ScrollViewer降低性能

时间:2021-02-05 20:22:49

I have this style for a data grid:

我有这种风格的数据网格:

<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>

I know that if you load a lot of data on a data grid, the performance suffers. I can use virtualization to mitigate that performance hit, however, as soon as I throw the grid in a custom scroll viewer, the virtualization is lost.

我知道如果在数据网格上加载大量数据,性能会受到影响。我可以使用虚拟化来降低性能损失,但是,只要我在自定义滚动查看器中抛出网格,虚拟化就会丢失。

I am trying to get it back, but I'm not sure how -- while still retain the element named Image in my XAML.

我试图让它恢复,但我不知道如何 - 同时仍然在我的XAML中保留名为Image的元素。

Basically, I want to have an image scrolling with the data grid contents and the above code works fine, it's just that I don't know how to enable virtualization. Is it even possible?

基本上,我想让图像滚动数据网格内容,上面的代码工作正常,只是我不知道如何启用虚拟化。它甚至可能吗?

Update: Looks like I've found a problem. The last Grid in the template causes a problem:

更新:看起来我发现了一个问题。模板中的最后一个Grid导致问题:

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

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

As soon as I take the Canvas and Grid out, leaving only the ItemsPresenter, then it's fast again. How can I get it fast and still retain this Canvas?

一旦我拿出Canvas和Grid,只留下ItemsPresenter,那么它再次快速。我怎样才能快速得到它并仍然保留这个画布?

Update 2: How can I apply this (ScrollViewer slow perfomance with DataGrid) strategy for my Grid shown above? I tried this:

更新2:如何在上面显示的G​​rid中应用此(ScrollViewer与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>

However, now the scrollbars have disappeared?

但是,现在滚动条已经消失了?

I realize that I can't virtualize a Canvas, and I don't need to. In fact, the whole Canvas gets drawn and I have no logic to separate it into smaller parts. It's completely fine to render the image at its entirety, as long as I can keep row virtualization.

我意识到我不能虚拟化Canvas,我不需要。事实上,整个Canvas被绘制出来了,我没有逻辑把它分成更小的部分。只要我可以保持行虚拟化,完全渲染图像就完全没问题了。

2 个解决方案

#1


2  

I've got virtualization working for my custom control based on TreeView (.Net 4.0). I modified a little the style to match DataGrid, hope it is going to work for your case:

我已经为基于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>

#2


1  

the problem is that virtualization only works if the contents of the scrollviewer supports IScrollInfo / VirtualizingPanel.

问题是只有当scrollviewer的内容支持IScrollInfo / VirtualizingPanel时,虚拟化才有效。

As far as I can see you want to have your items with a canvas with something below it - all inside your scrolling area. Is it actually a special kind of row you want? If so you could go that way and insert a special row instead? Or you could just move that outside the datagrid - or try using RowDetails - it is not the same apperance I know - but far easiere to use.

据我所知,你想让你的物品上面有一个带有下面东西的画布 - 都在滚动区域内。它实际上是你想要的一种特殊行吗?如果是这样你可以这样做并插入一个特殊的行?或者您可以将其移动到数据网格之外 - 或者尝试使用RowDetails - 它不是我所知道的相同外观 - 但是很容易使用。

To get virtualization to work with what you have your canvas have to be inside the virtualizing panel.

要使虚拟化能够与您的画布一起使用,您的画布必须位于虚拟化面板中。

#1


2  

I've got virtualization working for my custom control based on TreeView (.Net 4.0). I modified a little the style to match DataGrid, hope it is going to work for your case:

我已经为基于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>

#2


1  

the problem is that virtualization only works if the contents of the scrollviewer supports IScrollInfo / VirtualizingPanel.

问题是只有当scrollviewer的内容支持IScrollInfo / VirtualizingPanel时,虚拟化才有效。

As far as I can see you want to have your items with a canvas with something below it - all inside your scrolling area. Is it actually a special kind of row you want? If so you could go that way and insert a special row instead? Or you could just move that outside the datagrid - or try using RowDetails - it is not the same apperance I know - but far easiere to use.

据我所知,你想让你的物品上面有一个带有下面东西的画布 - 都在滚动区域内。它实际上是你想要的一种特殊行吗?如果是这样你可以这样做并插入一个特殊的行?或者您可以将其移动到数据网格之外 - 或者尝试使用RowDetails - 它不是我所知道的相同外观 - 但是很容易使用。

To get virtualization to work with what you have your canvas have to be inside the virtualizing panel.

要使虚拟化能够与您的画布一起使用,您的画布必须位于虚拟化面板中。