Skip to content

Commit c993d33

Browse files
Merge pull request #11907 from KlausLoeffelmann/DarkModeFix
Dark mode/Visual Styles (VB Only) fix
2 parents 34298ef + 3aeafc3 commit c993d33

File tree

3 files changed

+163
-36
lines changed

3 files changed

+163
-36
lines changed

src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/ApplicationServices/WindowsFormsApplicationBase.vb

+4
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,10 @@ Namespace Microsoft.VisualBasic.ApplicationServices
550550
Debug.Assert(dpiSetResult, "We could net set the HighDpiMode.")
551551

552552
' Now, let's set VisualStyles and ColorMode:
553+
If (_enableVisualStyles) Then
554+
Application.EnableVisualStyles()
555+
End If
556+
553557
Application.SetColorMode(_colorMode)
554558

555559
#Enable Warning WFO5001 ' Type is for evaluation purposes only and is subject to change or removal in future updates.

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

+26-24
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public sealed partial class Application
5050

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

5555
/// <summary>
5656
/// Events the user can hook into
@@ -252,11 +252,7 @@ internal static bool CustomThreadExceptionHandlerAttached
252252
/// </summary>
253253
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
254254
public static SystemColorMode ColorMode =>
255-
!s_systemColorMode.HasValue
256-
? SystemColorMode.Classic
257-
: s_systemColorMode.Value == SystemColorMode.System
258-
? SystemColorMode
259-
: s_systemColorMode.Value;
255+
s_systemColorMode ?? SystemColorMode.Classic;
260256

261257
/// <summary>
262258
/// Sets the default dark mode for the application.
@@ -281,12 +277,19 @@ public static void SetColorMode(SystemColorMode systemColorMode)
281277
return;
282278
}
283279

284-
if (GetSystemColorModeInternal() > -1)
280+
if ((systemColorMode == SystemColorMode.Dark && IsSystemDarkModeAvailable)
281+
|| systemColorMode == SystemColorMode.Classic)
285282
{
286283
s_systemColorMode = systemColorMode;
287284
return;
288285
}
289286

287+
if (GetSystemColorModeInternal() != SystemDarkModeDisabled)
288+
{
289+
s_systemColorMode = SystemColorMode.Dark;
290+
return;
291+
}
292+
290293
s_systemColorMode = SystemColorMode.Classic;
291294
}
292295
finally
@@ -352,35 +355,34 @@ static void NotifySystemEventsOfColorChange()
352355
? SystemColorMode.Dark
353356
: SystemColorMode.Classic;
354357

355-
// Returns 0 if dark mode is available, otherwise -1 (DarkModeNotAvailable)
358+
// Returns 0 if dark mode is enabled in the system, otherwise -1 (SystemDarkModeDisabled)
356359
private static int GetSystemColorModeInternal()
357360
{
358-
if (SystemInformation.HighContrast)
361+
if (!IsSystemDarkModeAvailable)
359362
{
360-
return DarkModeNotAvailable;
363+
return SystemDarkModeDisabled;
361364
}
362365

363-
int systemColorMode = DarkModeNotAvailable;
366+
int systemColorMode = SystemDarkModeDisabled;
364367

365-
// Dark mode is supported when we are >= W11/22000
366-
// Technically, we could go earlier, but then the APIs we're using weren't officially public.
367-
if (OsVersion.IsWindows11_OrGreater())
368+
try
369+
{
370+
// 0 for dark mode and |1| for light mode.
371+
systemColorMode = Math.Abs((Registry.GetValue(
372+
keyName: DarkModeKeyPath,
373+
valueName: DarkModeKey,
374+
defaultValue: SystemDarkModeDisabled) as int?) ?? systemColorMode);
375+
}
376+
catch (Exception ex) when (!ex.IsCriticalException())
368377
{
369-
try
370-
{
371-
systemColorMode = (Registry.GetValue(
372-
keyName: DarkModeKeyPath,
373-
valueName: DarkModeKey,
374-
defaultValue: DarkModeNotAvailable) as int?) ?? systemColorMode;
375-
}
376-
catch (Exception ex) when (!ex.IsCriticalException())
377-
{
378-
}
379378
}
380379

381380
return systemColorMode;
382381
}
383382

383+
private static bool IsSystemDarkModeAvailable =>
384+
!SystemInformation.HighContrast && OsVersion.IsWindows11_OrGreater();
385+
384386
/// <summary>
385387
/// Gets a value indicating whether the application is running in a dark system color context.
386388
/// Note: In a high contrast mode, this will always return <see langword="false"/>.

src/System.Windows.Forms/src/System/Windows/Forms/Form.cs

