diff --git a/AutoDarkModeApp/App.xaml.cs b/AutoDarkModeApp/App.xaml.cs
index 623aef720..d44d09d6c 100644
--- a/AutoDarkModeApp/App.xaml.cs
+++ b/AutoDarkModeApp/App.xaml.cs
@@ -25,90 +25,106 @@ public partial class App : Application
// https://docs.microsoft.com/dotnet/core/extensions/logging
public IHost Host { get; }
+ ///
+ /// Retrieves a registered service of the specified type from the application's dependency injection container.
+ ///
+ /// Use this method to access services configured in the application's dependency injection
+ /// container. Ensure that the service type is registered in the ConfigureServices method of App.xaml.cs before
+ /// calling this method.
+ /// The type of service to retrieve. Must be a reference type and registered in the application's service
+ /// collection.
+ /// An instance of the requested service type .
+ /// Thrown if the requested service type is not registered in the application's service
+ /// collection.
public static T GetService()
where T : class
{
if ((Current as App)!.Host.Services.GetService(typeof(T)) is not T service)
{
- throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs.");
+ throw new InvalidOperationException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs.");
}
return service;
}
+ public static MainWindow MainWindow { get; private set; } = null!;
+
+ public App()
+ {
+ CheckAppMutex();
+ InitializeComponent();
+
+ Host = Microsoft.Extensions.Hosting.Host
+ .CreateDefaultBuilder()
+ .UseContentRoot(AppContext.BaseDirectory)
+ .ConfigureServices((context, services) =>
+ {
+ // Services
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddSingleton();
+ services.AddSingleton();
+
+ // Window
+ // NOTE: The MainWindow is registered as a singleton because we only need one instance.
+ services.AddSingleton();
+
+ // Views and ViewModels
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+
+ // Configuration
+ services.Configure(context.Configuration.GetSection(nameof(LocalSettingsOptions)));
+ }
+ )
+ .Build();
+
+ UnhandledException += App_UnhandledException;
+ }
+
public static void CheckAppMutex()
{
if (!Mutex.WaitOne(TimeSpan.FromMilliseconds(50), false) && !Debugger.IsAttached)
{
- List processes = [.. Process.GetProcessesByName("AutoDarkModeApp")];
+ var processes = Process.GetProcessesByName("AutoDarkModeApp").Where(p => p.Id != Environment.ProcessId).ToList();
if (processes.Count > 0)
{
Helpers.WindowHelper.BringProcessToFront(processes[0]);
- Environment.Exit(-1);
+ App.Current.Exit();
}
}
}
- public static Window MainWindow { get; set; } = Window.Current;
-
- public App()
- {
- CheckAppMutex();
-
- InitializeComponent();
-
- Host = Microsoft.Extensions.Hosting.Host.
- CreateDefaultBuilder().
- UseContentRoot(AppContext.BaseDirectory).
- ConfigureServices((context, services) =>
- {
- // Services
- services.AddSingleton();
- services.AddSingleton();
-
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
-
- services.AddSingleton();
- services.AddSingleton();
-
- // Views and ViewModels
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
-
- // Configuration
- services.Configure(context.Configuration.GetSection(nameof(LocalSettingsOptions)));
- }).
- Build();
-
- UnhandledException += App_UnhandledException;
- }
-
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
// TODO: Log and handle exceptions as appropriate.
@@ -124,26 +140,26 @@ protected async override void OnLaunched(LaunchActivatedEventArgs args)
if (arguments.Length > 1)
{
new PipeClient().SendMessageAndGetReply(arguments[1]);
- Environment.Exit(-1);
+ App.Current.Exit();
+ return;
}
// Set App and Svc language
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = await LanguageHelper.GetDefaultLanguageAsync();
- var builder = AdmConfigBuilder.Instance();
- builder.Load();
- try
+ await Task.Run(() =>
{
- builder.Config.Tunable.UICulture = LanguageHelper.SelectedLanguageCode; // save before activation SVC (think of first-launch scenario)
+ var builder = AdmConfigBuilder.Instance();
+ builder.Load();
+ builder.Config.Tunable.UICulture = LanguageHelper.SelectedLanguageCode; // For Svc and other services that need to know the UI culture
builder.Save();
- }
- catch (Exception)
- {
- // We can't show a dialog here
- //await App.GetService().ShowErrorMessage(ex, App.MainWindow.Content.XamlRoot, "Startup", "Failed to force-set Svc language");
- }
+ });
+
+ // NOTE: Here we use the DI container to get the MainWindow and set it as a static property, which not only conforms to the standard, but also facilitates other places to access the MainWindow.
+ MainWindow = GetService();
+ MainWindow.Closed += async (s, e) => await GetService().CloseAsync();
- MainWindow = new MainWindow();
+ await GetService().ActivateAsync(args);
- await App.GetService().ActivateAsync(args);
+ // NOTE: The MainWindow must be activated (i.e. made visible) in the ActivationService, not here in App.xaml.cs, because there are navigation events and adjustment of window position and size.
}
}
diff --git a/AutoDarkModeApp/Contracts/Services/ICloseService.cs b/AutoDarkModeApp/Contracts/Services/ICloseService.cs
new file mode 100644
index 000000000..28deb2ae3
--- /dev/null
+++ b/AutoDarkModeApp/Contracts/Services/ICloseService.cs
@@ -0,0 +1,6 @@
+namespace AutoDarkModeApp.Contracts.Services;
+
+public interface ICloseService
+{
+ Task CloseAsync();
+}
diff --git a/AutoDarkModeApp/Helpers/LanguageHelper.cs b/AutoDarkModeApp/Helpers/LanguageHelper.cs
index 63dd336eb..5cc0956ab 100644
--- a/AutoDarkModeApp/Helpers/LanguageHelper.cs
+++ b/AutoDarkModeApp/Helpers/LanguageHelper.cs
@@ -9,7 +9,6 @@ public static class LanguageHelper
{
public static string SelectedLanguageCode { get; set; } = "en-US"; // equal to
- private static readonly ILocalSettingsService _localSettingsService = App.GetService()!;
public static readonly string[] SupportedCultures =
[
// Left-to-Right (LTR) languages
@@ -23,7 +22,8 @@ public static class LanguageHelper
public static async Task GetDefaultLanguageAsync()
{
- var language = await _localSettingsService.ReadSettingAsync("SelectedLanguageCode");
+ var localSettingsService = App.GetService();
+ var language = await localSettingsService.ReadSettingAsync("SelectedLanguageCode");
if (!string.IsNullOrEmpty(language) && SupportedCultures.Contains(language))
{
SelectedLanguageCode = language;
@@ -56,7 +56,7 @@ public static async Task GetDefaultLanguageAsync()
}
// else keep the default "en-US"
}
- await _localSettingsService.SaveSettingAsync("SelectedLanguageCode", SelectedLanguageCode);
+ await localSettingsService.SaveSettingAsync("SelectedLanguageCode", SelectedLanguageCode);
}
return SelectedLanguageCode;
}
diff --git a/AutoDarkModeApp/MainWindow.xaml.cs b/AutoDarkModeApp/MainWindow.xaml.cs
index 77b25808e..8acb6a73c 100644
--- a/AutoDarkModeApp/MainWindow.xaml.cs
+++ b/AutoDarkModeApp/MainWindow.xaml.cs
@@ -12,14 +12,16 @@ public sealed partial class MainWindow : Window
{
private readonly INavigationService _navigationService;
- public MainWindow()
+ [DllImport("user32.dll")]
+ private static extern uint GetDpiForWindow(IntPtr hWnd);
+
+ public MainWindow(INavigationService navigationService)
{
- _navigationService = App.GetService();
+ _navigationService = navigationService;
InitializeComponent();
- // TODO: Set the title bar icon by updating /Assets/WindowIcon.ico.
- // A custom title bar is required for full window theme and Mica support.
- // https://docs.microsoft.com/windows/apps/develop/title-bar?tabs=winui3#full-customization
+ Title = Debugger.IsAttached ? "Auto Dark Mode Debug" : "Auto Dark Mode";
+
ExtendsContentIntoTitleBar = true;
SetTitleBar(TitleBar);
TitleBar.Subtitle = Debugger.IsAttached ? "Debug" : "";
@@ -27,32 +29,24 @@ public MainWindow()
ApplySystemThemeToCaptionButtons();
- AppWindow.SetIcon(Path.Combine(AppContext.BaseDirectory, "Assets/AutoDarkModeIcon.ico"));
- AppWindow.SetTaskbarIcon(Path.Combine(AppContext.BaseDirectory, "Assets/AutoDarkModeIcon.ico"));
- AppWindow.SetTitleBarIcon(Path.Combine(AppContext.BaseDirectory, "Assets/AutoDarkModeIcon.ico"));
+ var iconPath = Path.Combine(AppContext.BaseDirectory, "Assets/AutoDarkModeIcon.ico");
+ AppWindow.SetIcon(iconPath);
+ AppWindow.SetTaskbarIcon(iconPath);
+ AppWindow.SetTitleBarIcon(iconPath);
- Title = Debugger.IsAttached ? "Auto Dark Mode Debug" : "Auto Dark Mode";
+ IntPtr hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
+ uint dpi = GetDpiForWindow(hwnd);
+ double scaleFactor = dpi / 96.0;
- // TODO: No one knows what the correct way to use it is. Waiting for official examples.
- [DllImport("user32.dll")]
- static extern uint GetDpiForWindow([In] IntPtr hwnd);
- IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
- var dpiForWindow = GetDpiForWindow(hWnd);
- double scaleFactor = dpiForWindow / 96.0;
if (AppWindow.Presenter is OverlappedPresenter presenter)
{
- presenter.PreferredMinimumWidth = (int)(870 * scaleFactor);
+ presenter.PreferredMinimumWidth = (int)(860 * scaleFactor);
presenter.PreferredMinimumHeight = (int)(600 * scaleFactor);
- presenter.PreferredMaximumHeight = 10000;
- presenter.PreferredMaximumWidth = 10000;
}
_navigationService.Frame = NavigationFrame;
_navigationService.InitializeNavigationView(NavigationViewControl);
-
_navigationService.InitializeBreadcrumbBar(BreadcrumBarControl);
-
- Closed += MainWindow_Closed;
}
private void NavViewTitleBar_BackRequested(Microsoft.UI.Xaml.Controls.TitleBar sender, object args)
@@ -74,36 +68,4 @@ private void ApplySystemThemeToCaptionButtons()
AppWindow.TitleBar.ButtonHoverBackgroundColor = backgroundHoverColor;
}
- private async void MainWindow_Closed(object sender, WindowEventArgs args)
- {
- var presenter = AppWindow.Presenter as OverlappedPresenter;
- var position = AppWindow.Position;
- var size = AppWindow.Size;
- var localSettings = App.GetService();
- await Task.Run(async () =>
- {
- if (presenter != null)
- {
- await localSettings.SaveSettingAsync("WindowState", (int)presenter.State);
-
- if (presenter.State == OverlappedPresenterState.Restored)
- {
- await localSettings.SaveSettingAsync("X", position.X);
- await localSettings.SaveSettingAsync("Y", position.Y);
- await localSettings.SaveSettingAsync("Width", size.Width);
- await localSettings.SaveSettingAsync("Height", size.Height);
- }
- }
-
- //TODO: MapLocationFinder will make WinUI app hang on exit, more information on https://github.com/microsoft/microsoft-ui-xaml/issues/10229
- try
- {
- Process.GetCurrentProcess().Kill();
- }
- catch (Exception ex)
- {
- Debug.WriteLine(ex.Message);
- }
- });
- }
}
diff --git a/AutoDarkModeApp/Services/CloseService.cs b/AutoDarkModeApp/Services/CloseService.cs
new file mode 100644
index 000000000..10af46ba6
--- /dev/null
+++ b/AutoDarkModeApp/Services/CloseService.cs
@@ -0,0 +1,26 @@
+using AutoDarkModeApp.Contracts.Services;
+using Microsoft.UI.Windowing;
+
+namespace AutoDarkModeApp.Services;
+
+public class CloseService(ILocalSettingsService localSettingsService) : ICloseService
+{
+ public async Task CloseAsync()
+ {
+ if (App.MainWindow.AppWindow.Presenter is OverlappedPresenter presenter)
+ {
+ var position = App.MainWindow.AppWindow.Position;
+ var size = App.MainWindow.AppWindow.Size;
+
+ await localSettingsService.SaveSettingAsync("WindowState", (int)presenter.State);
+
+ if (presenter.State == OverlappedPresenterState.Restored)
+ {
+ await localSettingsService.SaveSettingAsync("X", position.X);
+ await localSettingsService.SaveSettingAsync("Y", position.Y);
+ await localSettingsService.SaveSettingAsync("Width", size.Width);
+ await localSettingsService.SaveSettingAsync("Height", size.Height);
+ }
+ }
+ }
+}