diff --git a/src/System.Windows.Forms/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/System/Windows/Forms/Control.cs
index 1dad772dc70..68354fae131 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Control.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Control.cs
@@ -899,10 +899,10 @@ public virtual partial VisualStylesMode VisualStylesMode
VisualStylesMode oldEffectiveValue = EffectiveVisualStylesMode;
- // Inherit was requested explicitly, or the requested value matches the ambient (parent) value:
+ // Inherit was requested explicitly, or the requested value matches the uncoerced ambient value:
// drop any local override so the value is inherited again.
if (value == VisualStylesMode.Inherit
- || (ParentInternal is { } parent && parent.ResolvedVisualStylesMode == value))
+ || (ParentInternal is { } parent && parent.UncoercedVisualStylesMode == value))
{
Properties.RemoveValue(s_visualStylesModeProperty);
}
@@ -927,18 +927,30 @@ private bool ShouldSerializeVisualStylesMode()
private void ResetVisualStylesMode()
=> VisualStylesMode = VisualStylesMode.Inherit;
- private VisualStylesMode ResolvedVisualStylesMode
+ private VisualStylesMode UncoercedVisualStylesMode
{
get
{
VisualStylesMode value = VisualStylesMode;
return value == VisualStylesMode.Inherit
- ? ParentInternal?.ResolvedVisualStylesMode ?? DefaultVisualStylesMode
+ ? ParentInternal?.UncoercedVisualStylesMode ?? DefaultVisualStylesMode
: value;
}
}
+ private VisualStylesMode ResolvedVisualStylesMode
+ => GetSupportedVisualStylesMode(
+ VisualStylesMode == VisualStylesMode.Inherit
+ ? ParentInternal?.ResolvedVisualStylesMode ?? DefaultVisualStylesMode
+ : VisualStylesMode);
+
+ ///
+ /// Coerces a requested visual styles mode to one supported by this control.
+ ///
+ private protected virtual VisualStylesMode GetSupportedVisualStylesMode(VisualStylesMode mode)
+ => mode;
+
///
/// Gets the renderer-authoritative that controls must honor when deciding
/// or paint behavior, after applying the High Contrast and disabled clamps.
@@ -7446,28 +7458,60 @@ protected virtual void OnParentVisualStylesModeChanged(EventArgs e)
return;
}
+ VisualStylesModeChangeEventArgs? transition = e as VisualStylesModeChangeEventArgs;
+ if (transition is not null
+ && (!transition.IsCurrent
+ || ParentInternal?.EffectiveVisualStylesMode != transition.NewEffectiveVisualStylesMode))
+ {
+ return;
+ }
+
if (Properties.ContainsKey(s_visualStylesModeProperty))
{
if (Properties.GetValueOrDefault(s_visualStylesModeProperty)
- == ParentInternal?.ResolvedVisualStylesMode)
+ != ParentInternal?.UncoercedVisualStylesMode)
{
- // Same as the parent value, make it ambient again by removing it.
- Properties.RemoveValue(s_visualStylesModeProperty);
+ // A local value isolates this subtree from parent changes.
+ return;
+ }
+
+ VisualStylesMode oldEffectiveVisualStylesMode = EffectiveVisualStylesMode;
+
+ // Same as the parent value, make it ambient again by removing it.
+ Properties.RemoveValue(s_visualStylesModeProperty);
+
+ VisualStylesMode newEffectiveVisualStylesMode = EffectiveVisualStylesMode;
+ if (oldEffectiveVisualStylesMode == newEffectiveVisualStylesMode)
+ {
+ return;
}
- // A local value isolates this subtree from parent changes. If the local value matched the
- // parent's new value, removing it preserves the effective value while making it ambient again.
+ OnVisualStylesModeChanged(
+ transition?.CreateForControl(
+ this,
+ oldEffectiveVisualStylesMode,
+ newEffectiveVisualStylesMode)
+ ?? e);
+
return;
}
- if (e is VisualStylesModeChangeEventArgs transition
- && (!transition.IsCurrent
- || ParentInternal?.EffectiveVisualStylesMode != transition.NewEffectiveVisualStylesMode))
+ if (transition is not null)
{
+ VisualStylesModeChangeEventArgs transitionForControl = transition.CreateForControl(this);
+ if (transitionForControl.OldEffectiveVisualStylesMode
+ != transitionForControl.NewEffectiveVisualStylesMode)
+ {
+ OnVisualStylesModeChanged(transitionForControl);
+ }
+ else if (ChildControls is { } children)
+ {
+ CascadeVisualStylesModeChanged(children, transitionForControl, transitionForControl);
+ }
+
return;
}
- // In every other case we're going to raise the event.
OnVisualStylesModeChanged(e);
}
@@ -7534,19 +7578,34 @@ public bool IsCurrent
public VisualStylesModeChangeEventArgs CreateForControl(Control control)
{
- if (SystemVisualSettingsTransition is not { } systemVisualSettingsTransition)
+ if (SystemVisualSettingsTransition is { } systemVisualSettingsTransition)
{
- return this;
+ return new(
+ _state,
+ control,
+ control.Properties.GetValueOrDefault(s_visualStylesModeChangeVersionProperty, 0),
+ control.GetEffectiveVisualStylesMode(systemVisualSettingsTransition.OldSettings.HighContrastEnabled),
+ control.GetEffectiveVisualStylesMode(systemVisualSettingsTransition.NewSettings.HighContrastEnabled),
+ systemVisualSettingsTransition);
}
- return new(
+ return CreateForControl(
+ control,
+ control.GetSupportedVisualStylesMode(OldEffectiveVisualStylesMode),
+ control.GetSupportedVisualStylesMode(NewEffectiveVisualStylesMode));
+ }
+
+ public VisualStylesModeChangeEventArgs CreateForControl(
+ Control control,
+ VisualStylesMode oldEffectiveVisualStylesMode,
+ VisualStylesMode newEffectiveVisualStylesMode)
+ => new(
_state,
control,
control.Properties.GetValueOrDefault(s_visualStylesModeChangeVersionProperty, 0),
- control.GetEffectiveVisualStylesMode(systemVisualSettingsTransition.OldSettings.HighContrastEnabled),
- control.GetEffectiveVisualStylesMode(systemVisualSettingsTransition.NewSettings.HighContrastEnabled),
- systemVisualSettingsTransition);
- }
+ oldEffectiveVisualStylesMode,
+ newEffectiveVisualStylesMode,
+ systemVisualSettingsTransition: null);
public void PerformLayouts()
{
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridView.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridView.cs
index 5f1af6e83c4..d1189b02919 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridView.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridView.cs
@@ -16,6 +16,9 @@ namespace System.Windows.Forms;
[SRDescription(nameof(SR.DescriptionDataGridView))]
public partial class DataGridView : Control, ISupportInitialize
{
+ private protected override VisualStylesMode GetSupportedVisualStylesMode(VisualStylesMode mode)
+ => mode >= VisualStylesMode.Net11 ? VisualStylesMode.Classic : mode;
+
private static readonly object s_allowUserToAddRowsChangedEvent = new();
private static readonly object s_allowUserToDeleteRowsChangedEvent = new();
private static readonly object s_allowUserToOrderColumnsChangedEvent = new();
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStrip.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStrip.cs
index 6e0c698d38b..eb5205b5403 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStrip.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStrip.cs
@@ -23,6 +23,9 @@ namespace System.Windows.Forms;
[DefaultEvent(nameof(ItemClicked))]
public partial class ToolStrip : ScrollableControl, IArrangedElement, ISupportToolStripPanel
{
+ private protected override VisualStylesMode GetSupportedVisualStylesMode(VisualStylesMode mode)
+ => mode >= VisualStylesMode.Net11 ? VisualStylesMode.Classic : mode;
+
private static Size s_onePixel = new(1, 1);
internal static Point s_invalidMouseEnter = new(int.MaxValue, int.MaxValue);
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..45cb5c08d9d 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
@@ -496,6 +496,33 @@ public void Control_VisualStylesMode_ReentrantChange_SuppressesStaleChildCascade
Assert.Equal(1, childChangedCallCount);
}
+ [WinFormsFact]
+ public void Control_VisualStylesMode_LocalOverrideRemovedByCoercedParent_RaisesChangedAndCascades()
+ {
+ using DataGridView parent = new() { VisualStylesMode = VisualStylesMode.Disabled };
+ using SubControlWithVisualStyles child = new()
+ {
+ HighContrast = false,
+ VisualStylesMode = VisualStylesMode.Latest
+ };
+ using SubControlWithVisualStyles grandchild = new() { HighContrast = false };
+ child.Controls.Add(grandchild);
+ parent.Controls.Add(child);
+
+ int childChangedCallCount = 0;
+ child.VisualStylesModeChanged += (sender, e) => childChangedCallCount++;
+ int grandchildChangedCallCount = 0;
+ grandchild.VisualStylesModeChanged += (sender, e) => grandchildChangedCallCount++;
+
+ parent.VisualStylesMode = VisualStylesMode.Latest;
+
+ Assert.Equal(VisualStylesMode.Inherit, child.VisualStylesMode);
+ Assert.Equal(VisualStylesMode.Classic, child.EffectiveVisualStylesModeAccessor);
+ Assert.Equal(VisualStylesMode.Classic, grandchild.EffectiveVisualStylesModeAccessor);
+ Assert.Equal(1, childChangedCallCount);
+ Assert.Equal(1, grandchildChangedCallCount);
+ }
+
[WinFormsFact]
public void Appearance_ToggleSwitch_HasExpectedValue()
{
diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataGridViewTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataGridViewTests.cs
index d79cca0216b..dfae66ba01b 100644
--- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataGridViewTests.cs
+++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/DataGridViewTests.cs
@@ -38,6 +38,61 @@ public void DataGridView_Ctor_Default()
Assert.Same(control.RowTemplate, control.RowTemplate);
}
+ [WinFormsTheory]
+ [InlineData(VisualStylesMode.Net11)]
+ [InlineData(VisualStylesMode.Latest)]
+ public void DataGridView_VisualStylesMode_ModernMode_UsesClassicEffectiveMode(VisualStylesMode value)
+ {
+ using SubDataGridView control = new() { VisualStylesMode = value };
+
+ Assert.Equal(value, control.VisualStylesMode);
+ Assert.Equal(VisualStylesMode.Classic, control.EffectiveVisualStylesModeAccessor);
+ Assert.False(control.IsHandleCreated);
+ }
+
+ [WinFormsTheory]
+ [InlineData(VisualStylesMode.Net11)]
+ [InlineData(VisualStylesMode.Latest)]
+ public void DataGridView_VisualStylesMode_InheritedModernMode_UsesClassicEffectiveMode(VisualStylesMode value)
+ {
+ using Control parent = new() { VisualStylesMode = value };
+ using SubDataGridView control = new();
+ parent.Controls.Add(control);
+
+ Assert.Equal(VisualStylesMode.Inherit, control.VisualStylesMode);
+ Assert.Equal(VisualStylesMode.Classic, control.EffectiveVisualStylesModeAccessor);
+ Assert.False(control.IsHandleCreated);
+ }
+
+ [WinFormsFact]
+ public void DataGridView_VisualStylesMode_ParentChangesToModernMode_DoesNotRaiseChanged()
+ {
+ using Control parent = new() { VisualStylesMode = VisualStylesMode.Classic };
+ using SubDataGridView control = new();
+ parent.Controls.Add(control);
+ int callCount = 0;
+ control.VisualStylesModeChanged += (sender, e) => callCount++;
+
+ parent.VisualStylesMode = VisualStylesMode.Net11;
+
+ Assert.Equal(VisualStylesMode.Classic, control.EffectiveVisualStylesModeAccessor);
+ Assert.Equal(0, callCount);
+ }
+
+ [WinFormsFact]
+ public void DataGridView_VisualStylesMode_ExplicitClassic_RemainsLocalOverride()
+ {
+ using Control parent = new() { VisualStylesMode = VisualStylesMode.Net11 };
+ using SubDataGridView control = new();
+ parent.Controls.Add(control);
+ control.VisualStylesMode = VisualStylesMode.Classic;
+
+ parent.VisualStylesMode = VisualStylesMode.Disabled;
+
+ Assert.Equal(VisualStylesMode.Classic, control.VisualStylesMode);
+ Assert.Equal(VisualStylesMode.Classic, control.EffectiveVisualStylesModeAccessor);
+ }
+
private const int DefaultColumnHeadersHeight = 23;
public static IEnumerable