8
votes

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.

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

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

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

2
I was not aware Grid supports virtualization. ¿How are you implementing it?NestorArturo
I meant DataGrid supports virtualization.Tower
I recall something about ScrollViewer in DataGrid breaking the DataGrid virtualization but I cannot find it. Is it read only. If so give GridView a try - it is much faster.paparazzo
@Blam I have no issue with DataGrid if I remove my extra Canvas element. Therefore, it's not a problem with DataGrid per se, and in fact even ListView in this scenario is slow.Tower
@Blam Obviously a scroll viewer causes problems with virtualization because it asks the child to take full height so that the viewer can measure its size and show the scrollbar accordingly, but as soon as the child (data grid) goes full on height, virtualization is gone.Tower

2 Answers

2
votes

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:

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

the problem is that virtualization only works if the contents of the scrollviewer supports 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.

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