44using EventLogExpert . Eventing . EventResolvers ;
55using EventLogExpert . Eventing . Helpers ;
66using EventLogExpert . Services ;
7+ using EventLogExpert . UI ;
78using EventLogExpert . UI . Interfaces ;
89using EventLogExpert . UI . Models ;
910using EventLogExpert . UI . Options ;
@@ -19,6 +20,7 @@ namespace EventLogExpert;
1920public sealed partial class App : Application
2021{
2122 private readonly MainPage _mainPage ;
23+ private readonly ISettingsService _settings ;
2224
2325 public App (
2426 IDispatcher fluxorDispatcher ,
@@ -40,6 +42,13 @@ public App(
4042 {
4143 InitializeComponent ( ) ;
4244
45+ _settings = settings ;
46+
47+ // Apply native (XAML) theme before constructing MainPage so the initial
48+ // MenuFlyout / native control tree is created under the correct theme.
49+ ApplyNativeTheme ( _settings . Theme ) ;
50+ _settings . ThemeChanged += OnThemeChanged ;
51+
4352 _mainPage = new MainPage (
4453 fluxorDispatcher ,
4554 databaseCollectionProvider ,
@@ -73,6 +82,91 @@ protected override Window CreateWindow(IActivationState? activationState)
7382 window . Width = 2000 ;
7483 }
7584
85+ // The WinUI MenuBar (rendered for ContentPage.MenuBarItems) is a
86+ // native control whose theme is driven by the WinUI window root's
87+ // FrameworkElement.RequestedTheme - MAUI's UserAppTheme alone does
88+ // not propagate to it. Apply once the platform handler is attached.
89+ window . HandlerChanged += ( _ , _ ) => ApplyPlatformWindowTheme ( ) ;
90+
7691 return window ;
7792 }
93+
94+ private void ApplyNativeTheme ( Theme theme )
95+ {
96+ UserAppTheme = theme switch
97+ {
98+ Theme . Light => AppTheme . Light ,
99+ Theme . Dark => AppTheme . Dark ,
100+ _ => AppTheme . Unspecified ,
101+ } ;
102+
103+ ApplyPlatformWindowTheme ( ) ;
104+ }
105+
106+ private void OnThemeChanged ( ) =>
107+ // ThemeChanged may be raised from non-UI threads (Blazor JSInterop /
108+ // Fluxor effects). UserAppTheme must be set on the MAUI UI thread.
109+ MainThread . BeginInvokeOnMainThread ( ( ) => ApplyNativeTheme ( _settings . Theme ) ) ;
110+
111+ private void ApplyPlatformWindowTheme ( )
112+ {
113+ // The WinUI window root hosts the native title bar AND the MAUI
114+ // AppTitleBar (which contains the MenuBar). Both follow the root's
115+ // RequestedTheme. Force Dark so the entire top chrome stays dark
116+ // regardless of the user's selected app theme. The Blazor page is in
117+ // a WebView and doesn't inherit RequestedTheme - it follows the user
118+ // theme independently via CSS data-theme.
119+ foreach ( var window in Windows )
120+ {
121+ if ( window . Handler ? . PlatformView is not Microsoft . UI . Xaml . Window winUiWindow )
122+ {
123+ continue ;
124+ }
125+
126+ if ( winUiWindow . Content is Microsoft . UI . Xaml . FrameworkElement root )
127+ {
128+ root . RequestedTheme = Microsoft . UI . Xaml . ElementTheme . Dark ;
129+ }
130+
131+ ForceDarkTitleBar ( winUiWindow ) ;
132+ }
133+ }
134+
135+ private static void ForceDarkTitleBar ( Microsoft . UI . Xaml . Window winUiWindow )
136+ {
137+ try
138+ {
139+ var titleBar = winUiWindow . AppWindow ? . TitleBar ;
140+
141+ if ( titleBar is null ) { return ; }
142+
143+ if ( ! Microsoft . UI . Windowing . AppWindowTitleBar . IsCustomizationSupported ( ) ) { return ; }
144+
145+ var background = global ::Windows . UI . Color . FromArgb ( 0xFF , 0x22 , 0x22 , 0x22 ) ;
146+ var foreground = global ::Windows . UI . Color . FromArgb ( 0xFF , 0xFF , 0xFF , 0xFF ) ;
147+ var inactiveBackground = global ::Windows . UI . Color . FromArgb ( 0xFF , 0x2D , 0x2D , 0x2D ) ;
148+ var inactiveForeground = global ::Windows . UI . Color . FromArgb ( 0xFF , 0x99 , 0x99 , 0x99 ) ;
149+ var hoverBackground = global ::Windows . UI . Color . FromArgb ( 0xFF , 0x35 , 0x35 , 0x35 ) ;
150+ var pressedBackground = global ::Windows . UI . Color . FromArgb ( 0xFF , 0x44 , 0x44 , 0x44 ) ;
151+
152+ titleBar . BackgroundColor = background ;
153+ titleBar . ForegroundColor = foreground ;
154+ titleBar . InactiveBackgroundColor = inactiveBackground ;
155+ titleBar . InactiveForegroundColor = inactiveForeground ;
156+
157+ titleBar . ButtonBackgroundColor = background ;
158+ titleBar . ButtonForegroundColor = foreground ;
159+ titleBar . ButtonHoverBackgroundColor = hoverBackground ;
160+ titleBar . ButtonHoverForegroundColor = foreground ;
161+ titleBar . ButtonPressedBackgroundColor = pressedBackground ;
162+ titleBar . ButtonPressedForegroundColor = foreground ;
163+ titleBar . ButtonInactiveBackgroundColor = inactiveBackground ;
164+ titleBar . ButtonInactiveForegroundColor = inactiveForeground ;
165+ }
166+ catch ( System . Runtime . InteropServices . COMException )
167+ {
168+ // Window has been closed/disposed between the event firing and
169+ // here. Safe to ignore.
170+ }
171+ }
78172}
0 commit comments