From ed13e516760015b020cb99cd3b9aac2e4d0676b8 Mon Sep 17 00:00:00 2001 From: Logan H Date: Fri, 12 Jun 2026 11:12:21 +0100 Subject: [PATCH] fix: eliminate black-square flash on startup, start cleanly in tray StartupUri="MainWindow.xaml" forced WPF to Show() the window during startup; the StartInTray check (and Hide()) only ran later in the Loaded handler. With WindowStyle="None" + WindowChrome, the frame shown before the WPF content painted appeared as a black square that then vanished. Stop showing the window when starting in the tray. App.OnStartup now creates MainWindow explicitly and calls InitializeApplication(), which realizes the native handle via WindowInteropHelper.EnsureHandle() (so global hotkeys can still bind) without ever painting the window. The window is only Show()n when StartInTray is false. ShutdownMode is set to OnExplicitShutdown so the app doesn't quit when a transient dialog (e.g. the editor) closes while the main window has never been shown. Verified: build clean, 109/109 tests pass, and an EnumWindows sweep confirms zero visible top-level windows for the running process. Co-Authored-By: Claude Opus 4.8 --- MoneyShot/App.xaml | 2 +- MoneyShot/App.xaml.cs | 8 ++++++++ MoneyShot/MainWindow.xaml.cs | 32 +++++++++++++++++++++++--------- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/MoneyShot/App.xaml b/MoneyShot/App.xaml index cbc54fe..9e93348 100644 --- a/MoneyShot/App.xaml +++ b/MoneyShot/App.xaml @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MoneyShot" - StartupUri="MainWindow.xaml"> + ShutdownMode="OnExplicitShutdown"> diff --git a/MoneyShot/App.xaml.cs b/MoneyShot/App.xaml.cs index fb8a38c..47981f1 100644 --- a/MoneyShot/App.xaml.cs +++ b/MoneyShot/App.xaml.cs @@ -32,6 +32,14 @@ protected override void OnStartup(StartupEventArgs e) } base.OnStartup(e); + + // StartupUri is intentionally not used. It calls Show() during startup, which paints an + // unrendered (black) frame before the Loaded handler can hide it when StartInTray is set. + // Instead we create the window here and let InitializeApplication decide whether to show + // it or just realize its handle for hotkeys — see MainWindow.InitializeApplication. + var mainWindow = new MainWindow(); + MainWindow = mainWindow; + mainWindow.InitializeApplication(); } protected override void OnExit(ExitEventArgs e) diff --git a/MoneyShot/MainWindow.xaml.cs b/MoneyShot/MainWindow.xaml.cs index 32b8c66..dfcb21c 100644 --- a/MoneyShot/MainWindow.xaml.cs +++ b/MoneyShot/MainWindow.xaml.cs @@ -3,6 +3,7 @@ using System.Windows.Controls; using System.Windows.Forms; using System.Windows.Input; +using System.Windows.Interop; using System.Windows.Media; using MoneyShot.Services; using MoneyShot.Views; @@ -37,27 +38,40 @@ public MainWindow() _historyService = new HistoryService(); SetupSystemTray(); - Loaded += MainWindow_Loaded; } - private async void MainWindow_Loaded(object sender, RoutedEventArgs e) + /// + /// Performs post-construction startup: realizes the native handle, binds global hotkeys, and + /// shows the window only when the user hasn't opted to start in the tray. Called once from + /// . When is set the + /// window is never shown — creates the HWND that + /// hotkey registration needs without painting anything, which avoids the black-frame flash that + /// Show()-then-Hide() produced. + /// + public void InitializeApplication() { try { - _hotKeyService.Initialize(this); - RegisterHotKeys(); - PopulateMonitorButtons(); - - // Check if app should start in tray var settings = _settingsService.LoadSettings(); + if (settings.StartInTray) { - Hide(); + // Realize the Win32 handle without making the window visible. + new WindowInteropHelper(this).EnsureHandle(); + } + else + { + ShowMainWindow(); } + // The HWND now exists (via EnsureHandle or Show), so global hotkeys can bind to it. + _hotKeyService.Initialize(this); + RegisterHotKeys(); + PopulateMonitorButtons(); + if (settings.CheckForUpdatesOnStartup) { - await CheckForUpdatesOnStartupAsync(); + _ = CheckForUpdatesOnStartupAsync(); } } catch (Exception ex)