DataGrid population performance poor with small set of in memory data

Apr 21, 2009 at 7:25 AM
I like the way the user navigates in the grid.

My data isn't XML based or DB based. It's a memory collection.

The population performance of the grid is poor with the relative small set of 300 items.

(Once populated the performance is acceptable to me)

If I remove the scrollviewer the performance is slightly better. My very superflous performance profiling seems to point to the layout code.

Some example:

XAML:

<Window x:Class="DataGridTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
    Title="Window1" MinHeight="300" MinWidth="300">
   <StackPanel>
      <Button Content="Go" Click="Button_Click"/>
      <ScrollViewer VerticalScrollBarVisibility="Auto">
         <Controls:DataGrid Name="Test">
         </Controls:DataGrid>
      </ScrollViewer>
   </StackPanel>
</Window>


C#:

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;

namespace DataGridTest
{
   public partial class Window1
   {
      public Window1()
      {
         InitializeComponent();
      }

      void Button_Click(object sender, RoutedEventArgs e)
      {

         var items = Enumerable
            .Range(0, 400)
            .Select(i => new TestData())
            .ToArray();

         Test.ItemsSource = new ObservableCollection<TestData>(items);

      }
   }

   public class TestData
   {
      static readonly Random s_rand = new Random();

      public string V0 { get; set; }
      public string V1 { get; set; }
      public string V2 { get; set; }
      public string V3 { get; set; }
      public string V4 { get; set; }
      public string V5 { get; set; }
      public string V6 { get; set; }
      public string V7 { get; set; }
      public string V8 { get; set; }
      public string V9 { get; set; }

      public TestData()
      {
         V0 = s_rand.Next(1000, 2000).ToString();
         V1 = s_rand.Next(1000, 2000).ToString();
         V2 = s_rand.Next(1000, 2000).ToString();
         V3 = s_rand.Next(1000, 2000).ToString();
         V4 = s_rand.Next(1000, 2000).ToString();
         V5 = s_rand.Next(1000, 2000).ToString();
         V6 = s_rand.Next(1000, 2000).ToString();
         V7 = s_rand.Next(1000, 2000).ToString();
         V8 = s_rand.Next(1000, 2000).ToString();
         V9 = s_rand.Next(1000, 2000).ToString();
      }
   }
}

Apr 21, 2009 at 7:46 AM
I use changeset: 26281
Apr 21, 2009 at 8:49 AM
If I understand your problem correctly, you may want to try to disable row virtualization and see if that helps. (It will come at a penalty when the grid is initially populated, though.)
Apr 21, 2009 at 8:55 AM
Mikj -

Thanks for your answer. I tried:

         <Controls:DataGrid Name="Test" EnableRowVirtualization="False" />

To populate 400 items it takes roughly 5 seconds on my machine.

My machine is not super hot but definitely not bad.

Mårten

Coordinator
Apr 21, 2009 at 10:03 PM
Hi Mårten,

I am able to repro this on my machine also, and I'm not sure what the problem might be.  We use a lot of samples internally where we generate 1000s of rows similar to the way you're doing it and have not seen performance this bad before.  I'm filing a bug for us to investigate this, and we will get back to you when we're able to figure out what's going on.

Thanks,
Samantha
Apr 21, 2009 at 10:57 PM
Samantha -

Thanks for your answer. I'd really like to use the DataGrid if possible. At the moment I'm using the ListView but I'm not happy about the navigation in the list view and have to emulate the proper behavior. The ListView performance isn't optimal either in my use case but its better.

I would be very happy if this could be resolved so I could use the DataGrid.

PS. I tried to do some basic profiling but as I lack both WPF competence and knowledge of the DataGrid I felt unable to pinpoint the issue myself.

Mårten
Coordinator
May 19, 2009 at 11:48 PM

Don't feel bad. This is a common mistake made with list controls. There are two issues here:

The first is the extra ScrollViewer. The DataGrid's default template already contains a ScrollViewer. So, you can remove that from your markup.

The second is that the parent panel is a StackPanel. The StackPanel (in this case laying out its children vertically) will allow the children to size-to-content in the vertical dimension. That means that the child has all the space it wants and is unconstrained. The child, in this case is DataGrid, and when unconstrained, its layout panel created and layed out ever single item that the DataGrid was bound to. What you want to have happen is for a constraint to be applied to the DataGrid so that its layout panel will only create and layout the items that are in view. To do that, change the StackPanel to a Grid (or some other panel that achieves the same result).

   <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition />
    </Grid.RowDefinitions>
    <Button Content="Go" Click="Button_Click"/>
    <Controls:DataGrid Name="Test" Grid.Row="1">
    </Controls:DataGrid>
  </Grid>

Ben

May 21, 2009 at 6:43 AM
Edited May 21, 2009 at 6:46 AM

Ben, thank you for your reply.

