diff --git a/src/System.Windows.Forms/PublicAPI.Shipped.txt b/src/System.Windows.Forms/PublicAPI.Shipped.txt index c8734dec7e8..0b89c9f8a14 100644 --- a/src/System.Windows.Forms/PublicAPI.Shipped.txt +++ b/src/System.Windows.Forms/PublicAPI.Shipped.txt @@ -1606,7 +1606,6 @@ override System.Windows.Forms.ProgressBar.DoubleBuffered.set -> void override System.Windows.Forms.ProgressBar.Font.get -> System.Drawing.Font! override System.Windows.Forms.ProgressBar.Font.set -> void override System.Windows.Forms.ProgressBar.OnBackColorChanged(System.EventArgs! e) -> void -override System.Windows.Forms.ProgressBar.OnCreateControl() -> void override System.Windows.Forms.ProgressBar.OnForeColorChanged(System.EventArgs! e) -> void override System.Windows.Forms.ProgressBar.OnHandleCreated(System.EventArgs! e) -> void override System.Windows.Forms.ProgressBar.OnHandleDestroyed(System.EventArgs! e) -> void diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ProgressBar/ProgressBar.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ProgressBar/ProgressBar.cs index 9a5cdde50ca..8a86d86aa37 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ProgressBar/ProgressBar.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ProgressBar/ProgressBar.cs @@ -26,6 +26,7 @@ public partial class ProgressBar : Control private int _marqueeAnimationSpeed = 100; private static readonly Color s_defaultForeColor = SystemColors.Highlight; + private static readonly Color s_defaultDarkModeBackColor = SystemColors.ControlText; private ProgressBarStyle _style = ProgressBarStyle.Blocks; @@ -71,31 +72,6 @@ protected override CreateParams CreateParams } } - protected override void OnCreateControl() - { - base.OnCreateControl(); - - // If SystemColorMode is enabled, we need to disable the Visual Styles - // so Windows allows setting Fore- and Background color. - // There are more ideal ways imaginable, but this does the trick for now. - - if (Application.IsDarkModeEnabled) - { - if (!ShouldSerializeBackColor()) - { - BackColor = SystemColors.ControlDarkDark; - } - - if (!ShouldSerializeForeColor()) - { - ForeColor = SystemColors.Highlight; - } - - // Disables Visual Styles for the ProgressBar. - PInvoke.SetWindowTheme(HWND, " ", " "); - } - } - [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public override bool AllowDrop @@ -133,6 +109,8 @@ public ProgressBarStyle Style if (IsHandleCreated) { RecreateHandle(); + // Re-apply theming after handle recreation + ApplyTheming(); } if (_style == ProgressBarStyle.Marquee) @@ -349,7 +327,7 @@ protected override void OnBackColorChanged(EventArgs e) base.OnBackColorChanged(e); if (IsHandleCreated) { - PInvokeCore.SendMessage(this, PInvoke.PBM_SETBKCOLOR, 0, BackColor.ToWin32()); + PInvokeCore.SendMessage(this, PInvoke.PBM_SETBKCOLOR, 0, GetEffectiveBackColor().ToWin32()); } } @@ -358,7 +336,7 @@ protected override void OnForeColorChanged(EventArgs e) base.OnForeColorChanged(e); if (IsHandleCreated) { - PInvokeCore.SendMessage(this, PInvoke.PBM_SETBARCOLOR, 0, ForeColor.ToWin32()); + PInvokeCore.SendMessage(this, PInvoke.PBM_SETBARCOLOR, 0, GetEffectiveForeColor().ToWin32()); } } @@ -603,13 +581,17 @@ public void Increment(int value) protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); + + ApplyTheming(); + if (IsHandleCreated) { PInvokeCore.SendMessage(this, PInvoke.PBM_SETRANGE32, (WPARAM)_minimum, (LPARAM)_maximum); PInvokeCore.SendMessage(this, PInvoke.PBM_SETSTEP, (WPARAM)_step); PInvokeCore.SendMessage(this, PInvoke.PBM_SETPOS, (WPARAM)_value); - PInvokeCore.SendMessage(this, PInvoke.PBM_SETBKCOLOR, (WPARAM)0, (LPARAM)BackColor); - PInvokeCore.SendMessage(this, PInvoke.PBM_SETBARCOLOR, (WPARAM)0, (LPARAM)ForeColor); + + PInvokeCore.SendMessage(this, PInvoke.PBM_SETBKCOLOR, 0, GetEffectiveBackColor().ToWin32()); + PInvokeCore.SendMessage(this, PInvoke.PBM_SETBARCOLOR, 0, GetEffectiveForeColor().ToWin32()); } StartMarquee(); @@ -701,10 +683,56 @@ private void UpdatePos() /// private void UserPreferenceChangedHandler(object o, UserPreferenceChangedEventArgs e) { - if (IsHandleCreated) + if (!IsHandleCreated) + { + return; + } + + // Only react to changes that can affect colors or theme changes. + if (e.Category is not UserPreferenceCategory.Color and not UserPreferenceCategory.General) + { + return; + } + + ApplyTheming(); + + PInvokeCore.SendMessage(this, PInvoke.PBM_SETBARCOLOR, 0, GetEffectiveForeColor().ToWin32()); + PInvokeCore.SendMessage(this, PInvoke.PBM_SETBKCOLOR, 0, GetEffectiveBackColor().ToWin32()); + } + + private Color GetEffectiveBackColor() + { + if (ShouldSerializeBackColor()) + { + return BackColor; + } + + return Application.IsDarkModeEnabled ? s_defaultDarkModeBackColor : BackColor; + } + + private Color GetEffectiveForeColor() + { + return ShouldSerializeForeColor() ? ForeColor : s_defaultForeColor; + } + + private void ApplyTheming() + { + if (!IsHandleCreated) + { + return; + } + + if (Application.IsDarkModeEnabled) + { + // In dark mode on newer Windows builds, style switching can produce mixed rendering + // across Blocks/Continuous/Marquee. Disable visual styles and drive colors via PBM_SET*COLOR + // for consistent appearance. + PInvoke.SetWindowTheme(HWND, " ", " "); + } + else { - PInvokeCore.SendMessage(this, PInvoke.PBM_SETBARCOLOR, 0, ForeColor.ToWin32()); - PInvokeCore.SendMessage(this, PInvoke.PBM_SETBKCOLOR, 0, BackColor.ToWin32()); + // Restore default theming when dark mode and custom colors are no longer active. + PInvoke.SetWindowTheme(HWND, (PCWSTR)null, (PCWSTR)null); } } diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ProgressBarTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ProgressBarTests.cs index 31fac31cc65..e09dd9a6a5e 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ProgressBarTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ProgressBarTests.cs @@ -2165,6 +2165,69 @@ public void ProgressBar_OnHandleCreated_InvokeWithHandle_CallsHandleCreated(Prog Assert.True(control.IsHandleCreated); } + [WinFormsFact] + public void ProgressBar_RecreateHandle_WithAmbientBackColorInLightMode_SendsAmbientBackColor() + { + using Form parent = new() + { + BackColor = Color.OrangeRed + }; + using SubProgressBar progressBar = new(); + parent.Controls.Add(progressBar); + + parent.CreateControl(); + Assert.NotEqual(IntPtr.Zero, progressBar.Handle); + Assert.Equal(parent.BackColor, progressBar.BackColor); + + progressBar.ResetBkColorTracking(); + progressBar.RecreateHandle(); + + Assert.True(progressBar.SetBkColorMessageCount > 0); + Assert.Equal(ColorTranslator.ToWin32(progressBar.BackColor), progressBar.LastSetBkColorMessage); + } + + [WinFormsFact] + public void ProgressBar_StyleSwitch_WithCustomBackColorInLightMode_SendsCustomBackColor() + { + using Form parent = new(); + using SubProgressBar progressBar = new() + { + BackColor = Color.White, + Style = ProgressBarStyle.Blocks + }; + parent.Controls.Add(progressBar); + + parent.CreateControl(); + Assert.NotEqual(IntPtr.Zero, progressBar.Handle); + + progressBar.ResetColorTracking(); + progressBar.Style = ProgressBarStyle.Continuous; + + Assert.True(progressBar.SetBkColorMessageCount > 0); + Assert.Equal(ColorTranslator.ToWin32(progressBar.BackColor), progressBar.LastSetBkColorMessage); + } + + [WinFormsFact] + public void ProgressBar_StyleSwitch_WithCustomForeColorInLightMode_SendsCustomForeColor() + { + using Form parent = new(); + using SubProgressBar progressBar = new() + { + ForeColor = Color.LimeGreen, + Style = ProgressBarStyle.Blocks + }; + parent.Controls.Add(progressBar); + + parent.CreateControl(); + Assert.NotEqual(IntPtr.Zero, progressBar.Handle); + + progressBar.ResetColorTracking(); + progressBar.Style = ProgressBarStyle.Continuous; + + Assert.True(progressBar.SetBarColorMessageCount > 0); + Assert.Equal(ColorTranslator.ToWin32(progressBar.ForeColor), progressBar.LastSetBarColorMessage); + } + [WinFormsTheory] [NewAndDefaultData] public void ProgressBar_OnHandleDestroyed_Invoke_CallsHandleDestroyed(EventArgs eventArgs) @@ -2641,6 +2704,8 @@ public class SubProgressBar : ProgressBar public new void CreateHandle() => base.CreateHandle(); + public new void RecreateHandle() => base.RecreateHandle(); + public new AutoSizeMode GetAutoSizeMode() => base.GetAutoSizeMode(); public new bool GetStyle(ControlStyles flag) => base.GetStyle(flag); @@ -2674,5 +2739,42 @@ public class SubProgressBar : ProgressBar public new void OnRightToLeftLayoutChanged(EventArgs e) => base.OnRightToLeftLayoutChanged(e); public new void SetStyle(ControlStyles flag, bool value) => base.SetStyle(flag, value); + + public int? LastSetBkColorMessage { get; private set; } + + public int? LastSetBarColorMessage { get; private set; } + + public int SetBkColorMessageCount { get; private set; } + + public int SetBarColorMessageCount { get; private set; } + + public void ResetColorTracking() + { + LastSetBkColorMessage = null; + LastSetBarColorMessage = null; + SetBkColorMessageCount = 0; + SetBarColorMessageCount = 0; + } + + public void ResetBkColorTracking() + { + ResetColorTracking(); + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == (int)PInvoke.PBM_SETBKCOLOR) + { + SetBkColorMessageCount++; + LastSetBkColorMessage = unchecked((int)m.LParam); + } + else if (m.Msg == (int)PInvoke.PBM_SETBARCOLOR) + { + SetBarColorMessageCount++; + LastSetBarColorMessage = unchecked((int)m.LParam); + } + + base.WndProc(ref m); + } } }