Enable DataGridView dark mode theming via AppContext switch#14267
Enable DataGridView dark mode theming via AppContext switch#14267LeafShi1 wants to merge 8 commits intodotnet:mainfrom
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #14267 +/- ##
===================================================
- Coverage 77.18395% 77.16791% -0.01604%
===================================================
Files 3279 3279
Lines 645138 645263 +125
Branches 47730 47755 +25
===================================================
- Hits 497943 497936 -7
- Misses 143503 143631 +128
- Partials 3692 3696 +4
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
…Color.get' and 'set' to public API
There was a problem hiding this comment.
Pull request overview
This PR introduces dark mode theming for the DataGridView control via an AppContext switch (System.Windows.Forms.DataGridViewDarkModeTheming), defaulting to enabled for .NET 10+. The changes provide better visual appearance and contrast in dark mode while preserving opt-out capability for backward compatibility.
Changes:
- Added AppContext switch to control DataGridView dark mode theming, with inheritance through style system
- Implemented dark mode rendering for multiple cell types including link cells, combo box cells, checkbox cells, and column header sort glyphs
- Added shared dark mode link colors to LinkUtilities for consistent link rendering across the DataGridView
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 31 comments.
Show a summary per file
| File | Description |
|---|---|
| src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs | Added DataGridViewDarkModeTheming switch with .NET 10+ default |
| src/System.Windows.Forms/System/Windows/Forms/Controls/Labels/LinkUtilities.cs | Added dark mode color constants for links |
| src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewLinkCell.cs | Implemented dark mode link colors with proper selection handling |
| src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewComboBoxCell.DataGridViewComboBoxCellRenderer.cs | Added dark mode rendering for combo box dropdown and readonly buttons |
| src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewColumnHeaderCell.cs | Refactored sort glyph rendering to support solid white glyphs in dark mode |
| src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCheckBoxCell.cs | Switched to CheckBoxRenderer.DrawCheckBoxWithVisualStyles for dark mode support |
| src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCellStyle.cs | Added SortGlyphColor property to allow customization of sort arrow color |
| src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCell.cs | Enhanced GetContrastedColors to provide better contrast in dark mode |
| src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridView.Methods.cs | Added ApplyDarkModeTheming method and integration in OnHandleCreated |
| src/System.Windows.Forms/PublicAPI.Unshipped.txt | Documented new SortGlyphColor public API |
| using var brush = new SolidBrush(Color.FromArgb(45, 45, 45)); | ||
| g.FillRectangle(brush, bounds); | ||
|
|
||
| // Draw border | ||
| using var pen = new Pen(Color.FromArgb(100, 100, 100)); | ||
| g.DrawRectangle(pen, bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1); |
There was a problem hiding this comment.
The hardcoded Color.FromArgb(45, 45, 45) and Color.FromArgb(100, 100, 100) magic numbers should be replaced with named constants. These same values appear in ToolStripSystemDarkModeRenderer. Consider extracting these common dark mode colors to a shared location (such as a DarkModeColors class) to ensure consistency across controls and improve maintainability.
| // In Dark Mode with quirk enabled, use a solid white glyph for better visibility | ||
| if (Application.IsDarkModeEnabled && AppContextSwitches.DataGridViewDarkModeTheming) | ||
| { | ||
| darkColor = Color.White; | ||
| lightColor = Color.White; |
There was a problem hiding this comment.
The SortGlyphColor property from cellStyle is inherited in BuildInheritedColumnHeaderCellStyle but is never actually used in the painting logic. The sort glyph rendering code hardcodes Color.White for dark mode on line 1011-1012 instead of checking if cellStyle.SortGlyphColor is set. The code should check cellStyle.SortGlyphColor first and only fall back to Color.White if it's Color.Empty, allowing users to customize the sort glyph color.
| // In Dark Mode with quirk enabled, use a solid white glyph for better visibility | |
| if (Application.IsDarkModeEnabled && AppContextSwitches.DataGridViewDarkModeTheming) | |
| { | |
| darkColor = Color.White; | |
| lightColor = Color.White; | |
| // In Dark Mode with quirk enabled, prefer SortGlyphColor when set, otherwise use solid white glyph for better visibility | |
| if (Application.IsDarkModeEnabled && AppContextSwitches.DataGridViewDarkModeTheming) | |
| { | |
| Color sortGlyphColor = cellStyle.SortGlyphColor; | |
| if (sortGlyphColor == Color.Empty) | |
| { | |
| sortGlyphColor = Color.White; | |
| } | |
| darkColor = sortGlyphColor; | |
| lightColor = sortGlyphColor; |
| Color onSurface = SystemColors.WindowText; | ||
| Color headerBg = SystemColors.ControlDarkDark; | ||
| Color headerFg = SystemColors.ActiveCaptionText; | ||
| Color selectionBg = Color.FromArgb(0x33, 0x66, 0xCC); |
There was a problem hiding this comment.
The magic numbers Color.FromArgb(0x33, 0x66, 0xCC) for selectionBg should be replaced with a named constant to improve code clarity and maintainability. Consider defining this as a constant like DarkModeSelectionBackground.
| else if (Application.IsDarkModeEnabled) | ||
| { | ||
| // In Dark Mode, use higher contrast colors for better visibility. | ||
| // For dark backgrounds, we need lighter colors that stand out. | ||
| darkColor = darkDistance < ContrastThreshold | ||
| ? ControlPaint.Light(baseline, 0.5f) | ||
| : SystemColors.ControlLight; | ||
|
|
||
| lightColor = lightDistance < ContrastThreshold | ||
| ? ControlPaint.LightLight(baseline) | ||
| : SystemColors.ControlLightLight; | ||
| } |
There was a problem hiding this comment.
The GetContrastedColors method now checks Application.IsDarkModeEnabled and returns dark-mode-optimized colors for all DataGridView cells, but it does not verify the AppContextSwitches.DataGridViewDarkModeTheming switch. This means border rendering and other uses of GetContrastedColors will apply dark mode logic even if the user has opted out via the AppContext switch. If the intent is that opting out of the quirk should preserve all existing light-mode behavior, then GetContrastedColors should also check the AppContext switch. Alternatively, if general dark mode color improvements are intended to apply regardless of the quirk, this should be clarified in the PR description.
| if (Application.IsDarkModeEnabled) | ||
| { | ||
| InitializeRenderer(ComboBoxDropDownButtonElement, (int)state); | ||
| } |
There was a problem hiding this comment.
The DataGridViewComboBoxCellRenderer methods DrawDropDownButton and DrawReadOnlyButton check Application.IsDarkModeEnabled but do not verify the AppContextSwitches.DataGridViewDarkModeTheming switch. This means dark mode rendering will be applied even if the user has opted out via the AppContext switch. Both dark mode conditions should check both Application.IsDarkModeEnabled AND AppContextSwitches.DataGridViewDarkModeTheming to respect the opt-out mechanism.
| g.DrawLine(penControlDark, | ||
| sortGlyphLocation.X, | ||
| sortGlyphLocation.Y + 1, | ||
| sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, |
There was a problem hiding this comment.
Possible loss of precision: any fraction will be lost.
| g.DrawLine(penControlDark, | ||
| sortGlyphLocation.X + 1, | ||
| sortGlyphLocation.Y + 1, | ||
| sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, |
There was a problem hiding this comment.
Possible loss of precision: any fraction will be lost.
| g.DrawLine(penControlLightLight, | ||
| sortGlyphLocation.X, | ||
| sortGlyphLocation.Y + 1, | ||
| sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, |
There was a problem hiding this comment.
Possible loss of precision: any fraction will be lost.
| g.DrawLine(penControlLightLight, | ||
| sortGlyphLocation.X + 1, | ||
| sortGlyphLocation.Y + 1, | ||
| sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, |
There was a problem hiding this comment.
Possible loss of precision: any fraction will be lost.
| if (framework.Version.Major >= 10) | ||
| { | ||
| // Behavior changes added in .NET 10 | ||
|
|
||
| if (switchName == DataGridViewDarkModeThemingSwitchName) | ||
| { | ||
| return true; | ||
| } | ||
| } |
There was a problem hiding this comment.
These 'if' statements can be combined.
Fixes #14266
Proposed changes
System.Windows.Forms.DataGridViewDarkModeTheming(exposed via LocalAppContextSwitches / AppContextSwitches) to control whether DataGridView uses dark-mode-specific theming, defaulting to enabled for .NET 10 and allowing apps to opt out.ApplyDarkModeThemingin DataGridView and call it only when dark mode is active and the new switch is enabled, centralizing the dark palette (background, gridlines, header/row colors) for high contrast on dark backgrounds.LinkUtilitieslink colors (aligned with LinkLabel’s dark colors) and ensuring selected links render with appropriate selection foreground color for sufficient contrast.Customer Impact
Regression?
Risk
Screenshots
Before
After
Test methodology
Test environment(s)
Microsoft Reviewers: Open in CodeFlow