From 38b429d5206c11a305fefa3dcc000908d3a4513b Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Thu, 21 May 2026 13:47:19 +0800 Subject: [PATCH 1/4] Fix ProgressBar dark mode rendering inconsistency across style variants --- .../Forms/Controls/ProgressBar/ProgressBar.cs | 79 +++++++++++++------ 1 file changed, 53 insertions(+), 26 deletions(-) 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..71ee1d20529 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; @@ -74,26 +75,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)] @@ -133,6 +114,8 @@ public ProgressBarStyle Style if (IsHandleCreated) { RecreateHandle(); + // Re-apply theming after handle recreation + ApplyTheming(); } if (_style == ProgressBarStyle.Marquee) @@ -349,7 +332,8 @@ protected override void OnBackColorChanged(EventArgs e) base.OnBackColorChanged(e); if (IsHandleCreated) { - PInvokeCore.SendMessage(this, PInvoke.PBM_SETBKCOLOR, 0, BackColor.ToWin32()); + ApplyTheming(); + PInvokeCore.SendMessage(this, PInvoke.PBM_SETBKCOLOR, 0, GetEffectiveBackColor().ToWin32()); } } @@ -358,7 +342,8 @@ protected override void OnForeColorChanged(EventArgs e) base.OnForeColorChanged(e); if (IsHandleCreated) { - PInvokeCore.SendMessage(this, PInvoke.PBM_SETBARCOLOR, 0, ForeColor.ToWin32()); + ApplyTheming(); + PInvokeCore.SendMessage(this, PInvoke.PBM_SETBARCOLOR, 0, GetEffectiveForeColor().ToWin32()); } } @@ -603,13 +588,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(); @@ -703,8 +692,46 @@ private void UserPreferenceChangedHandler(object o, UserPreferenceChangedEventAr { if (IsHandleCreated) { - PInvokeCore.SendMessage(this, PInvoke.PBM_SETBARCOLOR, 0, ForeColor.ToWin32()); - PInvokeCore.SendMessage(this, PInvoke.PBM_SETBKCOLOR, 0, BackColor.ToWin32()); + 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 : SystemColors.Control; + } + + private Color GetEffectiveForeColor() + { + if (ShouldSerializeForeColor()) + { + return ForeColor; + } + + return s_defaultForeColor; + } + + private void ApplyTheming() + { + if (!IsHandleCreated) + { + return; + } + + if (Application.IsDarkModeEnabled || ShouldSerializeBackColor() || ShouldSerializeForeColor()) + { + // 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, " ", " "); } } From a2eeaffe243ccb2f5f9666f50d652cb418282d18 Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Fri, 22 May 2026 15:42:50 +0800 Subject: [PATCH 2/4] Add test and update issues in comments --- .../Forms/Controls/ProgressBar/ProgressBar.cs | 23 ++-- .../System/Windows/Forms/ProgressBarTests.cs | 102 ++++++++++++++++++ 2 files changed, 110 insertions(+), 15 deletions(-) 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 71ee1d20529..80b9a8d1f39 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 @@ -72,11 +72,6 @@ protected override CreateParams CreateParams } } - protected override void OnCreateControl() - { - base.OnCreateControl(); - } - [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public override bool AllowDrop @@ -332,7 +327,6 @@ protected override void OnBackColorChanged(EventArgs e) base.OnBackColorChanged(e); if (IsHandleCreated) { - ApplyTheming(); PInvokeCore.SendMessage(this, PInvoke.PBM_SETBKCOLOR, 0, GetEffectiveBackColor().ToWin32()); } } @@ -342,7 +336,6 @@ protected override void OnForeColorChanged(EventArgs e) base.OnForeColorChanged(e); if (IsHandleCreated) { - ApplyTheming(); PInvokeCore.SendMessage(this, PInvoke.PBM_SETBARCOLOR, 0, GetEffectiveForeColor().ToWin32()); } } @@ -706,17 +699,12 @@ private Color GetEffectiveBackColor() return BackColor; } - return Application.IsDarkModeEnabled ? s_defaultDarkModeBackColor : SystemColors.Control; + return Application.IsDarkModeEnabled ? s_defaultDarkModeBackColor : BackColor; } private Color GetEffectiveForeColor() { - if (ShouldSerializeForeColor()) - { - return ForeColor; - } - - return s_defaultForeColor; + return ShouldSerializeForeColor() ? ForeColor : s_defaultForeColor; } private void ApplyTheming() @@ -726,13 +714,18 @@ private void ApplyTheming() return; } - if (Application.IsDarkModeEnabled || ShouldSerializeBackColor() || ShouldSerializeForeColor()) + 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 + { + // 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); + } } } From 1d608175e1b22bc381ac36085698e0c9cfee57d4 Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Fri, 22 May 2026 17:59:57 +0800 Subject: [PATCH 3/4] limit UserPreferenceChanged handling to theme-related categories --- .../Forms/Controls/ProgressBar/ProgressBar.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) 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 80b9a8d1f39..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 @@ -683,13 +683,21 @@ private void UpdatePos() /// private void UserPreferenceChangedHandler(object o, UserPreferenceChangedEventArgs e) { - if (IsHandleCreated) + if (!IsHandleCreated) { - ApplyTheming(); + return; + } - PInvokeCore.SendMessage(this, PInvoke.PBM_SETBARCOLOR, 0, GetEffectiveForeColor().ToWin32()); - PInvokeCore.SendMessage(this, PInvoke.PBM_SETBKCOLOR, 0, GetEffectiveBackColor().ToWin32()); + // 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() From 0705f6756158103d77eb82d488cb8157ee789725 Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Mon, 25 May 2026 10:04:51 +0800 Subject: [PATCH 4/4] Removing Symbol 'System.Windows.Forms.ProgressBar.OnHandleControl()'from PublicAPI --- src/System.Windows.Forms/PublicAPI.Shipped.txt | 1 - 1 file changed, 1 deletion(-) 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