+133-12
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ public partial class Form : ContainerControl
135135
private static readonly int s_propOpacity = PropertyStore.CreateKey();
136136
private static readonly int s_propTransparencyKey = PropertyStore.CreateKey();
137137
private static readonly int s_propFormCornerPreference = PropertyStore.CreateKey();
138+
private static readonly int s_propFormBorderColor = PropertyStore.CreateKey();
139+
140+
private static readonly int s_propFormCaptionTextColor = PropertyStore.CreateKey();
141+
private static readonly int s_propFormCaptionBackColor = PropertyStore.CreateKey();
138142

139143
// Form per instance members
140144
// Note: Do not add anything to this list unless absolutely necessary.
@@ -2314,8 +2318,21 @@ protected override void SetVisibleCore(bool value)
23142318
}
23152319

23162320
/// <summary>
2317-
/// Sets or gets the rounding style of the corners using the <see cref="FormCornerPreference"/> enum.
2321+
/// Sets or gets the rounding style of the Form's corners using the <see cref="FormCornerPreference"/> enum.
23182322
/// </summary>
2323+
/// <remarks>
2324+
/// <para>
2325+
/// Note: Reading this property is only for tracking purposes. If the Form's corner preference is
2326+
/// changed through other external means (Win32 calls), reading this property will not reflect
2327+
/// those changes, as the Win32 API does not provide a mechanism to retrieve the current title
2328+
/// bar color.
2329+
/// </para>
2330+
/// <para>
2331+
/// The property only reflects the value that was previously set using this property. The
2332+
/// <see cref="FormCornerPreferenceChanged"/> event is raised accordingly when the value is
2333+
/// changed, which allows the property to be participating in binding scenarios.
2334+
/// </para>
2335+
/// </remarks>
23192336
[DefaultValue(FormCornerPreference.Default)]
23202337
[SRCategory(nameof(SR.CatWindowStyle))]
23212338
[SRDescription(nameof(SR.FormCornerPreferenceDescr))]
@@ -2341,7 +2358,14 @@ public FormCornerPreference FormCornerPreference
23412358
_ => throw new ArgumentOutOfRangeException(nameof(value))
23422359
};
23432360

2344-
Properties.AddOrRemoveValue(s_propFormCornerPreference, value);
2361+
if (value == FormCornerPreference.Default)
2362+
{
2363+
Properties.RemoveValue(s_propFormCornerPreference);
2364+
}
2365+
else
2366+
{
2367+
Properties.AddValue(s_propFormCornerPreference, value);
2368+
}
23452369

