Skip to content

Commit df24969

Browse files
committed
Apply my feedback to Klaus' dark mode feature
- Move Experimental URL to DiagnosticIds - Move special dark mode logic from CreateBrushScope to GetSysColorBrush - Remove unnecessary logic in FindNearestColor - Update brushes and pens by poking SystemEvents static directly
1 parent b238485 commit df24969

File tree

14 files changed

+106
-81
lines changed

14 files changed

+106
-81
lines changed

src/System.Drawing.Common/src/System/Drawing/SystemBrushes.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ namespace System.Drawing;
55

66
public static class SystemBrushes
77
{
8-
#if NET9_0_OR_GREATER
9-
#pragma warning disable SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
10-
private static bool s_colorSetOnLastAccess = SystemColors.UseAlternativeColorSet;
11-
#pragma warning restore SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
12-
#endif
138
private static readonly object s_systemBrushesKey = new();
149

1510
public static Brush ActiveBorder => FromSystemColor(SystemColors.ActiveBorder);
@@ -62,18 +57,6 @@ public static Brush FromSystemColor(Color c)
6257
throw new ArgumentException(SR.Format(SR.ColorNotSystemColor, c.ToString()));
6358
}
6459

65-
#if NET9_0_OR_GREATER
66-
#pragma warning disable SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
67-
if (s_colorSetOnLastAccess != SystemColors.UseAlternativeColorSet)
68-
{
69-
s_colorSetOnLastAccess = SystemColors.UseAlternativeColorSet;
70-
71-
// We need to clear the SystemBrushes cache, when the ColorMode had changed.
72-
Gdip.ThreadData.Remove(s_systemBrushesKey);
73-
}
74-
#pragma warning restore SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
75-
#endif
76-
7760
if (!Gdip.ThreadData.TryGetValue(s_systemBrushesKey, out object? tempSystemBrushes)
7861
|| tempSystemBrushes is not Brush[] systemBrushes)
7962
{

src/System.Drawing.Common/src/System/Drawing/SystemPens.cs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,6 @@ namespace System.Drawing;
55

66
public static class SystemPens
77
{
8-
#if NET9_0_OR_GREATER
9-
#pragma warning disable SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
10-
private static bool s_colorSetOnLastAccess = SystemColors.UseAlternativeColorSet;
11-
#pragma warning restore SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
12-
#endif
13-
148
private static readonly object s_systemPensKey = new();
159

1610
public static Pen ActiveBorder => FromSystemColor(SystemColors.ActiveBorder);
@@ -64,18 +58,6 @@ public static Pen FromSystemColor(Color c)
6458
throw new ArgumentException(SR.Format(SR.ColorNotSystemColor, c.ToString()));
6559
}
6660

67-
#if NET9_0_OR_GREATER
68-
#pragma warning disable SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
69-
if (s_colorSetOnLastAccess != SystemColors.UseAlternativeColorSet)
70-
{
71-
s_colorSetOnLastAccess = SystemColors.UseAlternativeColorSet;
72-
73-
// We need to clear the SystemBrushes cache, when the ColorMode had changed.
74-
Gdip.ThreadData.Remove(s_systemPensKey);
75-
}
76-
#pragma warning restore SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
77-
#endif
78-
7961
if (!Gdip.ThreadData.TryGetValue(s_systemPensKey, out object? tempSystemPens)
8062
|| tempSystemPens is not Pen[] systemPens)
8163
{

src/System.Windows.Forms.Analyzers/src/System/Windows/Forms/Analyzers/Diagnostics/DiagnosticIDs.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ internal static class DiagnosticIDs
1919
public const string DisposeModalDialog = "WFO2000";
2020

2121
// Experimental, number group 5000+
22-
public const string ExperimentalVisualStyles = "WFO5000";
2322
public const string ExperimentalDarkMode = "WFO5001";
2423
public const string ExperimentalAsync = "WFO5002";
2524
}

src/System.Windows.Forms.Primitives/src/NativeMethods.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,7 @@ SelectPalette
599599
SendDlgItemMessage
600600
SendInput
601601
SendMessage
602+
SendMessageCallback
602603
SendMessageTimeout
603604
SetActiveWindow
604605
SetBkColor

src/System.Windows.Forms.Primitives/src/System/Windows/Forms/DeviceContextExtensions.cs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -116,22 +116,7 @@ internal static unsafe void DrawLines(this HDC hdc, HPEN hpen, ReadOnlySpan<int>
116116
/// </remarks>
117117
internal static Color FindNearestColor(this HDC hdc, Color color)
118118
{
119-
#pragma warning disable SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
120-
if (color.IsSystemColor && SystemColors.UseAlternativeColorSet)
121-
{
122-
// We need to "un-system" the color for nearest color to work correctly in dark mode,
123-
// since GetNearestColor doesn't understand dark mode system colors and internally works
124-
// with the light mode colors.
125-
color = Color.FromArgb(color.ToArgb());
126-
Debug.Assert(!color.IsSystemColor, "Color should not be a system color.");
127-
}
128-
#pragma warning restore SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
129-
130-
Color newColor = ColorTranslator.FromWin32(
131-
(int)PInvoke.GetNearestColor(
132-
hdc: hdc,
133-
color: (COLORREF)(uint)ColorTranslator.ToWin32(color)).Value);
134-
119+
Color newColor = ColorTranslator.FromWin32((int)PInvoke.GetNearestColor(hdc, (COLORREF)(uint)ColorTranslator.ToWin32(color)).Value);
135120
return newColor.ToArgb() == color.ToArgb() ? color : newColor;
136121
}
137122

src/System.Windows.Forms.Primitives/src/Windows/Win32/CreateBrushScope.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,9 @@ internal readonly ref struct CreateBrushScope
2828
/// </summary>
2929
public CreateBrushScope(Color color)
3030
{
31-
#pragma warning disable SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
32-
HBRUSH = color.IsSystemColor && !SystemColors.UseAlternativeColorSet
31+
HBRUSH = color.IsSystemColor
3332
? PInvoke.GetSysColorBrush(color)
3433
: PInvoke.CreateSolidBrush(color);
35-
#pragma warning restore SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
3634

3735
ValidateBrushHandle();
3836
}

src/System.Windows.Forms.Primitives/src/Windows/Win32/PInvoke.GetSysColorBrush.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,19 @@ internal static partial class PInvoke
1010
/// <inheritdoc cref="GetSysColorBrush(SYS_COLOR_INDEX)"/>
1111
public static HBRUSH GetSysColorBrush(Color systemColor)
1212
{
13+
#pragma warning disable SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
14+
bool useSolidBrush = SystemColors.UseAlternativeColorSet;
15+
#pragma warning restore SYSLIB5002
16+
17+
if (useSolidBrush)
18+
{
19+
// We don't have a real system color, so we'll just create a solid brush.
20+
return CreateSolidBrush(systemColor);
21+
}
22+
1323
Debug.Assert(systemColor.IsSystemColor);
1424

1525
// Extract the COLOR value
16-
return PInvoke.GetSysColorBrush((SYS_COLOR_INDEX)(ColorTranslator.ToOle(systemColor) & 0xFF));
26+
return GetSysColorBrush((SYS_COLOR_INDEX)(ColorTranslator.ToOle(systemColor) & 0xFF));
1727
}
1828
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.InteropServices;
5+
using System.Runtime.CompilerServices;
6+
7+
namespace Windows.Win32;
8+
9+
internal static partial class PInvoke
10+
{
11+
internal static unsafe BOOL SendMessageCallback<T>(
12+
T hWnd,
13+
MessageId Msg,
14+
Action callback,
15+
WPARAM wParam = default,
16+
LPARAM lParam = default)
17+
where T : IHandle<HWND>
18+
{
19+
GCHandle gcHandle = GCHandle.Alloc(callback);
20+
BOOL result = SendMessageCallback(hWnd.Handle, Msg, wParam, lParam, &NativeCallback, (nuint)(nint)gcHandle);
21+
GC.KeepAlive(hWnd.Wrapper);
22+
return result;
23+
}
24+
25+
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
26+
private static void NativeCallback(HWND hwnd, uint Msg, nuint dwData, LRESULT lResult)
27+
{
28+
GCHandle gcHandle = (GCHandle)(nint)dwData;
29+
Action action = (Action)gcHandle.Target!;
30+
gcHandle.Free();
31+
action();
32+
}
33+
}

src/System.Windows.Forms/src/System/Windows/Forms/Application.cs

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ public sealed partial class Application
4848
private static SystemColorMode? s_systemColorMode;
4949
#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.
5050

51-
internal const string WinFormsExperimentalUrl = "https://aka.ms/winforms-experimental/{0}";
52-
5351
private const string DarkModeKeyPath = "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
5452
private const string DarkModeKey = "AppsUseLightTheme";
5553
private const int DarkModeNotAvailable = -1;
@@ -252,7 +250,7 @@ internal static bool CustomThreadExceptionHandlerAttached
252250
/// Gets the default dark mode for the application. This is the SystemColorMode which either has been set
253251
/// by <see cref="SetColorMode(SystemColorMode)"/> or its default value <see cref="SystemColorMode.Classic"/>.
254252
/// </summary>
255-
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = WinFormsExperimentalUrl)]
253+
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
256254
public static SystemColorMode ColorMode =>
257255
!s_systemColorMode.HasValue
258256
? SystemColorMode.Classic
@@ -264,7 +262,7 @@ internal static bool CustomThreadExceptionHandlerAttached
264262
/// Sets the default dark mode for the application.
265263
/// </summary>
266264
/// <param name="systemColorMode">The default dark mode to set.</param>
267-
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = WinFormsExperimentalUrl)]
265+
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
268266
public static void SetColorMode(SystemColorMode systemColorMode)
269267
{
270268
try
@@ -278,6 +276,11 @@ public static void SetColorMode(SystemColorMode systemColorMode)
278276
_ => throw new ArgumentOutOfRangeException(nameof(systemColorMode))
279277
};
280278

