diff --git a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs index 5b728b3472a..effd0dd4629 100644 --- a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs +++ b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs @@ -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; @@ -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; @@ -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; + } + } + return false; } @@ -231,4 +243,15 @@ public static bool MoveTreeViewTextLocationOnePixel [MethodImpl(MethodImplOptions.AggressiveInlining)] get => GetCachedSwitchValue(MoveTreeViewTextLocationOnePixelSwitchName, ref s_moveTreeViewTextLocationOnePixel); } + + /// + /// Indicates whether dark mode theming is automatically applied to DataGridView + /// controls when the application is running in dark mode. Defaults to + /// for .NET 10+ applications. Set to to opt out. + /// + public static bool DataGridViewDarkModeTheming + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetCachedSwitchValue(DataGridViewDarkModeThemingSwitchName, ref s_dataGridViewDarkModeTheming); + } } diff --git a/src/System.Windows.Forms/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/PublicAPI.Unshipped.txt index 3348c917404..3318b9388ef 100644 --- a/src/System.Windows.Forms/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms/PublicAPI.Unshipped.txt @@ -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! 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.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? diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridView.Methods.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridView.Methods.cs index c48810dbc1a..c04b417b925 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridView.Methods.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridView.Methods.cs @@ -17,6 +17,48 @@ namespace System.Windows.Forms; public partial class DataGridView { + /// + /// Applies dark mode theming to the DataGridView when the application is running + /// in dark mode. This is called automatically from + /// when the System.Windows.Forms.DataGridViewDarkModeTheming AppContext switch + /// is enabled (default for .NET 10+). + /// + 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); + 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) @@ -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) @@ -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; } diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCell.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCell.cs index a7f26a45ae3..fb2556e9a3f 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCell.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCell.cs @@ -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; + } else { darkColor = darkDistance < ContrastThreshold diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCellStyle.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCellStyle.cs index 31bcde3c355..f7471b58ce5 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCellStyle.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCellStyle.cs @@ -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; @@ -59,6 +60,7 @@ public DataGridViewCellStyle(DataGridViewCellStyle dataGridViewCellStyle) WrapModeInternal = dataGridViewCellStyle.WrapMode; Tag = dataGridViewCellStyle.Tag; PaddingInternal = dataGridViewCellStyle.Padding; + SortGlyphColor = dataGridViewCellStyle.SortGlyphColor; } [SRDescription(nameof(SR.DataGridViewCellStyleAlignmentDescr))] @@ -331,6 +333,25 @@ public Color SelectionForeColor } } + /// + /// Gets or sets the color used to draw the sort glyph (arrow) in column headers. + /// When set to , the glyph color is automatically + /// calculated based on the background color for optimal contrast. + /// + [SRCategory(nameof(SR.CatAppearance))] + public Color SortGlyphColor + { + get => Properties.GetValueOrDefault(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 @@ -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); @@ -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) { @@ -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); @@ -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); diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCheckBoxCell.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCheckBoxCell.cs index eeeaac207eb..8dcea8d9030 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCheckBoxCell.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewCheckBoxCell.cs @@ -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); diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewColumnHeaderCell.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewColumnHeaderCell.cs index 047158c8631..8063c751933 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewColumnHeaderCell.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewColumnHeaderCell.cs @@ -1001,175 +1001,202 @@ private Rectangle PaintPrivate( if (paint && displaySortGlyph && PaintContentBackground(paintParts)) { - (Color darkColor, Color lightColor) = GetContrastedColors(cellStyle.BackColor); + Color darkColor; + Color lightColor; + bool useFlatStyle = false; + + // 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; + useFlatStyle = true; + } + else + { + (darkColor, lightColor) = GetContrastedColors(cellStyle.BackColor); + } + using var penControlDark = darkColor.GetCachedPenScope(); using var penControlLightLight = lightColor.GetCachedPenScope(); + // Determine if we should use flat style (solid fill) or 3D style (outline) + bool isFlatLook = useFlatStyle || (advancedBorderStyle.Right != DataGridViewAdvancedCellBorderStyle.OutsetPartial && + advancedBorderStyle.Right != DataGridViewAdvancedCellBorderStyle.OutsetDouble && + advancedBorderStyle.Right != DataGridViewAdvancedCellBorderStyle.Outset && + advancedBorderStyle.Right != DataGridViewAdvancedCellBorderStyle.Inset); + if (SortGlyphDirection == SortOrder.Ascending) { - switch (advancedBorderStyle.Right) + if (isFlatLook) { - case DataGridViewAdvancedCellBorderStyle.OutsetPartial: - case DataGridViewAdvancedCellBorderStyle.OutsetDouble: - case DataGridViewAdvancedCellBorderStyle.Outset: - // Sunken look - g.DrawLine(penControlDark, - sortGlyphLocation.X, - sortGlyphLocation.Y + s_sortGlyphHeight - 2, - sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, - sortGlyphLocation.Y); - g.DrawLine(penControlDark, - sortGlyphLocation.X + 1, - sortGlyphLocation.Y + s_sortGlyphHeight - 2, - sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, - sortGlyphLocation.Y); - g.DrawLine(penControlLightLight, - sortGlyphLocation.X + s_sortGlyphWidth / 2, - sortGlyphLocation.Y, - sortGlyphLocation.X + s_sortGlyphWidth - 2, - sortGlyphLocation.Y + s_sortGlyphHeight - 2); - g.DrawLine(penControlLightLight, - sortGlyphLocation.X + s_sortGlyphWidth / 2, - sortGlyphLocation.Y, - sortGlyphLocation.X + s_sortGlyphWidth - 3, - sortGlyphLocation.Y + s_sortGlyphHeight - 2); - g.DrawLine(penControlLightLight, - sortGlyphLocation.X, - sortGlyphLocation.Y + s_sortGlyphHeight - 1, - sortGlyphLocation.X + s_sortGlyphWidth - 2, - sortGlyphLocation.Y + s_sortGlyphHeight - 1); - break; - - case DataGridViewAdvancedCellBorderStyle.Inset: - // Raised look - g.DrawLine(penControlLightLight, - sortGlyphLocation.X, - sortGlyphLocation.Y + s_sortGlyphHeight - 2, - sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, - sortGlyphLocation.Y); - g.DrawLine(penControlLightLight, - sortGlyphLocation.X + 1, - sortGlyphLocation.Y + s_sortGlyphHeight - 2, - sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, - sortGlyphLocation.Y); - g.DrawLine(penControlDark, - sortGlyphLocation.X + s_sortGlyphWidth / 2, - sortGlyphLocation.Y, - sortGlyphLocation.X + s_sortGlyphWidth - 2, - sortGlyphLocation.Y + s_sortGlyphHeight - 2); - g.DrawLine(penControlDark, - sortGlyphLocation.X + s_sortGlyphWidth / 2, - sortGlyphLocation.Y, - sortGlyphLocation.X + s_sortGlyphWidth - 3, - sortGlyphLocation.Y + s_sortGlyphHeight - 2); + // Flat look - solid filled triangle + for (int line = 0; line < s_sortGlyphWidth / 2; line++) + { g.DrawLine(penControlDark, - sortGlyphLocation.X, - sortGlyphLocation.Y + s_sortGlyphHeight - 1, - sortGlyphLocation.X + s_sortGlyphWidth - 2, - sortGlyphLocation.Y + s_sortGlyphHeight - 1); - break; - - default: - // Flat look - for (int line = 0; line < s_sortGlyphWidth / 2; line++) - { - g.DrawLine(penControlDark, - sortGlyphLocation.X + line, - sortGlyphLocation.Y + s_sortGlyphHeight - line - 1, - sortGlyphLocation.X + s_sortGlyphWidth - line - 1, - sortGlyphLocation.Y + s_sortGlyphHeight - line - 1); - } + sortGlyphLocation.X + line, + sortGlyphLocation.Y + s_sortGlyphHeight - line - 1, + sortGlyphLocation.X + s_sortGlyphWidth - line - 1, + sortGlyphLocation.Y + s_sortGlyphHeight - line - 1); + } - g.DrawLine(penControlDark, - sortGlyphLocation.X + s_sortGlyphWidth / 2, - sortGlyphLocation.Y + s_sortGlyphHeight - s_sortGlyphWidth / 2 - 1, - sortGlyphLocation.X + s_sortGlyphWidth / 2, - sortGlyphLocation.Y + s_sortGlyphHeight - s_sortGlyphWidth / 2); - break; + g.DrawLine(penControlDark, + sortGlyphLocation.X + s_sortGlyphWidth / 2, + sortGlyphLocation.Y + s_sortGlyphHeight - s_sortGlyphWidth / 2 - 1, + sortGlyphLocation.X + s_sortGlyphWidth / 2, + sortGlyphLocation.Y + s_sortGlyphHeight - s_sortGlyphWidth / 2); + } + else + { + switch (advancedBorderStyle.Right) + { + case DataGridViewAdvancedCellBorderStyle.OutsetPartial: + case DataGridViewAdvancedCellBorderStyle.OutsetDouble: + case DataGridViewAdvancedCellBorderStyle.Outset: + // Sunken look + g.DrawLine(penControlDark, + sortGlyphLocation.X, + sortGlyphLocation.Y + s_sortGlyphHeight - 2, + sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, + sortGlyphLocation.Y); + g.DrawLine(penControlDark, + sortGlyphLocation.X + 1, + sortGlyphLocation.Y + s_sortGlyphHeight - 2, + sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, + sortGlyphLocation.Y); + g.DrawLine(penControlLightLight, + sortGlyphLocation.X + s_sortGlyphWidth / 2, + sortGlyphLocation.Y, + sortGlyphLocation.X + s_sortGlyphWidth - 2, + sortGlyphLocation.Y + s_sortGlyphHeight - 2); + g.DrawLine(penControlLightLight, + sortGlyphLocation.X + s_sortGlyphWidth / 2, + sortGlyphLocation.Y, + sortGlyphLocation.X + s_sortGlyphWidth - 3, + sortGlyphLocation.Y + s_sortGlyphHeight - 2); + g.DrawLine(penControlLightLight, + sortGlyphLocation.X, + sortGlyphLocation.Y + s_sortGlyphHeight - 1, + sortGlyphLocation.X + s_sortGlyphWidth - 2, + sortGlyphLocation.Y + s_sortGlyphHeight - 1); + break; + + case DataGridViewAdvancedCellBorderStyle.Inset: + // Raised look + g.DrawLine(penControlLightLight, + sortGlyphLocation.X, + sortGlyphLocation.Y + s_sortGlyphHeight - 2, + sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, + sortGlyphLocation.Y); + g.DrawLine(penControlLightLight, + sortGlyphLocation.X + 1, + sortGlyphLocation.Y + s_sortGlyphHeight - 2, + sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, + sortGlyphLocation.Y); + g.DrawLine(penControlDark, + sortGlyphLocation.X + s_sortGlyphWidth / 2, + sortGlyphLocation.Y, + sortGlyphLocation.X + s_sortGlyphWidth - 2, + sortGlyphLocation.Y + s_sortGlyphHeight - 2); + g.DrawLine(penControlDark, + sortGlyphLocation.X + s_sortGlyphWidth / 2, + sortGlyphLocation.Y, + sortGlyphLocation.X + s_sortGlyphWidth - 3, + sortGlyphLocation.Y + s_sortGlyphHeight - 2); + g.DrawLine(penControlDark, + sortGlyphLocation.X, + sortGlyphLocation.Y + s_sortGlyphHeight - 1, + sortGlyphLocation.X + s_sortGlyphWidth - 2, + sortGlyphLocation.Y + s_sortGlyphHeight - 1); + break; + } } } else { Debug.Assert(SortGlyphDirection == SortOrder.Descending); - switch (advancedBorderStyle.Right) + if (isFlatLook) { - case DataGridViewAdvancedCellBorderStyle.OutsetPartial: - case DataGridViewAdvancedCellBorderStyle.OutsetDouble: - case DataGridViewAdvancedCellBorderStyle.Outset: - // Sunken look - g.DrawLine(penControlDark, - sortGlyphLocation.X, - sortGlyphLocation.Y + 1, - sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, - sortGlyphLocation.Y + s_sortGlyphHeight - 1); - g.DrawLine(penControlDark, - sortGlyphLocation.X + 1, - sortGlyphLocation.Y + 1, - sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, - sortGlyphLocation.Y + s_sortGlyphHeight - 1); - g.DrawLine(penControlLightLight, - sortGlyphLocation.X + s_sortGlyphWidth / 2, - sortGlyphLocation.Y + s_sortGlyphHeight - 1, - sortGlyphLocation.X + s_sortGlyphWidth - 2, - sortGlyphLocation.Y + 1); - g.DrawLine(penControlLightLight, - sortGlyphLocation.X + s_sortGlyphWidth / 2, - sortGlyphLocation.Y + s_sortGlyphHeight - 1, - sortGlyphLocation.X + s_sortGlyphWidth - 3, - sortGlyphLocation.Y + 1); - g.DrawLine(penControlLightLight, - sortGlyphLocation.X, - sortGlyphLocation.Y, - sortGlyphLocation.X + s_sortGlyphWidth - 2, - sortGlyphLocation.Y); - break; - - case DataGridViewAdvancedCellBorderStyle.Inset: - // Raised look - g.DrawLine(penControlLightLight, - sortGlyphLocation.X, - sortGlyphLocation.Y + 1, - sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, - sortGlyphLocation.Y + s_sortGlyphHeight - 1); - g.DrawLine(penControlLightLight, - sortGlyphLocation.X + 1, - sortGlyphLocation.Y + 1, - sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, - sortGlyphLocation.Y + s_sortGlyphHeight - 1); - g.DrawLine(penControlDark, - sortGlyphLocation.X + s_sortGlyphWidth / 2, - sortGlyphLocation.Y + s_sortGlyphHeight - 1, - sortGlyphLocation.X + s_sortGlyphWidth - 2, - sortGlyphLocation.Y + 1); - g.DrawLine(penControlDark, - sortGlyphLocation.X + s_sortGlyphWidth / 2, - sortGlyphLocation.Y + s_sortGlyphHeight - 1, - sortGlyphLocation.X + s_sortGlyphWidth - 3, - sortGlyphLocation.Y + 1); + // Flat look - solid filled triangle (pointing down) + for (int line = 0; line < s_sortGlyphWidth / 2; line++) + { g.DrawLine(penControlDark, - sortGlyphLocation.X, - sortGlyphLocation.Y, - sortGlyphLocation.X + s_sortGlyphWidth - 2, - sortGlyphLocation.Y); - break; - - default: - // Flat look - for (int line = 0; line < s_sortGlyphWidth / 2; line++) - { - g.DrawLine(penControlDark, - sortGlyphLocation.X + line, - sortGlyphLocation.Y + line + 2, - sortGlyphLocation.X + s_sortGlyphWidth - line - 1, - sortGlyphLocation.Y + line + 2); - } + sortGlyphLocation.X + line, + sortGlyphLocation.Y + line + 2, + sortGlyphLocation.X + s_sortGlyphWidth - line - 1, + sortGlyphLocation.Y + line + 2); + } - g.DrawLine(penControlDark, - sortGlyphLocation.X + s_sortGlyphWidth / 2, - sortGlyphLocation.Y + s_sortGlyphWidth / 2 + 1, - sortGlyphLocation.X + s_sortGlyphWidth / 2, - sortGlyphLocation.Y + s_sortGlyphWidth / 2 + 2); - break; + g.DrawLine(penControlDark, + sortGlyphLocation.X + s_sortGlyphWidth / 2, + sortGlyphLocation.Y + s_sortGlyphWidth / 2 + 1, + sortGlyphLocation.X + s_sortGlyphWidth / 2, + sortGlyphLocation.Y + s_sortGlyphWidth / 2 + 2); + } + else + { + switch (advancedBorderStyle.Right) + { + case DataGridViewAdvancedCellBorderStyle.OutsetPartial: + case DataGridViewAdvancedCellBorderStyle.OutsetDouble: + case DataGridViewAdvancedCellBorderStyle.Outset: + // Sunken look + g.DrawLine(penControlDark, + sortGlyphLocation.X, + sortGlyphLocation.Y + 1, + sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, + sortGlyphLocation.Y + s_sortGlyphHeight - 1); + g.DrawLine(penControlDark, + sortGlyphLocation.X + 1, + sortGlyphLocation.Y + 1, + sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, + sortGlyphLocation.Y + s_sortGlyphHeight - 1); + g.DrawLine(penControlLightLight, + sortGlyphLocation.X + s_sortGlyphWidth / 2, + sortGlyphLocation.Y + s_sortGlyphHeight - 1, + sortGlyphLocation.X + s_sortGlyphWidth - 2, + sortGlyphLocation.Y + 1); + g.DrawLine(penControlLightLight, + sortGlyphLocation.X + s_sortGlyphWidth / 2, + sortGlyphLocation.Y + s_sortGlyphHeight - 1, + sortGlyphLocation.X + s_sortGlyphWidth - 3, + sortGlyphLocation.Y + 1); + g.DrawLine(penControlLightLight, + sortGlyphLocation.X, + sortGlyphLocation.Y, + sortGlyphLocation.X + s_sortGlyphWidth - 2, + sortGlyphLocation.Y); + break; + + case DataGridViewAdvancedCellBorderStyle.Inset: + // Raised look + g.DrawLine(penControlLightLight, + sortGlyphLocation.X, + sortGlyphLocation.Y + 1, + sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, + sortGlyphLocation.Y + s_sortGlyphHeight - 1); + g.DrawLine(penControlLightLight, + sortGlyphLocation.X + 1, + sortGlyphLocation.Y + 1, + sortGlyphLocation.X + s_sortGlyphWidth / 2 - 1, + sortGlyphLocation.Y + s_sortGlyphHeight - 1); + g.DrawLine(penControlDark, + sortGlyphLocation.X + s_sortGlyphWidth / 2, + sortGlyphLocation.Y + s_sortGlyphHeight - 1, + sortGlyphLocation.X + s_sortGlyphWidth - 2, + sortGlyphLocation.Y + 1); + g.DrawLine(penControlDark, + sortGlyphLocation.X + s_sortGlyphWidth / 2, + sortGlyphLocation.Y + s_sortGlyphHeight - 1, + sortGlyphLocation.X + s_sortGlyphWidth - 3, + sortGlyphLocation.Y + 1); + g.DrawLine(penControlDark, + sortGlyphLocation.X, + sortGlyphLocation.Y, + sortGlyphLocation.X + s_sortGlyphWidth - 2, + sortGlyphLocation.Y); + break; + } } } } diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewComboBoxCell.DataGridViewComboBoxCellRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewComboBoxCell.DataGridViewComboBoxCellRenderer.cs index 7c5cf83af53..97ef39bdc90 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewComboBoxCell.DataGridViewComboBoxCellRenderer.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewComboBoxCell.DataGridViewComboBoxCellRenderer.cs @@ -17,6 +17,11 @@ private static class DataGridViewComboBoxCellRenderer private static readonly VisualStyleElement s_comboBoxDropDownButtonLeft = VisualStyleElement.ComboBox.DropDownButtonLeft.Normal; private static readonly VisualStyleElement s_comboBoxReadOnlyButton = VisualStyleElement.ComboBox.ReadOnlyButton.Normal; + // Dark Mode element for drop-down button (same as ComboBoxRenderer uses) + private static VisualStyleElement ComboBoxDropDownButtonElement => Application.IsDarkModeEnabled + ? VisualStyleElement.CreateElement($"{Control.DarkModeIdentifier}_{Control.ComboBoxButtonThemeIdentifier}::{Control.ComboboxClassIdentifier}", 1, 1) + : VisualStyleElement.ComboBox.DropDownButton.Normal; + public static VisualStyleRenderer VisualStyleRenderer { get @@ -54,7 +59,12 @@ public static void DrawBorder(Graphics g, Rectangle bounds) public static void DrawDropDownButton(Graphics g, Rectangle bounds, ComboBoxState state, bool rightToLeft) { - if (rightToLeft) + // Use Dark Mode element when enabled + if (Application.IsDarkModeEnabled) + { + InitializeRenderer(ComboBoxDropDownButtonElement, (int)state); + } + else if (rightToLeft) { InitializeRenderer(s_comboBoxDropDownButtonLeft, (int)state); } @@ -68,6 +78,20 @@ public static void DrawDropDownButton(Graphics g, Rectangle bounds, ComboBoxStat public static void DrawReadOnlyButton(Graphics g, Rectangle bounds, ComboBoxState state) { + // Use Dark Mode element when enabled + if (Application.IsDarkModeEnabled) + { + // Draw dark background similar to ComboBox in Dark Mode + 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); + + return; + } + InitializeRenderer(s_comboBoxReadOnlyButton, (int)state); t_visualStyleRenderer.DrawBackground(g, bounds); diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewLinkCell.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewLinkCell.cs index 89195caa137..45f0fdd42f3 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewLinkCell.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridViewLinkCell.cs @@ -52,6 +52,10 @@ public Color ActiveLinkColor { return HighContrastLinkColor; } + else if (Application.IsDarkModeEnabled && AppContextSwitches.DataGridViewDarkModeTheming) + { + return Selected ? SystemColors.HighlightText : LinkUtilities.DarkModeActiveLinkColor; + } else { // Return the default IE Color if cell is not not selected. @@ -160,6 +164,10 @@ public Color LinkColor { return HighContrastLinkColor; } + else if (Application.IsDarkModeEnabled && AppContextSwitches.DataGridViewDarkModeTheming) + { + return DarkModeLinkColor; + } else { // Return the default IE Color when cell is not selected @@ -297,6 +305,10 @@ public Color VisitedLinkColor { return Selected ? SystemColors.HighlightText : LinkUtilities.GetVisitedLinkColor(); } + else if (Application.IsDarkModeEnabled && AppContextSwitches.DataGridViewDarkModeTheming) + { + return DarkModeVisitedLinkColor; + } else { // Return the default IE Color if cell is not not selected @@ -357,6 +369,24 @@ private Color HighContrastLinkColor } } + /// + /// Gets a link color with sufficient contrast for dark mode backgrounds. + /// + private Color DarkModeLinkColor + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Selected ? SystemColors.HighlightText : LinkUtilities.DarkModeLinkColor; + } + + /// + /// Gets a visited link color with sufficient contrast for dark mode backgrounds. + /// + private Color DarkModeVisitedLinkColor + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Selected ? SystemColors.HighlightText : LinkUtilities.DarkModeVisitedLinkColor; + } + public override Type ValueType => base.ValueType ?? s_defaultValueType; public override object Clone() @@ -1014,6 +1044,16 @@ private Rectangle PaintPrivate( linkColor = LinkColor; } + // In dark mode, when the cell is painted as selected, force + // the link text to use SelectionForeColor so it contrasts + // with the custom selection background. + if (cellSelected + && Application.IsDarkModeEnabled + && AppContextSwitches.DataGridViewDarkModeTheming) + { + linkColor = cellStyle.SelectionForeColor; + } + if (PaintContentForeground(paintParts)) { if ((flags & TextFormatFlags.SingleLine) != 0) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Labels/LinkUtilities.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Labels/LinkUtilities.cs index 259e485424b..5b8db64f105 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Labels/LinkUtilities.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Labels/LinkUtilities.cs @@ -110,6 +110,21 @@ public static Color IEVisitedLinkColor } } + /// + /// Gets a light blue link color suitable for dark mode backgrounds. + /// + public static Color DarkModeLinkColor => Color.FromArgb(102, 178, 255); + + /// + /// Gets a light red active link color suitable for dark mode backgrounds. + /// + public static Color DarkModeActiveLinkColor => Color.FromArgb(255, 128, 128); + + /// + /// Gets a light purple visited link color suitable for dark mode backgrounds. + /// + public static Color DarkModeVisitedLinkColor => Color.FromArgb(200, 162, 255); + /// Produces a color for visited links using SystemColors public static Color GetVisitedLinkColor() {