Why does WPF allocate ~80 MB of managed memory when I hover over a DataGrid row, and how can I prevent it?

I have a WPF application (.NET 8.0) with a DataGrid bound to a collection of 50,000 objects. Each object has ~20 properties, mostly strings and decimals. The grid uses default styling with row highlighting on mouse hover.

I noticed in the Visual Studio Diagnostic Tools that simply moving the mouse over rows causes massive Gen 0 and Gen 1 garbage collections. Memory profiling (dotMemory) shows that each hover allocates thousands of Brush objects, Rect structs, and FormattedText instances — totalling about 80 MB over a few seconds of hovering.

I've already:

Enabled VirtualizingStackPanel.IsVirtualizing="True"

Set EnableRowVirtualization="True"

Used Recycling mode for the virtualizing panel

Disabled animations and transparency where possible

Yet the allocations persist. It seems WPF recreates hover visuals (like background brushes and focus rectangles) on every mouse move, even when the row is already highlighted.

Is there a way to cache or reuse these internal visual resources? Or do I need to override the default DataGridRow style and manually handle the MouseEnter/MouseLeave events with a single shared brush? I'm looking for a clean solution that doesn't sacrifice the native look and feel.

Also, does this happen because of SnapsToDevicePixels or layout rounding? Any insight into the internal rendering pipeline would be greatly appreciated. 1

0 comments No comments

Sign in to comment

2 answers

  1. Nancy Vo (WICLOUD CORPORATION) 6,025 Reputation points Microsoft External Staff Moderator

    Hello @Миров Сергей ,

    Thanks for your question.

    When you hover over DataGrid rows, WPF is creating brand new Brush objects (like SolidColorBrush) for every mouse movement, instead of reusing the same brush. It's like creating a new paintbrush for each stroke instead of using one paintbrush over and over.

    I recommend some following steps:

    1. You can use StaticResource for brushes instead of DynamicResource since StaticResource creates the brush once at compile time and reuses it. DynamicResource can create new instances at runtime.
    2. I suggest defining brushes in resources section, not inline. Instead of pasting the brush definition directly in each style trigger (which means each row gets its own copy), define it once in your resources and point to it. All rows then share that same single brush.
    3. If you're creating brushes in code, call .Freeze() on them. This basically locks the brush so WPF knows it won't change, which lets it optimize and reuse it. Saves a lot of memory.
    4. Please avoid per-item templates with inline brushes. Each row template instance would create its own brushes. Share brushes via resources instead.

    For more information, you can refer to theses documents:

    Furthermore, you don't need to override the DataGridRow style or mess around with MouseEnter/MouseLeave events. That would be overkill and more work than it's worth. And this is not caused by SnapsToDevicePixels or layout rounding. It's a lightweight visual property - it doesn't create or allocate brushes at all. Setting it to true or false has basically no effect on your memory issue.

    I hope this addresses your question. If this response was helpful, please consider following the guidance to provide feedback.

    0 comments No comments

    Sign in to comment
  2. AI answer

    The behavior is caused by how WPF generates and manages UI elements and visuals for list controls, not by a leak in VirtualizingStackPanel itself.

    For large item sets, WPF’s standard layout system creates a container and computes layout for each visible item. With UI virtualization enabled, WPF only creates containers and visuals for items that are on-screen, but it still may allocate short-lived objects (such as brushes and geometry/rect-like data) when visual states change, including hover highlighting.

    From the WPF performance guidance:

    • UI virtualization means item containers and layout are created only for visible items, but all data items are still in memory in a data-binding scenario.
    • VirtualizingStackPanel and VirtualizingPanel work with ItemsControl (such as ListView, ListBox, ListView) to generate elements only for visible items and remove visuals when they scroll out of view.
    • Visual objects for items are generated or kept alive only while visible; when they leave the viewable area, their visuals may be removed.

    This implies:

    • Hovering over rows repeatedly changes the visual state of the same small set of on-screen containers, but WPF may allocate new brushes and related objects for each state transition.
    • The allocations are short-lived and should be collected by Gen 0/Gen 1 GC; frequent collections are expected when many small transient objects are created.

    There is no documented mechanism to “plug into” or cache the internal brushes and visuals that WPF uses for built-in hover states. The supported knobs are the ones already in use:

    • Enabling UI virtualization via VirtualizingStackPanel and ItemsControl.
    • Using recycling mode for item containers.

    The official guidance for large data sets focuses on:

    • Ensuring virtualization is actually active (for example, not setting ScrollViewer.CanContentScroll="false" and not setting VirtualizingStackPanel.IsVirtualizing="false").
    • Being careful with additional state stored on item containers when using recycling, because containers are reused for different items and any state that belongs to the data item should be stored on the item, not the container.

    There is no mention in the documentation of SnapsToDevicePixels or layout rounding as a cause of large allocations for hover effects, nor of a supported way to change the internal rendering pipeline for DataGridRow hover visuals.

    Given the available guidance, the practical options within the documented model are:

    • Keep UI virtualization and recycling enabled (which is already done).
    • Avoid disabling virtualization via CanContentScroll or IsVirtualizing.
    • If hover behavior is still too allocation-heavy for the scenario, define a custom row style and visual states that use simpler or shared resources under application control, accepting that this moves away from the default template but stays within the supported styling model.

    There is no documented way to reduce these specific internal allocations while preserving the exact native look and feel beyond the general virtualization and control-optimization techniques.


    References:

    AI-generated content may be incorrect. Read our transparency notes for more information.
Sign in to answer

Your answer