Skip to content

Commit e8ec828

Browse files
Fix ComboBox dark mode BackColor bug.
Implement dark mode fix for disabled RichTextBox.
1 parent bca4402 commit e8ec828

File tree

7 files changed

+120
-16
lines changed

7 files changed

+120
-16
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
override System.Windows.Forms.ComboBox.OnEnabledChanged(System.EventArgs! e) -> void
2+
override System.Windows.Forms.RichTextBox.OnEnabledChanged(System.EventArgs! e) -> void

src/System.Windows.Forms/System/Windows/Forms/Control.cs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,6 @@ public unsafe partial class Control :
298298

299299
internal byte LayoutSuspendCount { get; private set; }
300300

301-
// Flag for the youngest control in the descendant-inheritance hierarchy as the
302-
// ultimate truth if the control - by having set the ControlStyles. -
303-
// indicates to participate in automatic dark mode theming or not.
304-
internal bool? _darkModeRequestState;
305-
306301
/// <summary>
307302
/// Initializes a new instance of the <see cref="Control"/> class.
308303
/// </summary>
@@ -491,6 +486,32 @@ internal int OriginalDeviceDpiInternal
491486
}
492487
}
493488

489+
/// <summary>
490+
/// Caches whether the youngest control in the inheritance hierarchy has requested
491+
/// participation in automatic dark mode theming based on its <see cref="ControlStyles"/> settings.
492+
/// </summary>
493+
/// <remarks>
494+
/// <para>
495+
/// This property acts as a cache for the dark mode theming request state due to the architectural pattern
496+
/// of Control inheritance in WinForms. The constructor of <see cref="Control"/> calls <see cref="CreateParams"/>,
497+
/// which means that <see cref="CreateParams"/> in derived controls is invoked before their respective constructors run.
498+
/// </para>
499+
/// <para>
500+
/// As a result, if a control indicates its intent to participate in dark mode theming by setting
501+
/// <see cref="ControlStyles.ApplyThemingImplicitly"/>, we need to cache this state in this internal property
502+
/// rather than relying on the setting it does in its constructor. This is necessary because theming decisions
503+
/// and related logic may be triggered during the base <see cref="Control"/> construction process, before the
504+
/// derived control's constructor has a chance to execute.
505+
/// </para>
506+
/// <para>
507+
/// Furthermore, since base classes (such as <c>TextBoxBase</c> or <c>ButtonBase</c>) may request dark mode
508+
/// theming, but a more derived control might opt out, this <see cref="DarkModeRequestState"/> property serves
509+
/// as the definitive source of truth for a particular control's dark mode participation. It ensures that the
510+
/// most derived control's preference is respected, regardless of what its ancestors may have set.
511+
/// </para>
512+
/// </remarks>
513+
internal bool? DarkModeRequestState { get; set; }
514+
494515
/// <summary>
495516
/// The Accessibility Object for this Control
496517
/// </summary>
@@ -7384,7 +7405,7 @@ protected virtual void OnHandleCreated(EventArgs e)
73847405

