diff --git a/src/System.Windows.Forms/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/System/Windows/Forms/Control.cs index 1dad772dc70..d7c8f789b59 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Control.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Control.cs @@ -899,10 +899,18 @@ public virtual partial VisualStylesMode VisualStylesMode VisualStylesMode oldEffectiveValue = EffectiveVisualStylesMode; - // Inherit was requested explicitly, or the requested value matches the ambient (parent) value: - // drop any local override so the value is inherited again. + bool hadLocalOverride = Properties.TryGetValue( + s_visualStylesModeProperty, + out VisualStylesMode existingRawValue) + && existingRawValue != VisualStylesMode.Inherit; + + // Inherit always clears the local override. Setting to the ambient (parent) value clears only when + // transitioning an existing local override back to ambient. if (value == VisualStylesMode.Inherit - || (ParentInternal is { } parent && parent.ResolvedVisualStylesMode == value)) + || (hadLocalOverride + && existingRawValue != value + && ParentInternal is { } parent + && parent.ResolvedVisualStylesMode == value)) { Properties.RemoveValue(s_visualStylesModeProperty); } diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/ButtonDarkModeAdapter.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/ButtonDarkModeAdapter.cs index d02787f52cd..f331f666bbb 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/ButtonDarkModeAdapter.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/ButtonDarkModeAdapter.cs @@ -85,24 +85,40 @@ private Color GetButtonBackColor(PushButtonState state) { Color backColor; - if (Control.BackColor != Forms.Control.DefaultBackColor) + // Net11 default buttons must always use the accent-driven palette, even when BackColor is set. + if (_modern && Control.IsDefault) { backColor = ButtonDarkModeRenderer.GetBackgroundColor( state, - Control.IsDefault, - Control.BackColor); + isDefault: true, + customBaseColor: Color.Empty); + } + else + { + bool hasExplicitBackColor = Control.ShouldSerializeBackColor(); + bool hasUsableAmbientBackColor = _modern + && !Control.BackColor.HasTransparency() + && Control.BackColor != Forms.Control.DefaultBackColor; - if (IsHighContrastHighlighted()) + if (hasExplicitBackColor || hasUsableAmbientBackColor) + { + backColor = ButtonDarkModeRenderer.GetBackgroundColor( + state, + Control.IsDefault, + Control.BackColor); + } + else { - backColor = SystemColors.HighlightText; + backColor = ButtonDarkModeRenderer.GetBackgroundColor( + state, + Control.IsDefault, + customBaseColor: Color.Empty); } } - else + + if (IsHighContrastHighlighted()) { - backColor = ButtonDarkModeRenderer.GetBackgroundColor( - state, - Control.IsDefault, - customBaseColor: Color.Empty); + backColor = SystemColors.HighlightText; } if (_animateBackgroundColors) diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ButtonVisualStylesTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ButtonVisualStylesTests.cs index d4d2d67d00d..3da46a65128 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ButtonVisualStylesTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ButtonVisualStylesTests.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. #nullable disable @@ -121,6 +121,71 @@ public void ModernGlyphControl_ThemedTabPage_PaintsOpaqueBackground(Type control Assert.Equal(255, actual.GetPixel(control.Width - 2, 2).A); } + [WinFormsTheory] + [InlineData(false)] + [InlineData(true)] + public void Button_ModernDefaultState_UsesAccent_WhenHostedOnContainer(bool hostedOnTabPage) + { + if (!Application.RenderWithVisualStyles) + { + return; + } + + using Form form = new() { VisualStylesMode = VisualStylesMode.Net11 }; + using Button button = new() + { + FlatStyle = FlatStyle.Standard, + Size = new Size(120, 32), + Text = string.Empty, + VisualStylesMode = VisualStylesMode.Inherit + }; + + if (hostedOnTabPage) + { + using TabControl tabControl = new() { Size = new Size(180, 120) }; + using TabPage tabPage = new() + { + Size = tabControl.Size, + UseVisualStyleBackColor = true + }; + + tabPage.Controls.Add(button); + tabControl.TabPages.Add(tabPage); + form.Controls.Add(tabControl); + form.AcceptButton = button; + form.CreateControl(); + tabControl.CreateControl(); + tabPage.CreateControl(); + button.CreateControl(); + + using Bitmap actual = new(button.Width, button.Height); + button.DrawToBitmap(actual, new Rectangle(Point.Empty, button.Size)); + + Color expected = ModernButtonColorMath.GetDefaultButtonColor( + Application.SystemVisualSettings.AccentColor, + VisualStyles.PushButtonState.Normal); + Color center = actual.GetPixel(button.Width / 2, button.Height / 2); + + Assert.Equal(expected.ToArgb(), center.ToArgb()); + return; + } + + form.Controls.Add(button); + form.AcceptButton = button; + form.CreateControl(); + button.CreateControl(); + + using Bitmap bitmap = new(button.Width, button.Height); + button.DrawToBitmap(bitmap, new Rectangle(Point.Empty, button.Size)); + + Color expectedColor = ModernButtonColorMath.GetDefaultButtonColor( + Application.SystemVisualSettings.AccentColor, + VisualStyles.PushButtonState.Normal); + Color actualColor = bitmap.GetPixel(button.Width / 2, button.Height / 2); + + Assert.Equal(expectedColor.ToArgb(), actualColor.ToArgb()); + } + [WinFormsFact] public void Button_VisualStylesMode_ChangedToNet11_Invalidates() { @@ -509,6 +574,72 @@ public void ModernButtonColorMath_ExplicitBackColorIsTheRenderedBase() button.FlatAppearance.MouseOverBackColor); } + [WinFormsFact] + public void ButtonDarkModeAdapter_NonNet11_ExplicitBackColor_DefaultStatePreservesBackColor() + { + using Button button = new() + { + FlatStyle = FlatStyle.Standard, + VisualStylesMode = VisualStylesMode.Classic, + BackColor = Color.FromArgb(10, 20, 30) + }; + button.NotifyDefault(true); + + ButtonInternal.ButtonDarkModeAdapter adapter = new(button); + dynamic accessor = adapter.TestAccessor.Dynamic; + + Color actual = (Color)accessor.GetButtonBackColor(VisualStyles.PushButtonState.Normal); + + Assert.Equal(button.BackColor, actual); + } + + [WinFormsFact] + public void ButtonDarkModeAdapter_ClassicInNet11Hierarchy_ExplicitBackColor_DefaultStatePreservesBackColor() + { + using Form form = new() { VisualStylesMode = VisualStylesMode.Net11 }; + using Button button = new() + { + FlatStyle = FlatStyle.Standard, + VisualStylesMode = VisualStylesMode.Classic, + BackColor = Color.FromArgb(10, 20, 30) + }; + form.Controls.Add(button); + form.AcceptButton = button; + button.NotifyDefault(true); + + ButtonInternal.ButtonDarkModeAdapter adapter = new(button); + dynamic accessor = adapter.TestAccessor.Dynamic; + + Color actual = (Color)accessor.GetButtonBackColor(VisualStyles.PushButtonState.Normal); + + Assert.Equal(button.BackColor, actual); + } + + [WinFormsFact] + public void ButtonDarkModeAdapter_ClassicInNet11Hierarchy_InheritedBackColor_DoesNotUseAmbientAsCustomBase() + { + using Form form = new() + { + VisualStylesMode = VisualStylesMode.Net11, + BackColor = Color.FromArgb(31, 63, 95) + }; + using Button button = new() + { + FlatStyle = FlatStyle.Standard, + VisualStylesMode = VisualStylesMode.Classic, + UseVisualStyleBackColor = true + }; + form.Controls.Add(button); + button.NotifyDefault(true); + + ButtonInternal.ButtonDarkModeAdapter adapter = new(button); + dynamic accessor = adapter.TestAccessor.Dynamic; + + Color actual = (Color)accessor.GetButtonBackColor(VisualStyles.PushButtonState.Normal); + + Assert.NotEqual(form.BackColor, actual); + } + [WinFormsFact] public void ModernButtonDarkModeRenderer_CornerRadiusDependsOnFocusAndDefaultState() { @@ -545,6 +676,29 @@ public void ModernButtonDarkModeRenderer_ModernStateDefaultsUseAccentAndExplicit Assert.Equal(Color.Blue, renderer.GetBackgroundColor(VisualStyles.PushButtonState.Hot, false, Color.Empty)); } + [WinFormsTheory] + [InlineData(VisualStyles.PushButtonState.Normal)] + [InlineData(VisualStyles.PushButtonState.Hot)] + [InlineData(VisualStyles.PushButtonState.Pressed)] + public void ButtonDarkModeAdapter_Net11_DefaultStateWithExplicitBackColor_UsesAccent( + VisualStyles.PushButtonState state) + { + using Button button = new() + { + FlatStyle = FlatStyle.Standard, + VisualStylesMode = VisualStylesMode.Net11, + BackColor = Color.FromArgb(16, 48, 80) + }; + button.NotifyDefault(true); + ButtonInternal.ButtonDarkModeAdapter adapter = new(button); + dynamic accessor = adapter.TestAccessor.Dynamic; + Color accent = Application.SystemVisualSettings.AccentColor; + + Color actual = (Color)accessor.GetButtonBackColor(state); + + Assert.Equal(ModernButtonColorMath.GetDefaultButtonColor(accent, state), actual); + } + [WinFormsTheory] [InlineData(FlatStyle.Standard, 96)] [InlineData(FlatStyle.Standard, 144)] diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ControlTests.VisualStylesMode.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ControlTests.VisualStylesMode.cs index 61bbbf947bb..78369c631ba 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ControlTests.VisualStylesMode.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ControlTests.VisualStylesMode.cs @@ -193,6 +193,25 @@ public void Control_VisualStylesMode_AmbientBehaviorTest() Assert.Equal(VisualStylesMode.Classic, child.EffectiveVisualStylesModeAccessor); } + [WinFormsFact] + public void Control_VisualStylesMode_FirstSetToAmbientValue_PreservesLocalOverride() + { + using SubControlWithVisualStyles parent = new() + { + VisualStylesMode = VisualStylesMode.Classic + }; + using SubControlWithVisualStyles child = new(); + parent.Controls.Add(child); + + child.VisualStylesMode = VisualStylesMode.Classic; + Assert.Equal(VisualStylesMode.Classic, child.VisualStylesMode); + + parent.VisualStylesMode = VisualStylesMode.Net11; + + Assert.Equal(VisualStylesMode.Classic, child.VisualStylesMode); + Assert.Equal(VisualStylesMode.Classic, child.EffectiveVisualStylesModeAccessor); + } + [WinFormsFact] public void Control_VisualStylesMode_ReparentingToDifferentMode_RaisesChanged() {