Skip to content
Open
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
14 changes: 11 additions & 3 deletions src/System.Windows.Forms/System/Windows/Forms/Control.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This change makes a Classic button ignore a non-default BackColor inherited from its parent. Before this PR, Control.BackColor—including an ambient value—was passed to the renderer whenever it differed from DefaultBackColor. After this change, ambient colors are accepted only when _modern is true. Consequently, a Classic button on a custom-colored container now renders with the default palette instead of the inherited color. This appears to be a behavioral regression and contradicts the PR description that the Classic path retains its original behavior.

&& 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -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)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
Loading