I don't feel bad. The example I posted was the simplest one where I could reproduce the performance issue. The real scenario of course looks different and is more complex.

I designed a new example which more closely shows what I want to achieve. The outer scrollviewer is there as the scrollviewer offered by the listbox doesn't have the behavior I'd like.

When expanding the items that have close to 400 item it takes roughly 2 sec on my machine. While not an eternity the delay makes the application fail to give the user experience I'd like.

I've tried updating the styles to remove unnecessary scrollviewer and other containers from the visual tree. I got a small improvement. If you can figure out a workaround in order to get the desired behavior and get decent performance I'd be very grateful.

FYI I attached an image of the application we are building (an internal tool). In the example I use listviews instead of the grid. I'm using this semi advanced application to learn best practices on how to design WPF application. Layout performance is something that I wasn't aware that you actually have to pay attention to.

Edit: Hmm how do I attach images to posts?

 <Window x:Class="PerfTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
    Title="Window1" Height="600" Width="800">
   <Window.Resources>
      <DataTemplate x:Key="TestCaseTemplate">
         <Border Margin="0,4,0,4" BorderThickness="0,1,0,0" BorderBrush="Gray">
            <Expander Margin="0,4,0,0" Padding="0,4,0,4" Header="{Binding Path=V0, Mode=OneWay}">
               <Controls:DataGrid VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" ItemsSource="{Binding Path=Properties, Mode=OneWay}" ColumnWidth="200"  />
            </Expander>
         </Border>
      </DataTemplate>
   </Window.Resources>
    <Grid>
      <Grid.RowDefinitions>
         <RowDefinition Height="Auto"/>
         <RowDefinition/>
      </Grid.RowDefinitions>
      <Button Grid.Row="0" Content="Test" Click="Button_Click"/>
      <ScrollViewer Grid.Row="1">
         <ListBox Grid.Row="1" ItemsSource="{Binding Mode=OneWay}" ItemTemplate="{StaticResource TestCaseTemplate}" HorizontalContentAlignment="Stretch" >
         </ListBox>
      </ScrollViewer>
   </Grid>
</Window>

using System;
using System.Linq;

namespace PerfTest
{
   public partial class Window1
   {
      public static readonly Random Randomizer = new Random(740531);

      public Window1()
      {
         InitializeComponent();
      }

      void Button_Click(object sender, System.Windows.RoutedEventArgs e)
      {
         DataContext = Generate<TestCase>(10, 20);
      }

      public static T[] Generate<T>(int minCount, int maxCount)
         where T : new()
      {
         var count = Randomizer.Next(minCount, maxCount);
         return Enumerable
            .Range(0, count)
            .Select(i => new T())
            .ToArray();


      }
   }

   public class TestCase
   {
      public string V0 { get; set; }
      public string V1 { get; set; }

      public TestProperty[] Properties { get; set; }

      public TestCase()
      {
         Properties = Window1.Generate<TestProperty>(100, 400);
         V0 = "Count : " +  Properties.Length;
         V1 = Window1.Randomizer.Next(1000, 2000).ToString();

      }
   }

   public class TestProperty
   {

      public string V0 { get; set; }
      public string V1 { get; set; }
      public string V2 { get; set; }
      public string V3 { get; set; }
      public string V4 { get; set; }

      public TestProperty()
      {
         V0 = Window1.Randomizer.Next(1000, 2000).ToString();
         V1 = Window1.Randomizer.Next(1000, 2000).ToString();
         V2 = Window1.Randomizer.Next(1000, 2000).ToString();
         V3 = Window1.Randomizer.Next(1000, 2000).ToString();
         V4 = Window1.Randomizer.Next(1000, 2000).ToString();
      }
   }

}

 

Coordinator
May 21, 2009 at 9:10 PM

Now you have two instances where your vertical constraint is infinity, thus doubly breaking virtualization.

The first instance is the ScrollViewer around the ListBox. The ScrollViewer will layout the ListBox unconstrained, causing all items in the ListBox to be instantiated. Since you didn't customize the ScrollViewer in the sample, I can't tell what the ListBox's ScrollViewer does or does not do that you don't like, but you can customize some of the parameters of ScrollViewer directly on ListBox using ScrollViewer's attached properties. For instance, you can set ScrollViewer.VerticalScrollBarVisibility on ListBox and it will affect the ListBox's ScrollViewer. If that isn't sufficient, consider changing the template of ListBox.

The second instance is the DataGrid being in a ListBoxItem (which is generated by the ListBox for each item in its ItemsSource). Because the ListBox lays out its children using a VirtualizingStackPanel, each child is unconstrained in the vertical dimension. That means that all of the DataGrid's children will be instantiated. In this case if there are many children in the DataGrid, you might consider applying a vertical constraint by setting Height on the DataGrid so that it becomes constrained.

Ben