Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal static partial class LocalAppContextSwitches
internal const string EnableMsoComponentManagerSwitchName = "Switch.System.Windows.Forms.EnableMsoComponentManager";
internal const string TreeNodeCollectionAddRangeRespectsSortOrderSwitchName = "System.Windows.Forms.TreeNodeCollectionAddRangeRespectsSortOrder";
internal const string MoveTreeViewTextLocationOnePixelSwitchName = "System.Windows.Forms.TreeView.MoveTreeViewTextLocationOnePixel";
internal const string DataGridViewDarkModeThemingSwitchName = "System.Windows.Forms.DataGridViewDarkModeTheming";

private static int s_scaleTopLevelFormMinMaxSizeForDpi;
private static int s_anchorLayoutV2;
Expand All @@ -39,6 +40,7 @@ internal static partial class LocalAppContextSwitches
private static int s_treeNodeCollectionAddRangeRespectsSortOrder;

private static int s_moveTreeViewTextLocationOnePixel;
private static int s_dataGridViewDarkModeTheming;

private static FrameworkName? s_targetFrameworkName;

Expand Down Expand Up @@ -134,6 +136,16 @@ private static bool GetSwitchDefaultValue(string switchName)
}
}

if (framework.Version.Major >= 10)
{
// Behavior changes added in .NET 10

if (switchName == DataGridViewDarkModeThemingSwitchName)
{
return true;
}
}
Comment on lines +139 to +147
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 'if' statements can be combined.

Copilot uses AI. Check for mistakes.

return false;
}

Expand Down Expand Up @@ -231,4 +243,15 @@ public static bool MoveTreeViewTextLocationOnePixel
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => GetCachedSwitchValue(MoveTreeViewTextLocationOnePixelSwitchName, ref s_moveTreeViewTextLocationOnePixel);
}