73857406
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates.
73867407
if (Application.IsDarkModeEnabled
7387-
&& _darkModeRequestState is true
7408+
&& DarkModeRequestState is true
73887409
&& !RecreatingHandle)
73897410
{
73907411
_ = PInvoke.SetWindowTheme(
@@ -9349,7 +9370,7 @@ internal virtual void RecreateHandleCore()
93499370
// ensure that the theming is applied to all child controls as well.
93509371
#pragma warning disable WFO5001
93519372
if (Application.IsDarkModeEnabled
9352-
&& _darkModeRequestState is true)
9373+
&& DarkModeRequestState is true)
93539374
{
93549375
_ = PInvoke.SetWindowTheme(
93559376
hwnd: HWND,
@@ -10345,9 +10366,9 @@ protected void SetStyle(ControlStyles flag, bool value)
1034510366
// reset those settings - the source of truth ultimately is the value which gets set
1034610367
// for the first time a control sets or clears this style explicitly in CreateParams.
1034710368
if ((flag & ControlStyles.ApplyThemingImplicitly) == ControlStyles.ApplyThemingImplicitly
10348-
&& !_darkModeRequestState.HasValue)
10369+
&& !DarkModeRequestState.HasValue)
1034910370
{
10350-
_darkModeRequestState = value;
10371+
DarkModeRequestState = value;
1035110372
}
1035210373
}
1035310374
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
@@ -10412,7 +10433,7 @@ protected virtual void SetVisibleCore(bool value)
1041210433
// We shouldn't mess with the color mode if users haven't specifically set it.
1041310434
// https://github.com/dotnet/winforms/issues/12014
1041410435
// And we shouldn't prepare dark mode, if the form opted out.
10415-
if (value && Application.ColorModeSet && _darkModeRequestState is true)
10436+
if (value && Application.ColorModeSet && DarkModeRequestState is true)
1041610437
{
1041710438
PrepareDarkMode(HWND, Application.IsDarkModeEnabled);
1041810439
}

src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/Button.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ private protected override bool OwnerDraw
168168
&& Image is null
169169

170170
// ...the user wants to opt out of implicit DarkMode rendering.
171-
&& _darkModeRequestState is true
171+
&& DarkModeRequestState is true
172172

173173
// And all of this only counts for FlatStyle.Standard. For the
174174
// rest, we're using specific renderers anyway, which check

src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,15 +249,31 @@ public AutoCompleteStringCollection AutoCompleteCustomSource
249249
}
250250
}
251251

252+
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
252253
/// <summary>
253254
/// The background color of this control. This is an ambient property and
254255
/// will always return a non-null value.
255256
/// </summary>
256257
public override Color BackColor
257258
{
258-
get => ShouldSerializeBackColor() ? base.BackColor : SystemColors.Window;
259+
get
260+
{
261+
if (ShouldSerializeBackColor())
262+
{
263+
return base.BackColor;
264+
}
265+
else
266+
{
267+
return Application.IsDarkModeEnabled
268+
&& DarkModeRequestState is true
269+
? SystemColors.ControlDarkDark
270+
: SystemColors.Window;
271+
}
272+
}
273+
259274
set => base.BackColor = value;
260275
}
276+
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
261277

262278
[Browsable(false)]
263279
[EditorBrowsable(EditorBrowsableState.Never)]
@@ -3620,6 +3636,9 @@ private unsafe void WmReflectMeasureItem(ref Message m)
36203636
m.ResultInternal = (LRESULT)1;
36213637
}
36223638

3639+
private static readonly IntPtr s_darkEditBrush
3640+
= PInvokeCore.CreateSolidBrush(ColorTranslator.ToWin32(Color.FromArgb(64, 64, 64)));
3641+
36233642
/// <summary>
36243643
/// The ComboBox's window procedure. Inheriting classes can override this
36253644
/// to add extra functionality, but should not forget to call
@@ -3672,7 +3691,32 @@ protected override unsafe void WndProc(ref Message m)
36723691
}
36733692

36743693
break;
3694+
36753695
case PInvokeCore.WM_CTLCOLOREDIT:
3696+
// Only handle if the ComboBox is disabled
3697+
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
3698+
if (IsHandleCreated
3699+
&& !Enabled
3700+
&& Application.IsDarkModeEnabled
3701+
&& DarkModeRequestState is true)
3702+
{
3703+
PInvokeCore.SetBkColor(
3704+
(HDC)m.WParamInternal,
3705+
ColorTranslator.ToWin32(Color.FromArgb(64, 64, 64)));
3706+
3707+
PInvokeCore.SetTextColor(
3708+
(HDC)m.WParamInternal,
3709+
ColorTranslator.ToWin32(Color.FromArgb(180, 180, 180)));
3710+
3711+
m.ResultInternal = (LRESULT)s_darkEditBrush;
3712+
}
3713+
else
3714+
{
3715+
m.ResultInternal = (LRESULT)(nint)InitializeDCForWmCtlColor((HDC)(nint)m.WParamInternal, m.MsgInternal);
3716+
}
3717+
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
3718+
3719+
break;
36763720
case PInvokeCore.WM_CTLCOLORLISTBOX:
36773721
m.ResultInternal = (LRESULT)(nint)InitializeDCForWmCtlColor((HDC)(nint)m.WParamInternal, m.MsgInternal);
36783722
break;

