Skip to content

Commit 655d718

Browse files
committed
feat: SystemMenuThemeManager
1 parent 2888293 commit 655d718

File tree

4 files changed

+225
-2
lines changed

4 files changed

+225
-2
lines changed

src/Wpf.Ui.Test/App.xaml.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Diagnostics;
22
using System.Windows;
33
using System.Windows.Threading;
4+
using Wpf.Ui.Violeta.Appearance;
45
using Wpf.Ui.Violeta.Controls;
56
using Wpf.Ui.Violeta.Resources;
67

@@ -10,6 +11,7 @@ public partial class App : Application
1011
{
1112
public App()
1213
{
14+
SystemMenuThemeManager.Apply();
1315
Splash.ShowAsync("pack://application:,,,/Wpf.Ui.Test;component/wpfui.png", 0.98d);
1416
InitializeComponent();
1517

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using Microsoft.Win32;
2+
using System;
3+
using Wpf.Ui.Appearance;
4+
using Wpf.Ui.Violeta.Win32;
5+
6+
namespace Wpf.Ui.Violeta.Appearance;
7+
8+
public static class SystemMenuThemeManager
9+
{
10+
public static void Apply(SystemMenuTheme theme = SystemMenuTheme.Auto)
11+
{
12+
// Enable dark mode for context menus if using dark theme
13+
if (Environment.OSVersion.Version.Build >= 18362) // Windows 10 1903
14+
{
15+
if (theme == SystemMenuTheme.Auto)
16+
{
17+
// UxTheme methods will apply all of menus.
18+
// However, the Windows style system prefers that
19+
// Windows System Menu is based on `Apps Theme`,
20+
// and Tray Context Menu is based on `System Theme` when using a custom theme.
21+
// But actually we can't have our cake and eat it too.
22+
// Finally, we synchronize the theme styles of tray with higher usage rates.
23+
if (ThemeManager.GetSystemTheme() == SystemTheme.Dark)
24+
{
25+
_ = UxTheme.SetPreferredAppMode(UxTheme.PreferredAppMode.ForceDark);
26+
UxTheme.FlushMenuThemes();
27+
}
28+
29+
// Synchronize the theme with system settings
30+
SystemEvents.UserPreferenceChanged -= OnUserPreferenceChangedEventHandler;
31+
SystemEvents.UserPreferenceChanged += OnUserPreferenceChangedEventHandler;
32+
}
33+
else if (theme == SystemMenuTheme.Dark)
34+
{
35+
SystemEvents.UserPreferenceChanged -= OnUserPreferenceChangedEventHandler;
36+
_ = UxTheme.SetPreferredAppMode(UxTheme.PreferredAppMode.ForceDark);
37+
UxTheme.FlushMenuThemes();
38+
}
39+
else if (theme == SystemMenuTheme.Light)
40+
{
41+
SystemEvents.UserPreferenceChanged -= OnUserPreferenceChangedEventHandler;
42+
_ = UxTheme.SetPreferredAppMode(UxTheme.PreferredAppMode.ForceLight);
43+
UxTheme.FlushMenuThemes();
44+
}
45+
}
46+
}
47+
48+
private static void OnUserPreferenceChangedEventHandler(object sender, UserPreferenceChangedEventArgs e)
49+
{
50+
if (ThemeManager.GetSystemTheme() == SystemTheme.Dark)
51+
{
52+
_ = UxTheme.SetPreferredAppMode(UxTheme.PreferredAppMode.ForceDark);
53+
UxTheme.FlushMenuThemes();
54+
}
55+
else
56+
{
57+
_ = UxTheme.SetPreferredAppMode(UxTheme.PreferredAppMode.ForceLight);
58+
UxTheme.FlushMenuThemes();
59+
}
60+
}
61+
}
62+
63+
/// <summary>
64+
/// Theme in which an system menu is displayed.
65+
/// </summary>
66+
public enum SystemMenuTheme
67+
{
68+
/// <summary>
69+
/// Auto system theme.
70+
/// </summary>
71+
Auto = ApplicationTheme.Unknown,
72+
73+
/// <summary>
74+
/// Dark system theme.
75+
/// </summary>
76+
Dark = ApplicationTheme.Dark,
77+
78+
/// <summary>
79+
/// Light system theme.
80+
/// </summary>
81+
Light = ApplicationTheme.Light,
82+
}

src/Wpf.Ui.Violeta/Appearance/ThemeManager.cs

+110-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,15 @@ private static void ApplicationThemeManager_Changed(ApplicationTheme currentAppl
6969
return;
7070
}
7171

72-
public static ApplicationTheme GetSystemTheme()
72+
/// <summary>
73+
/// Get the theme of the application (<seealso cref="ApplicationThemeManager.GetAppTheme"/>).
74+
/// </summary>
75+
/// <returns>
76+
/// Only the following enum will be returned.
77+
/// <para><see cref="ApplicationTheme.Dark"/></para>
78+
/// <para><see cref="ApplicationTheme.Light"/></para>
79+
/// </returns>
80+
public static ApplicationTheme GetApplicationTheme()
7381
{
7482
using RegistryKey? key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
7583
object? registryValueObject = key?.GetValue("AppsUseLightTheme");
@@ -84,12 +92,112 @@ public static ApplicationTheme GetSystemTheme()
8492
return registryValue > 0 ? ApplicationTheme.Light : ApplicationTheme.Dark;
8593
}
8694

95+
/// <summary>
96+
/// Get the theme of the system (<seealso cref="SystemThemeManager.GetCachedSystemTheme"/>).
97+
/// </summary>
98+
/// <returns>
99+
/// Only the following enum will be returned.
100+
/// <para><see cref="SystemTheme.Dark"/></para>
101+
/// <para><see cref="SystemTheme.Light"/></para>
102+
/// </returns>
103+
public static SystemTheme GetSystemTheme()
104+
{
105+
return Get() switch
106+
{
107+
SystemTheme.Dark or SystemTheme.HCBlack or SystemTheme.Glow or SystemTheme.CapturedMotion => SystemTheme.Dark,
108+
_ => SystemTheme.Light,
109+
};
110+
111+
static SystemTheme Get()
112+
{
113+
var currentTheme =
114+
Registry.GetValue(
115+
"HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes",
116+
"CurrentTheme",
117+
"aero.theme"
118+
) as string
119+
?? string.Empty;
120+
121+
if (!string.IsNullOrEmpty(currentTheme))
122+
{
123+
currentTheme = currentTheme.ToLower().Trim();
124+
125+
// This may be changed in the next versions, check the Insider previews
126+
if (currentTheme.Contains("basic.theme"))
127+
{
128+
return SystemTheme.Light;
129+
}
130+
131+
if (currentTheme.Contains("aero.theme"))
132+
{
133+
return SystemTheme.Light;
134+
}
135+
136+
if (currentTheme.Contains("dark.theme"))
137+
{
138+
return SystemTheme.Dark;
139+
}
140+
141+
if (currentTheme.Contains("hcblack.theme"))
142+
{
143+
return SystemTheme.HCBlack;
144+
}
145+
146+
if (currentTheme.Contains("hcwhite.theme"))
147+
{
148+
return SystemTheme.HCWhite;
149+
}
150+
151+
if (currentTheme.Contains("hc1.theme"))
152+
{
153+
return SystemTheme.HC1;
154+
}
155+
156+
if (currentTheme.Contains("hc2.theme"))
157+
{
158+
return SystemTheme.HC2;
159+
}
160+
161+
if (currentTheme.Contains("themea.theme"))
162+
{
163+
return SystemTheme.Glow;
164+
}
165+
166+
if (currentTheme.Contains("themeb.theme"))
167+
{
168+
return SystemTheme.CapturedMotion;
169+
}
170+
171+
if (currentTheme.Contains("themec.theme"))
172+
{
173+
return SystemTheme.Sunrise;
174+
}
175+
176+
if (currentTheme.Contains("themed.theme"))
177+
{
178+
return SystemTheme.Flow;
179+
}
180+
}
181+
182+
/*if (currentTheme.Contains("custom.theme"))
183+
return ; custom can be light or dark*/
184+
var rawSystemUsesLightTheme =
185+
Registry.GetValue(
186+
"HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
187+
"SystemUsesLightTheme",
188+
1
189+
) ?? 1;
190+
191+
return rawSystemUsesLightTheme is 0 ? SystemTheme.Dark : SystemTheme.Light;
192+
}
193+
}
194+
87195
public static void Apply(ApplicationTheme theme)
88196
{
89197
if (theme == ApplicationTheme.Unknown)
90198
{
91199
// To change `Unknown` to `System`.
92-
theme = GetSystemTheme();
200+
theme = GetApplicationTheme();
93201
}
94202

95203
if (ApplicationThemeManager.GetAppTheme() != theme)

src/Wpf.Ui.Violeta/Win32/UxTheme.cs

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace Wpf.Ui.Violeta.Win32;
5+
6+
internal static class UxTheme
7+
{
8+
[DllImport("uxtheme.dll", EntryPoint = "#132", SetLastError = true, CharSet = CharSet.Unicode)]
9+
public static extern bool ShouldAppsUseDarkMode();
10+
11+
/// <summary>
12+
/// Windows 10 1903, aka 18362, broke the API.
13+
/// - Before 18362, the #135 is AllowDarkModeForApp(BOOL)
14+
/// - After 18362, the #135 is SetPreferredAppMode(PreferredAppMode)
15+
/// Since the support for AllowDarkModeForApp is uncertain, it will not be considered for use.
16+
/// </summary>
17+
[DllImport("uxtheme.dll", EntryPoint = "#135", SetLastError = true, CharSet = CharSet.Unicode)]
18+
public static extern int SetPreferredAppMode(PreferredAppMode preferredAppMode);
19+
20+
[DllImport("uxtheme.dll", EntryPoint = "#135", SetLastError = true, CharSet = CharSet.Unicode)]
21+
[Obsolete("Since the support for AllowDarkModeForApp is uncertain, it will not be considered for use.")]
22+
public static extern void AllowDarkModeForApp(bool allowDark);
23+
24+
[DllImport("uxtheme.dll", EntryPoint = "#136", SetLastError = true, CharSet = CharSet.Unicode)]
25+
public static extern void FlushMenuThemes();
26+
27+
[DllImport("uxtheme.dll", EntryPoint = "#138", SetLastError = true, CharSet = CharSet.Unicode)]
28+
public static extern bool ShouldSystemUseDarkMode();
29+
30+
public enum PreferredAppMode : int { Default, AllowDark, ForceDark, ForceLight, Max };
31+
}

0 commit comments

Comments
 (0)