Skip to content

[RC1/9.0] Backport Dark Mode fix #11924

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,10 @@ Namespace Microsoft.VisualBasic.ApplicationServices
Debug.Assert(dpiSetResult, "We could net set the HighDpiMode.")

' Now, let's set VisualStyles and ColorMode:
If (_enableVisualStyles) Then
Application.EnableVisualStyles()
End If

Application.SetColorMode(_colorMode)

#Enable Warning WFO5001 ' Type is for evaluation purposes only and is subject to change or removal in future updates.
Expand Down
88 changes: 52 additions & 36 deletions src/System.Windows.Forms/src/System/Windows/Forms/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ public sealed partial class Application
private static bool s_useWaitCursor;

#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.
private static SystemColorMode? s_systemColorMode;
private static SystemColorMode? s_colorMode;
#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.

private const string DarkModeKeyPath = "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
private const string DarkModeKey = "AppsUseLightTheme";
private const int DarkModeNotAvailable = -1;
private const int SystemDarkModeDisabled = 1;

/// <summary>
/// Events the user can hook into
Expand Down Expand Up @@ -247,27 +247,51 @@ internal static bool CustomThreadExceptionHandlerAttached
=> ThreadContext.FromCurrent().CustomThreadExceptionHandlerAttached;

/// <summary>
/// Gets the default dark mode for the application. This is the SystemColorMode which either has been set
/// by <see cref="SetColorMode(SystemColorMode)"/> or its default value <see cref="SystemColorMode.Classic"/>.
/// Gets the default color mode (dark mode) for the application.
/// </summary>
/// <remarks>
/// <para>
/// This is the <see cref="SystemColorMode"/> which either has been set by <see cref="SetColorMode(SystemColorMode)"/>
/// or its default value <see cref="SystemColorMode.Classic"/>. If it has been set to <see cref="SystemColorMode.System"/>,
/// then the actual color mode is determined by the system settings (which can be retrieved by the
/// static (shared in VB) <see cref="Application.SystemColorMode"/> property.
/// </para>
/// </remarks>
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
public static SystemColorMode ColorMode =>
!s_systemColorMode.HasValue
? SystemColorMode.Classic
: s_systemColorMode.Value == SystemColorMode.System
? SystemColorMode
: s_systemColorMode.Value;
s_colorMode ?? SystemColorMode.Classic;

/// <summary>
/// Sets the default dark mode for the application.
/// Sets the default color mode (dark mode) for the application.
/// </summary>
/// <param name="systemColorMode">The default dark mode to set.</param>
/// <param name="systemColorMode">The application's default color mode (dark mode) to set.</param>
/// <remarks>
/// <para>
/// You should use this method to set the default color mode (dark mode) for the application. Set it,
/// before creating any UI elements, to ensure that the correct color mode is used. You can set it to
/// dark mode (<see cref="SystemColorMode.Dark"/>), light mode (<see cref="SystemColorMode.Classic"/>)
/// or to the system setting (<see cref="SystemColorMode.System"/>).
/// </para>
/// <para>
/// If you set it to <see cref="SystemColorMode.System"/>, the actual color mode is determined by the
/// Windows system settings. If the system setting is changed, the application will not automatically
/// adapt to the new setting.
/// </para>
/// <para>
/// Note that the dark color mode is only available from Windows 11 on or later versions. If the system
/// is set to a high contrast mode, the dark mode is not available.
/// </para>
/// <para>
/// <b>Note for Visual Basic:</b> If you are using the Visual Basic Application Framework, you should set the
/// color mode by handling the Application Events (see "WindowsFormsApplicationBase.ApplyApplicationDefaults").
/// </para>
/// </remarks>
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
public static void SetColorMode(SystemColorMode systemColorMode)
{
try
{
// Can't use the Generator here, since it cannot deal with experimentals.
// Can't use the Generator here, since it cannot deal with [Experimental].
_ = systemColorMode switch
{
SystemColorMode.Classic => systemColorMode,
Expand All @@ -276,18 +300,12 @@ public static void SetColorMode(SystemColorMode systemColorMode)
_ => throw new ArgumentOutOfRangeException(nameof(systemColorMode))
};

if (systemColorMode == s_systemColorMode)
if (systemColorMode == s_colorMode)
{
return;
}

if (GetSystemColorModeInternal() > -1)
{
s_systemColorMode = systemColorMode;
return;
}

s_systemColorMode = SystemColorMode.Classic;
s_colorMode = systemColorMode;
}
finally
{
Expand Down Expand Up @@ -315,6 +333,7 @@ static void NotifySystemEventsOfColorChange()
bool complete = false;
bool success = PInvoke.SendMessageCallback(hwnd, PInvoke.WM_SYSCOLORCHANGE + MessageId.WM_REFLECT, () => complete = true);
Debug.Assert(success);

if (!success)
{
return;
Expand Down Expand Up @@ -357,25 +376,21 @@ private static int GetSystemColorModeInternal()
{
if (SystemInformation.HighContrast)
{
return DarkModeNotAvailable;
return SystemDarkModeDisabled;
}

int systemColorMode = DarkModeNotAvailable;
int systemColorMode = SystemDarkModeDisabled;

// Dark mode is supported when we are >= W11/22000
// Technically, we could go earlier, but then the APIs we're using weren't officially public.
if (OsVersion.IsWindows11_OrGreater())
try
{
// 0 for dark mode and |1| for light mode.
systemColorMode = Math.Abs((Registry.GetValue(
keyName: DarkModeKeyPath,
valueName: DarkModeKey,
defaultValue: SystemDarkModeDisabled) as int?) ?? systemColorMode);
}
catch (Exception ex) when (!ex.IsCriticalException())
{
try
{
systemColorMode = (Registry.GetValue(
keyName: DarkModeKeyPath,
valueName: DarkModeKey,
defaultValue: DarkModeNotAvailable) as int?) ?? systemColorMode;
}
catch (Exception ex) when (!ex.IsCriticalException())
{
}
}

return systemColorMode;
Expand All @@ -388,7 +403,8 @@ private static int GetSystemColorModeInternal()
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
public static bool IsDarkModeEnabled =>
!SystemInformation.HighContrast
&& (ColorMode == SystemColorMode.Dark);
&& (ColorMode == SystemColorMode.Dark
|| (ColorMode == SystemColorMode.System && SystemColorMode == SystemColorMode.Dark));

/// <summary>
/// Gets the path for the executable file that started the application.
Expand Down
Loading