winforms icon indicating copy to clipboard operation
winforms copied to clipboard

Optimize OnDrawItem to prevent GDI resource exhaustion by caching scaled icon size

Open LeafShi1 opened this issue 1 month ago • 1 comments

Fixes #14122

Root Cause

The exception: An object could not be created, possibly due to a lack of memory, but most likely due to invalid input was triggered because of how OnDrawItem handled icon creation in a high-frequency drawing path:

Repeated GDI object creation in hot path Each call to OnDrawItem executed:

  • Icon.FromHandle(cursor.Handle)
  • Clone()
  • ScaleSmallIconToDpi(...)

This pattern allocates multiple unmanaged GDI resources per draw cycle. Under heavy redraw (scrolling, hover, DPI changes), these allocations accumulate quickly, exhausting GDI handles.

Proposed changes

  • Introduced a caching mechanism for scaled icon width based on (Cursor, DPI) to minimize redundant object creation.
  • Ensured ScaleSmallIconToDpi is still called as required, but only during cache population rather than every draw.
  • Added proper disposal of temporary Icon objects during cache initialization.
  • Cleared the cache in the End method to release resources when the control lifecycle ends.

Customer Impact

  • Eliminates random crashes caused by GDI handle exhaustion in owner-drawn controls.

Regression?

  • Yes

Risk

  • Minimal

Screenshots

Before

When using a PropertyGrid to edit properties of type Cursor, resizing the Cursor UITypeEditor drop-down window causes the application to stop responding or crash.

https://github.com/user-attachments/assets/99575f6d-244d-44da-9166-af0b6295fc47

After

https://github.com/user-attachments/assets/be1f6d59-448a-414e-9da2-064f2b3f61f5

Test methodology

  • Manual testing

Test environment(s)

  • .net 10.0.0-rc.3.25603.106
Microsoft Reviewers: Open in CodeFlow

LeafShi1 avatar Dec 09 '25 10:12 LeafShi1

Codecov Report

:x: Patch coverage is 13.33333% with 13 lines in your changes missing coverage. Please review. :white_check_mark: Project coverage is 77.15542%. Comparing base (5d64bba) to head (2c380b6). :warning: Report is 5 commits behind head on main.

Additional details and impacted files
@@                 Coverage Diff                 @@
##                main      #14123         +/-   ##
===================================================
+ Coverage   76.01271%   77.15542%   +1.14271%     
===================================================
  Files           3279        3279                 
  Lines         645325      645339         +14     
  Branches       47720       47721          +1     
===================================================
+ Hits          490529      497914       +7385     
+ Misses        145491      143737       -1754     
+ Partials        9305        3688       -5617     
Flag Coverage Δ
Debug 77.15542% <13.33333%> (+1.14271%) :arrow_up:
integration 18.99480% <0.00000%> (?)
production 52.02161% <13.33333%> (+2.56216%) :arrow_up:
test 97.40749% <ø> (ø)
unit 49.46226% <13.33333%> (+0.00281%) :arrow_up:

Flags with carried forward coverage won't be shown. Click here to find out more.

:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

codecov[bot] avatar Dec 09 '25 10:12 codecov[bot]