9

I'm working on a application where the bulk of my content is presented to users with the built in WPF DataGrid. Here is what my DataGrid looks like. I don't set anything but the RowDefinitions on the parent Grid.

<UserControl xmlns="blah blah blah">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition Height="Auto"/>
     </Grid.RowDefinitions>

  <DataGrid x:Name="dgrMaterialCollection" 
          IsReadOnly="True" 
          ItemsSource="{Binding Path=MyObservableCollection, UpdateSourceTrigger=PropertyChanged}" 
          AutoGenerateColumns="True" 
          AutoGeneratingColumn="dgrMaterialCollection_AutoGeneratingColumn"
          AutoGeneratedColumns="dgrMaterialCollection_AutoGeneratedColumns"
          CanUserResizeColumns="True" 
          CanUserReorderColumns="True"
          CanUserSortColumns="True"
          EnableRowVirtualization="True"
          EnableColumnVirtualization="True"
          VirtualizingPanel.IsVirtualizingWhenGrouping="True"
          VirtualizingPanel.VirtualizationMode="Standard"
          VirtualizingPanel.IsVirtualizing="True"
          SelectionMode="Single"
          SelectionUnit="FullRow"
          ScrollViewer.CanContentScroll="False"
          RowHeaderWidth="0" 
          Grid.Row="0"
          RowHeight="32"
          RowDetailsVisibilityMode="Collapsed">
  <DataGrid.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="DataGridStyle.xaml"/>
      </ResourceDictionary.MergedDictionaries>

      <!-- A few value converters -->
     </ResourceDictionary>
   </DataGrid.Resources>
   <!-- Some Manually Declared Columns -->
  </DataGrid>
  <StackPanel Grid.Row="1">
    <SomeTextBoxes/>
  </StackPanel>
 </Grid>
</UserControl>

When my application displays the DataGrid, there is about a 10 - 20 second delay depending the size of the dataset. During this delay the UI is locked up. Once the data grid is loaded - I can scroll through all few thousand items without any delay. When I say without delay, I mean clicking on the scroll bar and moving it up and down as fast as humanly possible, with no delay in rendering the rows as it scrolls. It seems like the virtualization is not working, and instead the datagrid is creating all the rows at one time instead of while the user is scrolling.

My Datagrid is nested in the following structure (spread out over multiple xaml files). There are no extra ScrollViewers, StackPanels, or anything. Just this.

<RibbonWindow>
  <Grid>
    <ContentControl> <!-- Populated via a DataTemplate and ContentTemplateSelector -->
      <UserControl>
        <UserControl>
          <UserControl>
            <Grid>
              <Here is the DataGrid> 

