Optimize OnDrawItem to prevent GDI resource exhaustion by caching scaled icon size
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
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.