WPF DataGrid grouping leads to horrible memory consumption

I just can't get what I'm doing wrong with DataGrid grouping.

I have a minimal application like this:

    public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        List<Rec> data = new List<Rec>();
        var rnd = new Random();
        for (int i = 0; i < 3000; i++)
        {
            data.Add(new Rec() { Group = string.Format("Group{0}", rnd.Next(1,3)), Name = string.Format("Item{0}", rnd.Next(1,50)), Age = rnd.Next(10,100) });
        }

        var dataView = new ListCollectionView(data);
        dataView.GroupDescriptions.Add(new PropertyGroupDescription("Group"));

        dataGrid.ItemsSource = dataView;
    }
}

And XAML for it:

<Window x:Class="dggrouping_test.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:dggrouping_test"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <DataGrid x:Name="dataGrid">
        <DataGrid.GroupStyle>
            <x:Static Member="GroupStyle.Default"/>
        </DataGrid.GroupStyle>
    </DataGrid>
</Grid>

When I compile and run this, app consumes about 550 MB memory (!!!) on 3000 rows. It takes seconds to load. If I just comment dataView.GroupDescriptions.Add(...) and run again - memory usage swoops down to 85 MB. So, it consumes 450 MB per 3000 rows on a minimal example. What am I doing wrong with it?

Upvotes: 1

Views: 1192

Answers (2)

Solved an issue with VirtualizingPanel.IsVirtualizingWhenGrouping attached property. It works on .NET 4.5 and later.

<DataGrid x:Name="dataGrid" VirtualizingPanel.IsVirtualizingWhenGrouping="True">
    <!-...-->
</DataGrid>

Upvotes: 2

Christopher
Christopher

Reputation: 9804

There is more then one answer.

Design

3000 rows is not a small amount. My figure of thumb is soemthing like 100 fields/pieces of informations is the limit you should show to the user. The more fields each row needs, the less rows you should show (down to 5-10) Anything more he can not even process.

Do as much Filtering as you can during the Query. If your DBMS supports paging, use that too. It is a common mistake to retrieve all, then do Filtering in the GUI. 3000 looks like it falls plain into that.

UI Virtualsiation

XAML is designed with a lot of UI Virtualsition Support in mind. The question is only if it is turned on and you expose the right stuff for it to work

Binding Spam

A UI Update is costly. It does not mater if you only do it once per user triggered Event. But it becomes a problem if it is run from any form of loop. I wrote some example code for this issue:

using System;
using System.Windows.Forms;

namespace UIWriteOverhead
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        int[] getNumbers(int upperLimit)
        {
            int[] ReturnValue = new int[upperLimit];

            for (int i = 0; i < ReturnValue.Length; i++)
                ReturnValue[i] = i;

            return ReturnValue;
        }

        void printWithBuffer(int[] Values)
        {
            textBox1.Text = "";
            string buffer = "";

            foreach (int Number in Values)
                buffer += Number.ToString() + Environment.NewLine;
            textBox1.Text = buffer;
        }

        void printDirectly(int[] Values){
            textBox1.Text = "";

            foreach (int Number in Values)
                textBox1.Text += Number.ToString() + Environment.NewLine;
        }

        private void btnPrintBuffer_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Generating Numbers");
            int[] temp = getNumbers(10000);
            MessageBox.Show("Printing with buffer");
            printWithBuffer(temp);
            MessageBox.Show("Printing done");
        }

        private void btnPrintDirect_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Generating Numbers");
            int[] temp = getNumbers(1000);
            MessageBox.Show("Printing directly");
            printDirectly(temp);
            MessageBox.Show("Printing done");
        }
    }
} 

Now few things can cause as much UI updates as excessive Change Notifications. It can be beneficial to remove a Collection for the UI while doing processing work like this to rebind it once you are done.

General Pattern

XAML and WPF/UWP are designed with the MVVM pattern in mind. While you can use other approaches, you miss about 90% of it's power and run into issues every other step. Your code does not look like a proper MVVM pattern to me (the initialsiation code in the Window gave it away; as is the lack of using a CollectionView for the Sorting/Filtering part).

There is a decent chance that the issues you have would never exist if you followed the pattern. In either case it is worth learning if you plan to do any serious work in XAML. I wrote a intro into MVVM a few years back: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf

Memory Issues?

While the Speed issues are real, the memory issues are not nessesarily. It is easy to mix up the measurement.

A lot of people missunderstand that the Garbage Collector is pretty lazy by design. If it only collects once at application closure, that is the ideal case. Accordingly memory useage can increase without there being any problem. You can check if there is a memory leak by calling GC.Collect() for test. But in the life application it should not appear (pick a proper GC strategy instead). https://social.msdn.microsoft.com/Forums/en-US/286d8c7f-87ca-46b9-9608-2b559d7dc79f/garbage-collection-pros-and-limits?forum=csharpgeneral

Upvotes: 1

Related Questions