23462370
if (IsHandleCreated)
23472371
{
@@ -2356,6 +2380,9 @@ public FormCornerPreference FormCornerPreference
23562380
/// Raises the <see cref="FormCornerPreferenceChanged"/> event when the
23572381
/// <see cref="FormCornerPreference"/> property changes.
23582382
/// </summary>
2383+
/// <param name="e">
2384+
/// An <see cref="EventArgs"/> that contains the event data, in this case empty.
2385+
/// </param>
23592386
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
23602387
protected virtual void OnFormCornerPreferenceChanged(EventArgs e)
23612388
{
@@ -2388,21 +2415,41 @@ private unsafe void SetFormCornerPreferenceInternal(FormCornerPreference cornerP
23882415
/// <summary>
23892416
/// Sets or gets the Form's border color.
23902417
/// </summary>
2418+
/// <returns>
2419+
/// The <see cref="Color"/> which has be previously set using this property or <see cref="Color.Empty"/>.
2420+
/// Note that the underlying Win32 API does not provide a reliable mechanism to retrieve the current
2421+
/// border color.
2422+
/// </returns>
2423+
/// <remarks>
2424+
/// <para>
2425+
/// Note: Reading this property is only for tracking purposes. If the Form's border color is
2426+
/// changed through other external means (Win32 calls), reading this property will not reflect
2427+
/// those changes, as the Win32 API does not provide a mechanism to retrieve the current title
2428+
/// bar color.
2429+
/// </para>
2430+
/// <para>
2431+
/// The property only reflects the value that was previously set using this property. The
2432+
/// <see cref="FormBorderColorChanged"/> event is raised accordingly when the value is
2433+
/// changed, which allows the property to be participating in binding scenarios.
2434+
/// </para>
2435+
/// </remarks>
23912436
[SRCategory(nameof(SR.CatWindowStyle))]
23922437
[SRDescription(nameof(SR.FormBorderColorDescr))]
23932438
[Browsable(false)]
23942439
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
23952440
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
23962441
public Color FormBorderColor
23972442
{
2398-
get => GetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR);
2443+
get => Properties.GetValueOrDefault(s_propFormBorderColor, Color.Empty);
23992444
set
24002445
{
24012446
if (value == FormBorderColor)
24022447
{
24032448
return;
24042449
}
24052450

2451+
Properties.AddValue(s_propFormBorderColor, value);
2452+
24062453
if (IsHandleCreated)
24072454
{
24082455
SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR, value);
@@ -2415,6 +2462,9 @@ public Color FormBorderColor
24152462
/// <summary>
24162463
/// Raises the <see cref="FormBorderColorChanged"/> event when the <see cref="FormBorderColor"/> property changes.
24172464
/// </summary>
2465+
/// <param name="e">
2466+
/// An <see cref="EventArgs"/> that contains the event data, in this case empty.
2467+
/// </param>
24182468
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
24192469
protected virtual void OnFormBorderColorChanged(EventArgs e)
24202470
{
@@ -2425,23 +2475,43 @@ protected virtual void OnFormBorderColorChanged(EventArgs e)
24252475
}
24262476

24272477
/// <summary>
2428-
/// Sets or gets the Form's title bar back color.
2478+
/// Sets or gets the Form's title bar back color (caption back color).
24292479
/// </summary>
2480+
/// <returns>
2481+
/// The <see cref="Color"/>, which has be previously set using this property or <see cref="Color.Empty"/>.
2482+
/// Note that the underlying Win32 API does not provide a reliable mechanism to retrieve the current title
2483+
/// bar color.
2484+
/// </returns>
2485+
/// <remarks>
2486+
/// <para>
2487+
/// Note: Reading this property is only for tracking purposes. If the window's title bar color is
2488+
/// changed through other external means (Win32 calls), reading this property will not reflect
2489+
/// those changes, as the Win32 API does not provide a mechanism to retrieve the current title
2490+
/// bar color.
2491+
/// </para>
2492+
/// <para>
2493+
/// The property only reflects the value that was previously set using this property. The
2494+
/// <see cref="FormCaptionBackColorChanged"/> event is raised accordingly when the value is
2495+
/// changed, which allows the property to be participating in binding scenarios.
2496+
/// </para>
2497+
/// </remarks>
24302498
[SRCategory(nameof(SR.CatWindowStyle))]
24312499
[SRDescription(nameof(SR.FormCaptionBackColorDescr))]
24322500
[Browsable(false)]
24332501
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
24342502
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
24352503
public Color FormCaptionBackColor
24362504
{
2437-
get => GetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_CAPTION_COLOR);
2505+
get => Properties.GetValueOrDefault(s_propFormCaptionBackColor, Color.Empty);
24382506
set
24392507
{
24402508
if (value == FormCaptionBackColor)
24412509
{
24422510
return;
24432511
}
24442512

2513+
Properties.AddValue(s_propFormCaptionBackColor, value);
2514+
24452515
if (IsHandleCreated)
24462516
{
24472517
SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_CAPTION_COLOR, value);
@@ -2452,8 +2522,12 @@ public Color FormCaptionBackColor
24522522
}
24532523

24542524
/// <summary>
2455-
/// Raises the <see cref="FormCaptionBackColor"/> event when the <see cref="FormCaptionBackColor"/> property changes.
2525+
/// Raises the <see cref="FormCaptionBackColorChanged"/> event when the <see cref="FormCaptionBackColor"/>
2526+
/// property changes.
24562527
/// </summary>
2528+
/// <param name="e">
2529+
/// An <see cref="EventArgs"/> that contains the event data, in this case empty.
2530+
/// </param>
24572531
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
24582532
protected virtual void OnFormCaptionBackColorChanged(EventArgs e)
24592533
{
@@ -2464,23 +2538,43 @@ protected virtual void OnFormCaptionBackColorChanged(EventArgs e)
24642538
}
24652539

24662540
/// <summary>
2467-
/// Sets or gets the Form's title bar back color.
2541+
/// Sets or gets the Form's title bar text color (windows caption text color).
24682542
/// </summary>
2543+
/// <returns>
2544+
/// The <see cref="Color"/>, which has be previously set using this property or <see cref="Color.Empty"/>.
2545+
/// Note that the underlying Win32 API does not provide a reliable mechanism to retrieve the current title
2546+
/// bar text color.
2547+
/// </returns>
2548+
/// <remarks>
2549+
/// <para>
2550+
/// Note: Reading this property is only for tracking purposes. If the Form's title bar's text color
2551+
/// (window caption text) is changed through other external means (Win32 calls), reading this property
2552+
/// will not reflect those changes, as the Win32 API does not provide a mechanism to retrieve the
2553+
/// current title bar color.
2554+
/// </para>
2555+
/// <para>
2556+
/// The property only reflects the value that was previously set using this property. The
2557+
/// <see cref="FormCaptionTextColorChanged"/> event is raised accordingly when the value is
2558+
/// changed, which allows the property to be participating in binding scenarios.
2559+
/// </para>
2560+
/// </remarks>
24692561
[SRCategory(nameof(SR.CatWindowStyle))]
24702562
[SRDescription(nameof(SR.FormCaptionTextColorDescr))]
24712563
[Browsable(false)]
24722564
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
24732565
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
24742566
public Color FormCaptionTextColor
24752567
{
2476-
get => GetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_TEXT_COLOR);
2568+
get => Properties.GetValueOrDefault(s_propFormCaptionTextColor, Color.Empty);
24772569
set
24782570
{
24792571
if (value == FormCaptionTextColor)
24802572
{
24812573
return;
24822574
}
24832575

2576+
Properties.AddValue(s_propFormCaptionTextColor, value);
2577+
24842578
if (IsHandleCreated)
24852579
{
24862580
SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_TEXT_COLOR, value);
@@ -2491,12 +2585,16 @@ public Color FormCaptionTextColor
24912585
}
24922586

24932587
/// <summary>
2494-
/// Raises the <see cref="FormCaptionTextColor"/> event when the <see cref="FormCaptionTextColor"/> property changes.
2588+
/// Raises the <see cref="FormCaptionTextColorChanged"/> event when the
2589+
/// <see cref="FormCaptionTextColor"/> property changes.
24952590
/// </summary>
2591+
/// <param name="e">
2592+
/// An <see cref="EventArgs"/> that contains the event data, in this case empty.
2593+
/// </param>
24962594
[Experimental(DiagnosticIDs.ExperimentalDarkMode, UrlFormat = DiagnosticIDs.UrlFormat)]
24972595
protected virtual void OnFormCaptionTextColorChanged(EventArgs e)
24982596
{
2499-
if (Events[s_formCaptionBackColorChanged] is EventHandler eventHandler)
2597+
if (Events[s_formCaptionTextColorChanged] is EventHandler eventHandler)
25002598
{
25012599
eventHandler(this, e);
25022600
}
@@ -4211,6 +4309,29 @@ protected override void OnHandleCreated(EventArgs e)
42114309
{
42124310
_formStateEx[s_formStateExUseMdiChildProc] = (IsMdiChild && Visible) ? 1 : 0;
42134311
base.OnHandleCreated(e);
4312+
4313+
if (Properties.TryGetValue(s_propFormBorderColor, out Color? formBorderColor))
4314+
{
4315+
SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR, formBorderColor.Value);
4316+
}
4317+
4318+
if (Properties.TryGetValue(s_propFormCaptionBackColor, out Color? formCaptionBackColor))
4319+
{
4320+
SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_CAPTION_COLOR, formCaptionBackColor.Value);
4321+
}
4322+
4323+
if (Properties.TryGetValue(s_propFormCaptionTextColor, out Color? formCaptionTextColor))
4324+
{
4325+
SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_TEXT_COLOR, formCaptionTextColor.Value);
4326+
}
4327+
4328+
#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.
4329+
if (Properties.TryGetValue(s_propFormCornerPreference, out FormCornerPreference? cornerPreference))
4330+
{
4331+
SetFormCornerPreferenceInternal(cornerPreference.Value);
4332+
}
4333+
#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.
4334+
42144335
UpdateLayered();
42154336
}
42164337

@@ -5814,7 +5935,7 @@ public DialogResult ShowDialog(IWin32Window? owner)
58145935
/// This method immediately returns, even if the form is large and takes a long time to be set up.
58155936
/// </para>
58165937
/// <para>
5817-
/// If the form is already displayed asynchronously by <see cref="Form.ShowAsync"/>, an <see cref="InvalidOperationException"/> will be thrown.
5938+
/// If the form is already displayed asynchronously by <see cref="ShowAsync"/>, an <see cref="InvalidOperationException"/> will be thrown.
58185939
/// </para>
58195940
/// <para>
58205941
/// An <see cref="InvalidOperationException"/> will also occur if no <see cref="WindowsFormsSynchronizationContext"/> could be retrieved or installed.
@@ -5849,7 +5970,7 @@ public DialogResult ShowDialog(IWin32Window? owner)
58495970
/// This method immediately returns, even if the form is large and takes a long time to be set up.
58505971
/// </para>
58515972
/// <para>
5852-
/// If the form is already displayed asynchronously by <see cref="Form.ShowAsync"/>, an <see cref="InvalidOperationException"/> will be thrown.
5973+
/// If the form is already displayed asynchronously by <see cref="ShowAsync"/>, an <see cref="InvalidOperationException"/> will be thrown.
58535974
/// </para>
58545975
/// <para>
58555976
/// An <see cref="InvalidOperationException"/> will also occur if no <see cref="WindowsFormsSynchronizationContext"/> could be retrieved or installed.

0 commit comments

Comments
 (0)