diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/CheckBoxModernAdapter.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/CheckBoxModernAdapter.cs index 920d8553c6f..6c506d87aaf 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/CheckBoxModernAdapter.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/CheckBoxModernAdapter.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; @@ -80,21 +80,33 @@ internal override LayoutOptions CommonLayout() private void PaintCore(PaintEventArgs e) { Graphics graphics = e.GraphicsInternal; - ParentBackgroundRenderer.Paint( - Control, - graphics, - Control.ClientRectangle, - Control.BackColor); + bool useExplicitBackColor = Control.ShouldSerializeBackColor() || !Control.UseVisualStyleBackColor; + bool hasTransparentBackColor = Control.BackColor.HasTransparency(); + + if (useExplicitBackColor && !hasTransparentBackColor) + { + using var backBrush = Control.BackColor.GetCachedSolidBrushScope(); + graphics.FillRectangle(backBrush, Control.ClientRectangle); + } + else + { + ParentBackgroundRenderer.Paint( + Control, + graphics, + Control.ClientRectangle, + Control.BackColor); + + if (useExplicitBackColor && hasTransparentBackColor && Control.BackColor.A > 0) + { + using var backBrush = Control.BackColor.GetCachedSolidBrushScope(); + graphics.FillRectangle(backBrush, Control.ClientRectangle); + } + } LayoutData layout = Layout(e).Layout(); AdjustFocusRectangle(layout); PaintBackgroundImage(e); - Color? customOnColor = Control.ShouldSerializeBackColor() - && Control.BackColor.A == byte.MaxValue - ? Control.BackColor - : null; - Color? customBorderColor = Control.FlatAppearance.BorderColor.IsEmpty ? null : Control.FlatAppearance.BorderColor; @@ -107,7 +119,7 @@ private void PaintCore(PaintEventArgs e) Control.Enabled, Control.MouseIsOver, Control.Focused && Control.ShowFocusCues, - customOnColor, + customOnColor: null, customBorderColor); PaintImage(e, layout); @@ -117,11 +129,16 @@ private void PaintCore(PaintEventArgs e) : Application.IsDarkModeEnabled ? Color.FromArgb(0xF0, 0xF0, 0xF0) : SystemColors.WindowText; + Color disabledTextBackColor = Control.ShouldSerializeBackColor() + && Control.BackColor.A == byte.MaxValue + ? Control.BackColor + : Control.Parent?.BackColor ?? Control.BackColor; + Color textColor = Control.Enabled ? preferredTextColor : ModernControlColorMath.GetDisabledTextColor( preferredTextColor, - Control.Parent?.BackColor ?? Control.BackColor); + disabledTextBackColor); PaintField(e, layout, PaintRender(e).Calculate(), textColor, drawFocus: true); } diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/RadioButtonModernAdapter.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/RadioButtonModernAdapter.cs index ebf45535516..60de5596f10 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/RadioButtonModernAdapter.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/RadioButtonModernAdapter.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; @@ -79,21 +79,33 @@ internal override LayoutOptions CommonLayout() private void PaintCore(PaintEventArgs e) { Graphics graphics = e.GraphicsInternal; - ParentBackgroundRenderer.Paint( - Control, - graphics, - Control.ClientRectangle, - Control.BackColor); + bool useExplicitBackColor = Control.ShouldSerializeBackColor() || !Control.UseVisualStyleBackColor; + bool hasTransparentBackColor = Control.BackColor.HasTransparency(); + + if (useExplicitBackColor && !hasTransparentBackColor) + { + using var backBrush = Control.BackColor.GetCachedSolidBrushScope(); + graphics.FillRectangle(backBrush, Control.ClientRectangle); + } + else + { + ParentBackgroundRenderer.Paint( + Control, + graphics, + Control.ClientRectangle, + Control.BackColor); + + if (useExplicitBackColor && hasTransparentBackColor && Control.BackColor.A > 0) + { + using var backBrush = Control.BackColor.GetCachedSolidBrushScope(); + graphics.FillRectangle(backBrush, Control.ClientRectangle); + } + } LayoutData layout = Layout(e).Layout(); AdjustFocusRectangle(layout); PaintBackgroundImage(e); - Color? customOnColor = Control.ShouldSerializeBackColor() - && Control.BackColor.A == byte.MaxValue - ? Control.BackColor - : null; - Color? customBorderColor = Control.FlatAppearance.BorderColor.IsEmpty ? null : Control.FlatAppearance.BorderColor; @@ -106,7 +118,7 @@ private void PaintCore(PaintEventArgs e) Control.Enabled, Control.MouseIsOver, Control.Focused && Control.ShowFocusCues, - customOnColor, + customOnColor: null, customBorderColor); PaintImage(e, layout); @@ -116,11 +128,16 @@ private void PaintCore(PaintEventArgs e) : Application.IsDarkModeEnabled ? Color.FromArgb(0xF0, 0xF0, 0xF0) : SystemColors.WindowText; + Color disabledTextBackColor = Control.ShouldSerializeBackColor() + && Control.BackColor.A == byte.MaxValue + ? Control.BackColor + : Control.Parent?.BackColor ?? Control.BackColor; + Color textColor = Control.Enabled ? preferredTextColor : ModernControlColorMath.GetDisabledTextColor( preferredTextColor, - Control.Parent?.BackColor ?? Control.BackColor); + disabledTextBackColor); PaintField(e, layout, PaintRender(e).Calculate(), textColor, drawFocus: true); } diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/CheckBoxTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/CheckBoxTests.cs index 3680393822d..40ff7d66a88 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/CheckBoxTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/CheckBoxTests.cs @@ -571,17 +571,24 @@ public void CheckBox_AppearanceChanged_RecreatesModernAdapter() [InlineData(CheckState.Unchecked, false)] [InlineData(CheckState.Checked, true)] [InlineData(CheckState.Indeterminate, true)] - public void CheckBox_ModernGlyph_RendersAccentForCheckedStates(CheckState checkState, bool expectedAccent) + public void CheckBox_ModernGlyph_UsesExplicitBackColorWithoutTintingCheckedGlyph(CheckState checkState, bool expectedAccent) { if (SystemInformation.HighContrast) { return; } + Color accentColor = Application.SystemVisualSettings.AccentColor; + Color backgroundColor = Color.FromArgb( + accentColor.R ^ 0xFF, + accentColor.G ^ 0xFF, + accentColor.B ^ 0xFF); + using Panel parent = new() { BackColor = Color.White }; using CheckBox box = new() { - BackColor = Color.Red, + BackColor = backgroundColor, + UseVisualStyleBackColor = true, CheckState = checkState, Size = new Size(40, 24), VisualStylesMode = VisualStylesMode.Net11 @@ -594,7 +601,69 @@ public void CheckBox_ModernGlyph_RendersAccentForCheckedStates(CheckState checkS box.CreateStandardAdapter().PaintUp(e, checkState); - Assert.Equal(expectedAccent, CountPixels(bitmap, Color.Red) > 0); + Color backgroundPixel = bitmap.GetPixel(box.Width - 2, box.Height / 2); + Assert.Equal(backgroundColor.ToArgb(), backgroundPixel.ToArgb()); + Assert.Equal( + expectedAccent, + CountPixels(bitmap, accentColor) > 0); + } + + [WinFormsFact] + public void CheckBox_ModernGlyph_UsesExplicitBackColorWhenVisualStyleBackgroundDisabled() + { + using Panel parent = new() { BackColor = Color.White }; + using CheckBox box = new() + { + BackColor = Color.Aqua, + CheckState = CheckState.Unchecked, + Text = string.Empty, + Size = new Size(40, 24), + VisualStylesMode = VisualStylesMode.Net11 + }; + + parent.Controls.Add(box); + + using Bitmap bitmap = new(box.Width, box.Height); + using Graphics graphics = Graphics.FromImage(bitmap); + PaintEventArgs e = new(graphics, box.ClientRectangle); + + box.CreateStandardAdapter().PaintUp(e, box.CheckState); + + Color backgroundPixel = bitmap.GetPixel(box.Width - 2, box.Height / 2); + Assert.Equal(Color.Aqua.ToArgb(), backgroundPixel.ToArgb()); + } + + [WinFormsFact] + public void CheckBox_ModernGlyph_UsesTranslucentBackColorWhenVisualStyleBackgroundEnabled() + { + Color parentBackColor = Color.White; + Color translucentBackColor = Color.FromArgb(128, Color.Aqua); + + using Panel parent = new() { BackColor = parentBackColor }; + using CheckBox box = new() + { + BackColor = translucentBackColor, + UseVisualStyleBackColor = true, + CheckState = CheckState.Unchecked, + Text = string.Empty, + Size = new Size(40, 24), + VisualStylesMode = VisualStylesMode.Net11 + }; + + parent.Controls.Add(box); + + using Bitmap bitmap = new(box.Width, box.Height); + using Graphics graphics = Graphics.FromImage(bitmap); + graphics.Clear(parentBackColor); + PaintEventArgs e = new(graphics, box.ClientRectangle); + + box.CreateStandardAdapter().PaintUp(e, box.CheckState); + + Color backgroundPixel = bitmap.GetPixel(box.Width - 2, box.Height / 2); + Color expected = BlendColors(parentBackColor, translucentBackColor); + Assert.InRange(Math.Abs(backgroundPixel.R - expected.R), 0, 1); + Assert.InRange(Math.Abs(backgroundPixel.G - expected.G), 0, 1); + Assert.InRange(Math.Abs(backgroundPixel.B - expected.B), 0, 1); } [WinFormsFact] @@ -1034,6 +1103,18 @@ private static int CountPixels(Bitmap bitmap, Color color) return count; } + private static Color BlendColors(Color background, Color overlay) + { + int alpha = overlay.A; + int inverseAlpha = byte.MaxValue - alpha; + + int red = ((overlay.R * alpha) + (background.R * inverseAlpha) + 127) / 255; + int green = ((overlay.G * alpha) + (background.G * inverseAlpha) + 127) / 255; + int blue = ((overlay.B * alpha) + (background.B * inverseAlpha) + 127) / 255; + + return Color.FromArgb(red, green, blue); + } + [WinFormsFact] public void CheckBox_GetAutoSizeMode_Invoke_ReturnsExpected() { diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/RadioButtonTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/RadioButtonTests.cs index 8e0b9fc6e02..c9d93b307c5 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/RadioButtonTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/RadioButtonTests.cs @@ -209,17 +209,24 @@ public void RadioButton_AppearanceChanged_RecreatesModernAdapter() [WinFormsTheory] [InlineData(false, false)] [InlineData(true, true)] - public void RadioButton_ModernGlyph_RendersAccentWhenChecked(bool isChecked, bool expectedAccent) + public void RadioButton_ModernGlyph_UsesExplicitBackColorWithoutTintingCheckedGlyph(bool isChecked, bool expectedAccent) { if (SystemInformation.HighContrast) { return; } + Color accentColor = Application.SystemVisualSettings.AccentColor; + Color backgroundColor = Color.FromArgb( + accentColor.R ^ 0xFF, + accentColor.G ^ 0xFF, + accentColor.B ^ 0xFF); + using Panel parent = new() { BackColor = Color.White }; using RadioButton control = new() { - BackColor = Color.Red, + BackColor = backgroundColor, + UseVisualStyleBackColor = true, Checked = isChecked, Size = new Size(40, 24), VisualStylesMode = VisualStylesMode.Net11 @@ -234,7 +241,69 @@ public void RadioButton_ModernGlyph_RendersAccentWhenChecked(bool isChecked, boo e, isChecked ? CheckState.Checked : CheckState.Unchecked); - Assert.Equal(expectedAccent, CountPixels(bitmap, Color.Red) > 0); + Color backgroundPixel = bitmap.GetPixel(control.Width - 2, control.Height / 2); + Assert.Equal(backgroundColor.ToArgb(), backgroundPixel.ToArgb()); + Assert.Equal( + expectedAccent, + CountPixels(bitmap, accentColor, channelTolerance: 24) > 0); + } + + [WinFormsFact] + public void RadioButton_ModernGlyph_UsesExplicitBackColorWhenVisualStyleBackgroundDisabled() + { + using Panel parent = new() { BackColor = Color.White }; + using RadioButton control = new() + { + BackColor = Color.Aqua, + Checked = false, + Text = string.Empty, + Size = new Size(40, 24), + VisualStylesMode = VisualStylesMode.Net11 + }; + + parent.Controls.Add(control); + + using Bitmap bitmap = new(control.Width, control.Height); + using Graphics graphics = Graphics.FromImage(bitmap); + PaintEventArgs e = new(graphics, control.ClientRectangle); + + control.CreateStandardAdapter().PaintUp(e, CheckState.Unchecked); + + Color backgroundPixel = bitmap.GetPixel(control.Width - 2, control.Height / 2); + Assert.Equal(Color.Aqua.ToArgb(), backgroundPixel.ToArgb()); + } + + [WinFormsFact] + public void RadioButton_ModernGlyph_UsesTranslucentBackColorWhenVisualStyleBackgroundEnabled() + { + Color parentBackColor = Color.White; + Color translucentBackColor = Color.FromArgb(128, Color.Aqua); + + using Panel parent = new() { BackColor = parentBackColor }; + using RadioButton control = new() + { + BackColor = translucentBackColor, + UseVisualStyleBackColor = true, + Checked = false, + Text = string.Empty, + Size = new Size(40, 24), + VisualStylesMode = VisualStylesMode.Net11 + }; + + parent.Controls.Add(control); + + using Bitmap bitmap = new(control.Width, control.Height); + using Graphics graphics = Graphics.FromImage(bitmap); + graphics.Clear(parentBackColor); + PaintEventArgs e = new(graphics, control.ClientRectangle); + + control.CreateStandardAdapter().PaintUp(e, CheckState.Unchecked); + + Color backgroundPixel = bitmap.GetPixel(control.Width - 2, control.Height / 2); + Color expected = BlendColors(parentBackColor, translucentBackColor); + Assert.InRange(Math.Abs(backgroundPixel.R - expected.R), 0, 1); + Assert.InRange(Math.Abs(backgroundPixel.G - expected.G), 0, 1); + Assert.InRange(Math.Abs(backgroundPixel.B - expected.B), 0, 1); } [WinFormsFact] @@ -373,6 +442,18 @@ public void RadioButton_ModernGlyph_EndAnimation_StopsAndSettles() private static int CountPixels(Bitmap bitmap, Color color) => CountPixels(bitmap, color, channelTolerance: 0); + private static Color BlendColors(Color background, Color overlay) + { + int alpha = overlay.A; + int inverseAlpha = byte.MaxValue - alpha; + + int red = ((overlay.R * alpha) + (background.R * inverseAlpha) + 127) / 255; + int green = ((overlay.G * alpha) + (background.G * inverseAlpha) + 127) / 255; + int blue = ((overlay.B * alpha) + (background.B * inverseAlpha) + 127) / 255; + + return Color.FromArgb(red, green, blue); + } + private static int CountPixels(Bitmap bitmap, Color color, int channelTolerance) { int count = 0;