diff --git a/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/ApplicationServices/WindowsFormsApplicationBase.vb b/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/ApplicationServices/WindowsFormsApplicationBase.vb index 4882984446e..1751e06924d 100644 --- a/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/ApplicationServices/WindowsFormsApplicationBase.vb +++ b/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/ApplicationServices/WindowsFormsApplicationBase.vb @@ -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. diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs index a249cc47210..8f249aad8d3 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs @@ -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; /// /// Events the user can hook into @@ -247,27 +247,51 @@ internal static bool CustomThreadExceptionHandlerAttached => ThreadContext.FromCurrent().CustomThreadExceptionHandlerAttached; /// - /// Gets the default dark mode for the application. This is the SystemColorMode which either has been set - /// by or its default value . + /// Gets the default color mode (dark mode) for the application. /// + /// + /// + /// This is the which either has been set by + /// or its default value . If it has been set to , + /// then the actual color mode is determined by the system settings (which can be retrieved by the + /// static (shared in VB) property. + /// + /// [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; /// - /// Sets the default dark mode for the application. + /// Sets the default color mode (dark mode) for the application. /// - /// The default dark mode to set. + /// The application's default color mode (dark mode) to set. + /// + /// + /// 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 (), light mode () + /// or to the system setting (). + /// + /// + /// If you set it to , 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. + /// + /// + /// 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. + /// + /// + /// Note for Visual Basic: If you are using the Visual Basic Application Framework, you should set the + /// color mode by handling the Application Events (see "WindowsFormsApplicationBase.ApplyApplicationDefaults"). + /// + /// [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, @@ -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 { @@ -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; @@ -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; @@ -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)); /// /// Gets the path for the executable file that started the application. diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs index 281067c3515..c8b899ffb5a 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs @@ -137,6 +137,10 @@ public partial class Form : ContainerControl private static readonly int s_propOpacity = PropertyStore.CreateKey(); private static readonly int s_propTransparencyKey = PropertyStore.CreateKey(); private static readonly int s_propFormCornerPreference = PropertyStore.CreateKey(); + private static readonly int s_propFormBorderColor = PropertyStore.CreateKey(); + + private static readonly int s_propFormCaptionTextColor = PropertyStore.CreateKey(); + private static readonly int s_propFormCaptionBackColor = PropertyStore.CreateKey(); // Form per instance members // Note: Do not add anything to this list unless absolutely necessary. @@ -2344,8 +2348,21 @@ protected override void SetVisibleCore(bool value) } /// - /// Sets or gets the rounding style of the corners using the enum. + /// Sets or gets the rounding style of the Form's corners using the enum. /// + /// + /// + /// Note: Reading this property is only for tracking purposes. If the Form's corner preference is + /// changed through other external means (Win32 calls), reading this property will not reflect + /// those changes, as the Win32 API does not provide a mechanism to retrieve the current title + /// bar color. + /// + /// + /// The property only reflects the value that was previously set using this property. The + /// event is raised accordingly when the value is + /// changed, which allows the property to be participating in binding scenarios. + /// + /// [DefaultValue(FormCornerPreference.Default)] [SRCategory(nameof(SR.CatWindowStyle))] [SRDescription(nameof(SR.FormCornerPreferenceDescr))] @@ -2395,6 +2412,9 @@ public FormCornerPreference FormCornerPreference /// Raises the event when the /// property changes. /// + /// + /// An that contains the event data, in this case empty. + /// [Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)] protected virtual void OnFormCornerPreferenceChanged(EventArgs e) { @@ -2427,6 +2447,24 @@ private unsafe void SetFormCornerPreferenceInternal(FormCornerPreference cornerP /// /// Sets or gets the Form's border color. /// + /// + /// The which has be previously set using this property or . + /// Note that the underlying Win32 API does not provide a reliable mechanism to retrieve the current + /// border color. + /// + /// + /// + /// Note: Reading this property is only for tracking purposes. If the Form's border color is + /// changed through other external means (Win32 calls), reading this property will not reflect + /// those changes, as the Win32 API does not provide a mechanism to retrieve the current title + /// bar color. + /// + /// + /// The property only reflects the value that was previously set using this property. The + /// event is raised accordingly when the value is + /// changed, which allows the property to be participating in binding scenarios. + /// + /// [SRCategory(nameof(SR.CatWindowStyle))] [SRDescription(nameof(SR.FormBorderColorDescr))] [Browsable(false)] @@ -2434,7 +2472,10 @@ private unsafe void SetFormCornerPreferenceInternal(FormCornerPreference cornerP [Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)] public Color FormBorderColor { - get => GetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR); + get => Properties.ContainsObject(s_propFormBorderColor) + ? Properties.GetColor(s_propFormBorderColor) + : Color.Empty; + set { if (value == FormBorderColor) @@ -2442,6 +2483,8 @@ public Color FormBorderColor return; } + Properties.SetColor(s_propFormBorderColor, value); + if (IsHandleCreated) { SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR, value); @@ -2454,6 +2497,9 @@ public Color FormBorderColor /// /// Raises the event when the property changes. /// + /// + /// An that contains the event data, in this case empty. + /// [Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)] protected virtual void OnFormBorderColorChanged(EventArgs e) { @@ -2464,8 +2510,26 @@ protected virtual void OnFormBorderColorChanged(EventArgs e) } /// - /// Sets or gets the Form's title bar back color. + /// Sets or gets the Form's title bar back color (caption back color). /// + /// + /// The , which has be previously set using this property or . + /// Note that the underlying Win32 API does not provide a reliable mechanism to retrieve the current title + /// bar color. + /// + /// + /// + /// Note: Reading this property is only for tracking purposes. If the window's title bar color is + /// changed through other external means (Win32 calls), reading this property will not reflect + /// those changes, as the Win32 API does not provide a mechanism to retrieve the current title + /// bar color. + /// + /// + /// The property only reflects the value that was previously set using this property. The + /// event is raised accordingly when the value is + /// changed, which allows the property to be participating in binding scenarios. + /// + /// [SRCategory(nameof(SR.CatWindowStyle))] [SRDescription(nameof(SR.FormCaptionBackColorDescr))] [Browsable(false)] @@ -2473,7 +2537,10 @@ protected virtual void OnFormBorderColorChanged(EventArgs e) [Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)] public Color FormCaptionBackColor { - get => GetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_CAPTION_COLOR); + get => Properties.ContainsObject(s_propFormCaptionBackColor) + ? Properties.GetColor(s_propFormCaptionBackColor) + : Color.Empty; + set { if (value == FormCaptionBackColor) @@ -2481,6 +2548,8 @@ public Color FormCaptionBackColor return; } + Properties.SetColor(s_propFormCaptionBackColor, value); + if (IsHandleCreated) { SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_CAPTION_COLOR, value); @@ -2491,8 +2560,12 @@ public Color FormCaptionBackColor } /// - /// Raises the event when the property changes. + /// Raises the event when the + /// property changes. /// + /// + /// An that contains the event data, in this case empty. + /// [Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)] protected virtual void OnFormCaptionBackColorChanged(EventArgs e) { @@ -2503,8 +2576,26 @@ protected virtual void OnFormCaptionBackColorChanged(EventArgs e) } /// - /// Sets or gets the Form's title bar back color. + /// Sets or gets the Form's title bar text color (windows caption text color). /// + /// + /// The , which has be previously set using this property or . + /// Note that the underlying Win32 API does not provide a reliable mechanism to retrieve the current title + /// bar text color. + /// + /// + /// + /// Note: Reading this property is only for tracking purposes. If the Form's title bar's text color + /// (window caption text) is changed through other external means (Win32 calls), reading this property + /// will not reflect those changes, as the Win32 API does not provide a mechanism to retrieve the + /// current title bar color. + /// + /// + /// The property only reflects the value that was previously set using this property. The + /// event is raised accordingly when the value is + /// changed, which allows the property to be participating in binding scenarios. + /// + /// [SRCategory(nameof(SR.CatWindowStyle))] [SRDescription(nameof(SR.FormCaptionTextColorDescr))] [Browsable(false)] @@ -2512,7 +2603,9 @@ protected virtual void OnFormCaptionBackColorChanged(EventArgs e) [Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)] public Color FormCaptionTextColor { - get => GetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_TEXT_COLOR); + get => Properties.ContainsObject(s_propFormCaptionTextColor) + ? Properties.GetColor(s_propFormCaptionTextColor) + : Color.Empty; set { if (value == FormCaptionTextColor) @@ -2520,6 +2613,8 @@ public Color FormCaptionTextColor return; } + Properties.SetColor(s_propFormCaptionTextColor, value); + if (IsHandleCreated) { SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_TEXT_COLOR, value); @@ -2530,12 +2625,16 @@ public Color FormCaptionTextColor } /// - /// Raises the event when the property changes. + /// Raises the event when the + /// property changes. /// + /// + /// An that contains the event data, in this case empty. + /// [Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)] protected virtual void OnFormCaptionTextColorChanged(EventArgs e) { - if (Events[s_formCaptionBackColorChanged] is EventHandler eventHandler) + if (Events[s_formCaptionTextColorChanged] is EventHandler eventHandler) { eventHandler(this, e); } @@ -5854,7 +5953,7 @@ public DialogResult ShowDialog(IWin32Window? owner) /// This method immediately returns, even if the form is large and takes a long time to be set up. /// /// - /// If the form is already displayed asynchronously by , an will be thrown. + /// If the form is already displayed asynchronously by , an will be thrown. /// /// /// An will also occur if no could be retrieved or installed. @@ -5889,7 +5988,7 @@ public DialogResult ShowDialog(IWin32Window? owner) /// This method immediately returns, even if the form is large and takes a long time to be set up. /// /// - /// If the form is already displayed asynchronously by , an will be thrown. + /// If the form is already displayed asynchronously by , an will be thrown. /// /// /// An will also occur if no could be retrieved or installed. diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ApplicationTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ApplicationTests.cs index 5d39868002e..e343bcb57c6 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ApplicationTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ApplicationTests.cs @@ -146,6 +146,37 @@ public void Application_EnableVisualStyles_ManifestResourceExists() Assert.NotNull(stream); } +#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. +#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. + [Fact] + public void Application_SetColorMode_PlausibilityTests() + { + if (SystemInformation.HighContrast) + { + // We don't run this test in HighContrast mode. + return; + } + + SystemColorMode systemColorMode = Application.SystemColorMode; + + Application.SetColorMode(SystemColorMode.Classic); + Assert.False(Application.IsDarkModeEnabled); + Assert.Equal(SystemColorMode.Classic, Application.ColorMode); + Assert.False(SystemColors.UseAlternativeColorSet); + + Application.SetColorMode(SystemColorMode.Dark); + Assert.True(Application.IsDarkModeEnabled); + Assert.Equal(SystemColorMode.Dark, Application.ColorMode); + Assert.True(SystemColors.UseAlternativeColorSet); + + Application.SetColorMode(SystemColorMode.System); + Assert.False(Application.IsDarkModeEnabled ^ systemColorMode == SystemColorMode.Dark); + Assert.Equal(SystemColorMode.System, Application.ColorMode); + Assert.False(SystemColors.UseAlternativeColorSet ^ systemColorMode == SystemColorMode.Dark); + } +#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. +#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. + [WinFormsFact] public void Application_DefaultFont_ReturnsNull_IfNoFontSet() {