src/System.Windows.Forms/System/Windows/Forms/Controls/ListView/ListView.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4673,7 +4673,7 @@ protected override void OnHandleCreated(EventArgs e)
46734673
private void ApplyDarkModeOnDemand()
46744674
{
46754675
if (Application.IsDarkModeEnabled
4676-
&& _darkModeRequestState is true)
4676+
&& DarkModeRequestState is true)
46774677
{
46784678
// Enable double buffering when in dark mode to reduce flicker.
46794679
uint exMask = PInvoke.LVS_EX_ONECLICKACTIVATE | PInvoke.LVS_EX_TWOCLICKACTIVATE |

src/System.Windows.Forms/System/Windows/Forms/Controls/RichTextBox/RichTextBox.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2412,6 +2412,24 @@ protected override void OnGotFocus(EventArgs e)
24122412
}
24132413
}
24142414

2415+
protected override void OnEnabledChanged(EventArgs e)
2416+
{
2417+
base.OnEnabledChanged(e);
2418+
HandleDarkModeDisabledBackground();
2419+
}
2420+
2421+
private void HandleDarkModeDisabledBackground()
2422+
{
2423+
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
2424+
if (Application.IsDarkModeEnabled
2425+
&& !Enabled
2426+
&& DarkModeRequestState is true)
2427+
{
2428+
Invalidate();
2429+
}
2430+
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
2431+
}
2432+
24152433
protected override void OnHandleCreated(EventArgs e)
24162434
{
24172435
// base.OnHandleCreated is called somewhere in the middle of this
@@ -2458,6 +2476,8 @@ protected override void OnHandleCreated(EventArgs e)
24582476
// base sets the Text property. It's important to do this *after* setting EM_AUTOUrlDETECT.
24592477
base.OnHandleCreated(e);
24602478

2479+
HandleDarkModeDisabledBackground();
2480+
24612481
// For some reason, we need to set the OleCallback before setting the RTF property.
24622482
UpdateOleCallback();
24632483

@@ -3445,6 +3465,24 @@ protected override void WndProc(ref Message m)
34453465
{
34463466
switch (m.MsgInternal)
34473467
{
3468+
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
3469+
case PInvokeCore.WM_PAINT:
3470+
if (IsHandleCreated && !Enabled)
3471+
{
3472+
if (Application.IsDarkModeEnabled && DarkModeRequestState is true)
3473+
{
3474+
// If the control is in dark mode, we need to paint the background
3475+
// with the dark mode color.
3476+
using Graphics g = Graphics.FromHwnd(Handle);
3477+
g.Clear(SystemColors.Control);
3478+
}
3479+
}
3480+
3481+
base.WndProc(ref m);
3482+
break;
3483+
3484+
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
3485+
34483486
case MessageId.WM_REFLECT_NOTIFY:
34493487
WmReflectNotify(ref m);
34503488
break;

src/System.Windows.Forms/System/Windows/Forms/Controls/TextBox/TextBoxBase.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,9 @@ public override Color BackColor
287287
else
288288
{
289289
return ReadOnly
290-
291290
// If we're ReadOnly and in DarkMode, we are using a different background color.
292291
? Application.IsDarkModeEnabled
293-
&& _darkModeRequestState is true
292+
&& DarkModeRequestState is true
294293
? SystemColors.ControlDarkDark
295294
: SystemColors.Control
296295
: SystemColors.Window;
@@ -955,7 +954,7 @@ private void EnsureReadonlyBackgroundColor(bool value)
955954
{
956955
// If we have no specifically defined back color, we set the back color in case we're in dark mode.
957956
if (Application.IsDarkModeEnabled
958-
&& _darkModeRequestState is true)
957+
&& DarkModeRequestState is true)
959958
{
960959
base.BackColor = value ? SystemColors.ControlLight : SystemColors.Window;
961960
Invalidate();

0 commit comments

Comments
 (0)