Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 thread
LeafShi1 marked this conversation as resolved.

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 @@ -5,6 +5,8 @@ static System.Windows.Forms.Application.SystemVisualSettingsChanged -> System.Wi
System.Windows.Forms.PropertyGrid.VisualStylesModeChanged -> System.EventHandler?
virtual System.Windows.Forms.Control.OnSystemVisualSettingsChanged(System.Windows.Forms.SystemVisualSettingsChangedEventArgs! e) -> void
System.Windows.Forms.Control.SystemVisualSettingsChanged -> System.Windows.Forms.SystemVisualSettingsChangedEventHandler?
System.Windows.Forms.DataGridViewCellStyle.SortGlyphColor.get -> System.Drawing.Color
System.Windows.Forms.DataGridViewCellStyle.SortGlyphColor.set -> void
System.Windows.Forms.SystemVisualSettings.AccentColor.get -> System.Drawing.Color
System.Windows.Forms.SystemVisualSettings.ClientAreaAnimationEnabled.get -> bool
System.Windows.Forms.SystemVisualSettings.FocusBorderMetrics.get -> System.Drawing.Size
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,51 @@ namespace System.Windows.Forms;

public partial class DataGridView
{
// A medium accent blue commonly used for selection highlights in dark UI.
private static readonly Color s_darkModeSelectionBackground = Color.FromArgb(0x33, 0x66, 0xCC);

/// <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 = s_darkModeSelectionBackground;
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 +2898,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 +15301,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 && AppContextSwitches.DataGridViewDarkModeTheming)
{
// 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;
}
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