279+
if (systemColorMode == s_systemColorMode)
280+
{
281+
return;
282+
}
283+
281284
if (GetSystemColorModeInternal() > -1)
282285
{
283286
s_systemColorMode = systemColorMode;
@@ -288,7 +291,40 @@ public static void SetColorMode(SystemColorMode systemColorMode)
288291
}
289292
finally
290293
{
291-
SystemColors.UseAlternativeColorSet = IsDarkModeEnabled;
294+
bool useAlternateColorSet = SystemColors.UseAlternativeColorSet;
295+
bool darkModeEnabled = IsDarkModeEnabled;
296+
297+
if (useAlternateColorSet != darkModeEnabled)
298+
{
299+
SystemColors.UseAlternativeColorSet = darkModeEnabled;
300+
NotifySystemEventsOfColorChange();
301+
}
302+
}
303+
304+
static void NotifySystemEventsOfColorChange()
305+
{
306+
string s_systemTrackerWindow = $".NET-BroadcastEventWindow.{AppDomain.CurrentDomain.GetHashCode():x}.0";
307+
308+
HWND hwnd = PInvoke.FindWindow(s_systemTrackerWindow, s_systemTrackerWindow);
309+
if (hwnd.IsNull)
310+
{
311+
// Haven't created the window yet, so no need to notify.
312+
return;
313+
}
314+
315+
bool complete = false;
316+
bool success = PInvoke.SendMessageCallback(hwnd, PInvoke.WM_SYSCOLORCHANGE + MessageId.WM_REFLECT, () => complete = true);
317+
Debug.Assert(success);
318+
if (!success)
319+
{
320+
return;
321+
}
322+
323+
while (!complete)
324+
{
325+
DoEvents();
326+
Thread.Yield();
327+
}
292328
}
293329
}
294330

