diff --git a/components/Helpers/src/CommunityToolkit.WinUI.Helpers.csproj b/components/Helpers/src/CommunityToolkit.WinUI.Helpers.csproj index c586beef..76f50f8f 100644 --- a/components/Helpers/src/CommunityToolkit.WinUI.Helpers.csproj +++ b/components/Helpers/src/CommunityToolkit.WinUI.Helpers.csproj @@ -11,7 +11,7 @@ - + @@ -20,6 +20,13 @@ \ + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/components/Helpers/src/MultiTarget.props b/components/Helpers/src/MultiTarget.props index b11c1942..c780abff 100644 --- a/components/Helpers/src/MultiTarget.props +++ b/components/Helpers/src/MultiTarget.props @@ -6,4 +6,4 @@ --> uwp;wasdk;wpf;wasm;linuxgtk;macos;ios;android; - \ No newline at end of file + diff --git a/components/Helpers/src/NativeMethods.json b/components/Helpers/src/NativeMethods.json new file mode 100644 index 00000000..be639985 --- /dev/null +++ b/components/Helpers/src/NativeMethods.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "allowMarshaling": false, + "comInterop": { + "preserveSigMethods": [ + "*" + ] + } +} diff --git a/components/Helpers/src/NativeMethods.txt b/components/Helpers/src/NativeMethods.txt new file mode 100644 index 00000000..3da417f6 --- /dev/null +++ b/components/Helpers/src/NativeMethods.txt @@ -0,0 +1,4 @@ +RegisterClassEx +DefWindowProc +CreateWindowEx +WM_SETTINGCHANGE diff --git a/components/Helpers/src/ThemeListener.cs b/components/Helpers/src/ThemeListener.cs index ecd04b13..aaacbf56 100644 --- a/components/Helpers/src/ThemeListener.cs +++ b/components/Helpers/src/ThemeListener.cs @@ -4,6 +4,8 @@ using Windows.Foundation.Metadata; using Windows.UI.ViewManagement; +using CommunityToolkit.WinUI.HelpersRns; + #if !WINAPPSDK using Windows.System; @@ -83,6 +85,15 @@ public ThemeListener(DispatcherQueue? dispatcherQueue = null) Window.Current.CoreWindow.Activated += CoreWindow_Activated; } +#if WINAPPSDK + ThemeListenerHelperWindow.Instance.ThemeChanged += this.Instance_ThemeChanged; +#endif + } + + private void Instance_ThemeChanged(ApplicationTheme theme) + { + CurrentTheme = theme; + ThemeChanged?.Invoke(this); } private async void Accessible_HighContrastChanged(AccessibilitySettings sender, object args) diff --git a/components/Helpers/src/ThemeListenerHelperWindow.cs b/components/Helpers/src/ThemeListenerHelperWindow.cs new file mode 100644 index 00000000..e4a7944c --- /dev/null +++ b/components/Helpers/src/ThemeListenerHelperWindow.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace CommunityToolkit.WinUI.HelpersRns; + +#if WINAPPSDK +internal class ThemeListenerHelperWindow +{ + public delegate void ThemeChangedHandler(ApplicationTheme theme); + + public static ThemeListenerHelperWindow Instance = new(); + + public event ThemeChangedHandler? ThemeChanged; + + private static string s_className = "ThemeListenerHelperWindow"; + + private HWND m_hwnd; + + private ThemeListenerHelperWindow() + { + s_className = "ThemeListenerHelperWindow"; + RegisterClass(); + m_hwnd = CreateWindow(); + } + + private static unsafe void RegisterClass() + { + WNDCLASSEXW wcx = default; + wcx.cbSize = (uint)sizeof(WNDCLASSEXW); + fixed (char* pClassName = s_className) + { + wcx.lpszClassName = pClassName; + wcx.lpfnWndProc = &WndProc; + if (PInvoke.RegisterClassEx(in wcx) is 0) + { + Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError()); + } + } + } + + private static unsafe HWND CreateWindow() + { + HWND hwnd = PInvoke.CreateWindowEx(0, s_className, string.Empty, 0, 0, 0, 0, 0, default, null, null, null); + if (hwnd == HWND.Null) + { + Marshal.ThrowExceptionForHR(Marshal.GetLastPInvokeError()); + } + return hwnd; + } + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])] + private static LRESULT WndProc(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam) + { + if (msg is PInvoke.WM_SETTINGCHANGE) + { + // REG_DWORD + object? value = Registry.GetValue( + @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", + "AppsUseLightTheme", + true); + + if (value is int dword) + { + Instance.ThemeChanged?.Invoke(dword is 1 ? ApplicationTheme.Light : ApplicationTheme.Dark); + } + } + + return PInvoke.DefWindowProc(hWnd, msg, wParam, lParam); + } +} +#endif