/// <summary>
/// Indicates whether dark mode theming is automatically applied to DataGridView
/// controls when the application is running in dark mode. Defaults to <see langword="true"/>
/// for .NET 10+ applications. Set to <see langword="false"/> to opt out.
/// </summary>
public static bool DataGridViewDarkModeTheming
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => GetCachedSwitchValue(DataGridViewDarkModeThemingSwitchName, ref s_dataGridViewDarkModeTheming);
}
}
2 changes: 2 additions & 0 deletions src/System.Windows.Forms/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ static System.Windows.Forms.TaskDialog.ShowDialogAsync(nint hwndOwner, System.Wi
static System.Windows.Forms.TaskDialog.ShowDialogAsync(System.Windows.Forms.IWin32Window! owner, System.Windows.Forms.TaskDialogPage! page, System.Windows.Forms.TaskDialogStartupLocation startupLocation = System.Windows.Forms.TaskDialogStartupLocation.CenterOwner) -> System.Threading.Tasks.Task<System.Windows.Forms.TaskDialogButton!>!
static System.Windows.Forms.TaskDialog.ShowDialogAsync(System.Windows.Forms.TaskDialogPage! page, System.Windows.Forms.TaskDialogStartupLocation startupLocation = System.Windows.Forms.TaskDialogStartupLocation.CenterScreen) -> System.Threading.Tasks.Task<System.Windows.Forms.TaskDialogButton!>!
System.Windows.Forms.ControlStyles.ApplyThemingImplicitly = 524288 -> System.Windows.Forms.ControlStyles
System.Windows.Forms.DataGridViewCellStyle.SortGlyphColor.get -> System.Drawing.Color
System.Windows.Forms.DataGridViewCellStyle.SortGlyphColor.set -> void
System.Windows.Forms.Form.FormBorderColorChanged -> System.EventHandler?
System.Windows.Forms.Form.FormCaptionBackColorChanged -> System.EventHandler?
System.Windows.Forms.Form.FormCaptionTextColorChanged -> System.EventHandler?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,48 @@ namespace System.Windows.Forms;

public partial class DataGridView
{
/// <summary>
/// Applies dark mode theming to the DataGridView when the application is running
/// in dark mode. This is called automatically from <see cref="OnHandleCreated"/>
/// when the <c>System.Windows.Forms.DataGridViewDarkModeTheming</c> AppContext switch
/// is enabled (default for .NET 10+).
/// </summary>
private void ApplyDarkModeTheming()
{
Color surface = SystemColors.Window;
Color onSurface = SystemColors.WindowText;
Color headerBg = SystemColors.ControlDarkDark;
Color headerFg = SystemColors.ActiveCaptionText;
Color selectionBg = Color.FromArgb(0x33, 0x66, 0xCC);
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Color selectionFg = Color.White;

// Disable the header's system theme so that our custom styles are not overridden by the system.
EnableHeadersVisualStyles = false;

// Table body
BackgroundColor = surface;
DefaultCellStyle.BackColor = surface;
DefaultCellStyle.ForeColor = onSurface;

// Column headers
ColumnHeadersDefaultCellStyle.BackColor = headerBg;
ColumnHeadersDefaultCellStyle.ForeColor = headerFg;

// Light-colored dividing line
GridColor = ControlPaint.Light(surface, 0.50f);

// Row headers
RowHeadersDefaultCellStyle.BackColor = headerBg;
RowHeadersDefaultCellStyle.ForeColor = headerFg;

// Selected state - use Color.Empty so header selection follows body selection
RowHeadersDefaultCellStyle.SelectionBackColor = Color.Empty;
ColumnHeadersDefaultCellStyle.SelectionBackColor = Color.Empty;

DefaultCellStyle.SelectionBackColor = selectionBg;
DefaultCellStyle.SelectionForeColor = selectionFg;
}

protected virtual void AccessibilityNotifyCurrentCellChanged(Point cellAddress)
{
if (cellAddress.X < 0 || cellAddress.X >= Columns.Count)
Expand Down Expand Up @@ -2853,6 +2895,20 @@ private void BuildInheritedColumnHeaderCellStyle(DataGridViewCellStyle inherited
inheritedCellStyle.SelectionForeColor = dataGridViewStyle.SelectionForeColor;
}

// Inherit SortGlyphColor for column header sort arrow
if (cellStyle is not null && !cellStyle.SortGlyphColor.IsEmpty)
{
inheritedCellStyle.SortGlyphColor = cellStyle.SortGlyphColor;
}
else if (!columnHeadersStyle.SortGlyphColor.IsEmpty)
{
inheritedCellStyle.SortGlyphColor = columnHeadersStyle.SortGlyphColor;
}
else if (!dataGridViewStyle.SortGlyphColor.IsEmpty)
{
inheritedCellStyle.SortGlyphColor = dataGridViewStyle.SortGlyphColor;
}

inheritedCellStyle.Font = cellStyle?.Font ?? columnHeadersStyle.Font ?? dataGridViewStyle.Font;

if (cellStyle is not null && !cellStyle.IsNullValueDefault)
Expand Down Expand Up @@ -15242,6 +15298,12 @@ protected override void OnHandleCreated(EventArgs e)
OnGlobalAutoSize();
}

// Automatically apply dark mode theming when enabled via quirk switch.
if (Application.IsDarkModeEnabled && AppContextSwitches.DataGridViewDarkModeTheming)
{
ApplyDarkModeTheming();
}

SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,18 @@ private static Bitmap GetBitmap(string bitmapName) =>
? ControlPaint.LightLight(baseline)
: SystemColors.ControlLightLight;
}
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;
}
Comment on lines +1438 to +1449
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
else
{
darkColor = darkDistance < ContrastThreshold
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class DataGridViewCellStyle : ICloneable
private static readonly int s_propPadding = PropertyStore.CreateKey();
private static readonly int s_propSelectionBackColor = PropertyStore.CreateKey();
private static readonly int s_propSelectionForeColor = PropertyStore.CreateKey();
private static readonly int s_propSortGlyphColor = PropertyStore.CreateKey();
private static readonly int s_propTag = PropertyStore.CreateKey();
private static readonly int s_propWrapMode = PropertyStore.CreateKey();
private DataGridView? _dataGridView;
Expand Down Expand Up @@ -59,6 +60,7 @@ public DataGridViewCellStyle(DataGridViewCellStyle dataGridViewCellStyle)
WrapModeInternal = dataGridViewCellStyle.WrapMode;
Tag = dataGridViewCellStyle.Tag;
PaddingInternal = dataGridViewCellStyle.Padding;
SortGlyphColor = dataGridViewCellStyle.SortGlyphColor;
}