Some things I have attempted to speed up the loading

  • Explicitly set all the column widths (shaved about 10 second off my load time)
  • Turned off AutoGenerateColumns (didn't make a difference).
  • Disabling the styles set in the DataGrid's RessourceDictionary (no difference)
  • Load items into my ObservableCollection on a background thread, using async/await, and other multithreaded strategies. This has made no difference in load times.
  • Add items to a new ObservableCollection, then setting it to the databound property (so the events aren't triggered)
  • Add items to the ObservableCollection one at a time (events are triggered upon each add)
  • Turning virtualization on and off makes no difference in load time.

I know the delay is not caused by populating the ObservableCollection. It starts after the collection is fully populated, and when the WPF DataGridis rendering all the rows/columns. I have virtualization enabled and it still takes forever. I am using the .NET framework 4.5.1 (so the 4.6 issue with Virtualizationis not causing this). There are also long delays when sorting (using built in datagrid sorting) and resizing columns from the UI.

What am I missing here? Is there something I can do to ensure Virtualizationis working? What are some things that might speed this up? If I know Virtualization is working then I may move onto a new strategy, like paging or some kind of lazy loading. Another solution I would be willing to work with is one where the user interface is not blocked/frozen while all the rows are being rendered. That way the users could at least click around on other thing while rows are added in.

  • 1
    What makes you think virtualization isn't working? Use a profiler and measure your program to see where it's spending its load time. – hoodaticus Jun 27 '17 at 17:39
  • Turing virtualization on and off does not change the load time. I think it is not working because the delay happens after my collection is completely populated and before the datagrid is visible. Once the UI unblocks the datagrid appears will all the rows visible and ready to scroll through. The delay also seems to grow by roughly 5 seconds per 1000 items in the collection the itemsource is bound to. The delay is also happening while code external to the my visual studio project is running (I'm assuming it's WPF code). I can't get into the external code to debug it. –  Jun 27 '17 at 17:48
  • this is going to sound stupid but bear with me - what happens if you move the DataSource setter AFTER the virtualization settings? – hoodaticus Jun 27 '17 at 17:59
  • Does this help `ItemsSource="{Binding Path=MyObservableCollection, IsAsync=True}"` ? – Peter Jun 27 '17 at 18:04
  • Lol. Just tried that, got nothing. I also ran the built in VS profiler (I have to use 2013 Professional). It says 99% of the execution time happens in the System.Windows..Application.RunInternal method, of that it only calls 4 of my functions, each taking up <0.1% of execution time. That part of the code that populated the observable collection is also at <0.1% –  Jun 27 '17 at 18:07
  • Peter, that doesn't make a difference. The `IsAsync` property will only affect how the collection is retrieved (through the `getter` property), not how WPF will act once it has the collection. From what I've read, `IsAsync` will only help if you have a property that does a lot of work when it's `getter` is called. –  Jun 27 '17 at 18:11
  • Use Snoop to verify your control tree, first to ensure virtualizatiin is not working and all items are loaded and second to spot which control up the hierarchy causes that. – Evk Jun 27 '17 at 18:23
  • @Danny just was curious if he uses the resources with the `ObservableCollection` and made a mistake by saying it wasn't. – Peter Jun 27 '17 at 18:29
  • I see you have a Grid.Row specified. What's the parent layout look like? Anything virtualized needs a boundary to invoke virtualization. – Chris W. Jun 27 '17 at 18:33
  • @ChrisW. I'll edit the original post to include the parent. –  Jun 27 '17 at 18:36
  • @Evk Looking at the `DataGridRowsPresenter` through snoop shows it with an `ActualHeight` of 128000 and `RenderSize` of 934,128000. If I expand it, it shows all the rows, and snoop locks up because there are so many. The virtualizing properties of the `DataGrid` are all turned on, but it looks like every row is present in the `DataGrid` and not virtualized. –  Jun 27 '17 at 18:54
  • 1
    Now would be good to look up the hierarchy (important to do that in Snoop) and try to find some control which renders its children with a whole available space (stack panel). – Evk Jun 27 '17 at 18:56
  • Ya @Evk is on the right track I was alluding to. I'd bet beer that if you set a fixed height on your DataGrid it would virtualize. Which means something up the tree isn't providing a boundary to invoke it. – Chris W. Jun 27 '17 at 18:58
  • I set the Height on the `DataGrid` to 500 and still get the same slow behavior. The `ActualHeight` is set to 500 but `RenderSize` doesn't change (952,500). My `DataGrid`, as shown in the original post, is only set in `Grids`, `UserControls`, a `ContentControl`, and the main `RibbonWindow`. I went up the hierarchy and set all the heights to a fixed number and still loads up slowly. –  Jun 27 '17 at 19:08
  • 4
    Hahaha set `ScrollViewer.CanContentScroll="False"` to `True` dude ;) – Chris W. Jun 27 '17 at 19:39
  • Good lord. That did it. Would you post that as an answer so I can give you credit for it? I'd also be interested to know why it has to be set to true. –  Jun 27 '17 at 19:41
  • 1
    Meh, some of these other fellas put more time in than I did I'm sure. There's a more detailed explanation as to why [here](https://stackoverflow.com/questions/3724593/why-setting-scrollviewer-cancontentscroll-to-false-disable-virtualization) but the cliff notes version is, it needs logical units to track (items) instead of pixel based. I just can't believe I skimmed right over that lol. Cheers :) – Chris W. Jun 27 '17 at 19:53
  • Well thanks for everyone's help. If someone else doesn't want to post an answer I'll write one up myself tomorrow. –  Jun 27 '17 at 20:09

1 Answers1

5

The ScrollViewer.CanContentScroll value on the DataGrid element must be set to True in order for row virtualization to work properly. Here is an explanation of why it must be set this way.

Thanks to ChrisW for catching that in the comments on this question.

  • 3
    I think you mean "must be set to **True**"? – Amanduh Aug 13 '18 at 15:30
  • If you have the datagrid within a stackpanel (e.g. to add buttons below the grid) the virtualization gets disabled. The documentation suggests using a VirtualizingStackPanel, I've yet to verify that it works. – Paul McCarthy Mar 05 '20 at 00:30
  • It didn't work but SO has more info here https://stackoverflow.com/questions/52221666/wpf-datagrid-virtualization-getting-disabled-when-placed-in-a-scrollviewer – Paul McCarthy Mar 05 '20 at 00:46