@@ -310,7 +346,7 @@ public static void SetColorMode(SystemColorMode systemColorMode)
310346
/// SystemColorModes is not supported, if the Windows OS <c>High Contrast Mode</c> has been enabled in the system settings.
311347
/// </para>
312348
/// </remarks>
313-
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = WinFormsExperimentalUrl)]
349+
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
314350
public static SystemColorMode SystemColorMode =>
315351
GetSystemColorModeInternal() == 0
316352
? SystemColorMode.Dark
@@ -349,7 +385,7 @@ private static int GetSystemColorModeInternal()
349385
/// Gets a value indicating whether the application is running in a dark system color context.
350386
/// Note: In a high contrast mode, this will always return <see langword="false"/>.
351387
/// </summary>
352-
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = WinFormsExperimentalUrl)]
388+
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
353389
public static bool IsDarkModeEnabled =>
354390
!SystemInformation.HighContrast
355391
&& (ColorMode == SystemColorMode.Dark);

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -716,8 +716,7 @@ internal HBRUSH BackColorBrush
716716
Color color = BackColor;
717717
HBRUSH backBrush;
718718

719-
#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.
720-
if (color.IsSystemColor && !Application.IsDarkModeEnabled)
719+
if (color.IsSystemColor)
721720
{
722721
backBrush = PInvoke.GetSysColorBrush(color);
723722
SetState(States.OwnCtlBrush, false);
@@ -727,7 +726,6 @@ internal HBRUSH BackColorBrush
727726
backBrush = PInvoke.CreateSolidBrush((COLORREF)(uint)ColorTranslator.ToWin32(color));
728727
SetState(States.OwnCtlBrush, true);
729728
}
730-
#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.
731729

732730
Debug.Assert(!backBrush.IsNull, "Failed to create brushHandle");
733731
Properties.SetObject(s_backBrushProperty, backBrush);

0 commit comments

Comments
 (0)