[SRDescription(nameof(SR.DataGridViewCellStyleAlignmentDescr))]
Expand Down Expand Up @@ -331,6 +333,25 @@ public Color SelectionForeColor
}
}

/// <summary>
/// Gets or sets the color used to draw the sort glyph (arrow) in column headers.
/// When set to <see cref="Color.Empty"/>, the glyph color is automatically
/// calculated based on the background color for optimal contrast.
/// </summary>
[SRCategory(nameof(SR.CatAppearance))]
public Color SortGlyphColor
{
get => Properties.GetValueOrDefault<Color>(s_propSortGlyphColor);
set
{
Color previous = Properties.AddOrRemoveValue(s_propSortGlyphColor, value);
if (!previous.Equals(value))
{
OnPropertyChanged(DataGridViewCellStylePropertyInternal.Color);
}
}
}

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public object? Tag
Expand Down Expand Up @@ -439,6 +460,11 @@ public virtual void ApplyStyle(DataGridViewCellStyle dataGridViewCellStyle)
{
PaddingInternal = dataGridViewCellStyle.Padding;
}

if (!dataGridViewCellStyle.SortGlyphColor.IsEmpty)
{
SortGlyphColor = dataGridViewCellStyle.SortGlyphColor;
}
}

public virtual DataGridViewCellStyle Clone() => new(this);
Expand All @@ -465,7 +491,8 @@ internal DataGridViewCellStyleDifferences GetDifferencesFrom(DataGridViewCellSty
dgvcs.BackColor != BackColor ||
dgvcs.ForeColor != ForeColor ||
dgvcs.SelectionBackColor != SelectionBackColor ||
dgvcs.SelectionForeColor != SelectionForeColor);
dgvcs.SelectionForeColor != SelectionForeColor ||
dgvcs.SortGlyphColor != SortGlyphColor);

if (preferredSizeAffectingPropDifferent)
{
Expand All @@ -492,6 +519,7 @@ public override int GetHashCode()
hash.Add(ForeColor);
hash.Add(SelectionBackColor);
hash.Add(SelectionForeColor);
hash.Add(SortGlyphColor);
hash.Add(Font);
hash.Add(NullValue);
hash.Add(DataSourceNullValue);
Expand Down Expand Up @@ -530,6 +558,8 @@ internal void RemoveScope(DataGridViewCellStyleScopes scope)

private bool ShouldSerializeSelectionForeColor() => Properties.ContainsKey(s_propSelectionForeColor);

private bool ShouldSerializeSortGlyphColor() => Properties.ContainsKey(s_propSortGlyphColor);

public override string ToString()
{
StringBuilder sb = new(128);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1240,10 +1240,11 @@ private Rectangle PaintPrivate(
{
if (paint && PaintContentForeground(paintParts))
{
DataGridViewCheckBoxCellRenderer.DrawCheckBox(
CheckBoxRenderer.DrawCheckBoxWithVisualStyles(
g,
new Rectangle(checkBoxX, checkBoxY, checkBoxSize.Width, checkBoxSize.Height),
(int)themeCheckBoxState);
new Point(checkBoxX, checkBoxY),
themeCheckBoxState,
DataGridView.HWNDInternal);
}

resultBounds = new Rectangle(checkBoxX, checkBoxY, checkBoxSize.Width, checkBoxSize.Height);
Expand Down
Loading
Loading