From 3daf1b0606129ae0c962e46525539064f2ee0a56 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:33:21 -0500 Subject: [PATCH 1/2] Replace custom TrayToolTip with plain ToolTipText to fix crash The custom visual TrayToolTip (Border + TextBlock) triggers a known race condition in Hardcodet.NotifyIcon.Wpf where Popup.CreateWindow throws "The root Visual of a VisualTarget cannot have a parent." This crash poisons the ReaderWriterLockSlim on the UI thread, breaking Overview queries permanently until restart. Plain string ToolTipText avoids the WPF Popup infrastructure entirely. Fixes #422 Co-Authored-By: Claude Opus 4.6 --- Dashboard/Services/NotificationService.cs | 27 +++---------------- Lite/Services/SystemTrayService.cs | 33 +++++------------------ 2 files changed, 10 insertions(+), 50 deletions(-) diff --git a/Dashboard/Services/NotificationService.cs b/Dashboard/Services/NotificationService.cs index 5e30b333..1a2e1be1 100644 --- a/Dashboard/Services/NotificationService.cs +++ b/Dashboard/Services/NotificationService.cs @@ -42,29 +42,10 @@ public void Initialize() _trayIcon = new TaskbarIcon(); - bool HasLightBackground = Helpers.ThemeManager.HasLightBackground; - - /* Custom tooltip styled to match current theme */ - _trayIcon.TrayToolTip = new Border - { - Background = new SolidColorBrush(HasLightBackground - ? (Color)ColorConverter.ConvertFromString("#FFFFFF") - : (Color)ColorConverter.ConvertFromString("#22252b")), - BorderBrush = new SolidColorBrush(HasLightBackground - ? (Color)ColorConverter.ConvertFromString("#DEE2E6") - : (Color)ColorConverter.ConvertFromString("#33363e")), - BorderThickness = new Thickness(1), - Padding = new Thickness(10, 8, 10, 8), - CornerRadius = new CornerRadius(4), - Child = new TextBlock - { - Text = "SQL Server Performance Monitor", - Foreground = new SolidColorBrush(HasLightBackground - ? (Color)ColorConverter.ConvertFromString("#1A1D23") - : (Color)ColorConverter.ConvertFromString("#E4E6EB")), - FontSize = 12 - } - }; + /* Use plain string tooltip to avoid Hardcodet TrayToolTip crash (issue #422). + Custom visual tooltips trigger a race condition in Popup.CreateWindow + that throws "The root Visual of a VisualTarget cannot have a parent." */ + _trayIcon.ToolTipText = "SQL Server Performance Monitor"; // Load icon from embedded resource using pack URI try diff --git a/Lite/Services/SystemTrayService.cs b/Lite/Services/SystemTrayService.cs index a54bfc19..77d8b9b9 100644 --- a/Lite/Services/SystemTrayService.cs +++ b/Lite/Services/SystemTrayService.cs @@ -25,7 +25,6 @@ public class SystemTrayService : IDisposable private readonly CollectionBackgroundService? _backgroundService; private bool _disposed; private MenuItem? _pauseResumeItem; - private TextBlock? _tooltipText; public SystemTrayService(Window mainWindow, CollectionBackgroundService? backgroundService = null) { @@ -43,30 +42,10 @@ public void Initialize() _trayIcon = new TaskbarIcon(); - bool HasLightBackground = Helpers.ThemeManager.HasLightBackground; - - /* Custom tooltip styled to match current theme */ - _tooltipText = new TextBlock - { - Text = "Performance Monitor Lite", - Foreground = new SolidColorBrush(HasLightBackground - ? (Color)ColorConverter.ConvertFromString("#1A1D23") - : (Color)ColorConverter.ConvertFromString("#E4E6EB")), - FontSize = 12 - }; - _trayIcon.TrayToolTip = new Border - { - Background = new SolidColorBrush(HasLightBackground - ? (Color)ColorConverter.ConvertFromString("#FFFFFF") - : (Color)ColorConverter.ConvertFromString("#22252b")), - BorderBrush = new SolidColorBrush(HasLightBackground - ? (Color)ColorConverter.ConvertFromString("#DEE2E6") - : (Color)ColorConverter.ConvertFromString("#33363e")), - BorderThickness = new Thickness(1), - Padding = new Thickness(10, 8, 10, 8), - CornerRadius = new CornerRadius(4), - Child = _tooltipText - }; + /* Use plain string tooltip to avoid Hardcodet TrayToolTip crash (issue #422). + Custom visual tooltips trigger a race condition in Popup.CreateWindow + that throws "The root Visual of a VisualTarget cannot have a parent." */ + _trayIcon.ToolTipText = "Performance Monitor Lite"; /* Load icon */ try @@ -135,9 +114,9 @@ private void ToggleCollection() _pauseResumeItem.Icon = new TextBlock { Text = _backgroundService.IsPaused ? "▶" : "⏸", Background = Brushes.Transparent }; } - if (_tooltipText != null) + if (_trayIcon != null) { - _tooltipText.Text = _backgroundService.IsPaused + _trayIcon.ToolTipText = _backgroundService.IsPaused ? "Performance Monitor Lite (Paused)" : "Performance Monitor Lite"; } From 562269f61bf91b8813a11c552bc003dca9a5392a Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Wed, 4 Mar 2026 15:40:19 -0500 Subject: [PATCH 2/2] Restore custom TrayToolTip and silently handle Hardcodet crash (issue #422) Reverts the ToolTipText approach which caused context menu positioning and theme regressions. Instead, keeps the original custom TrayToolTip for proper dark theme styling and popup anchoring, and adds IsTrayToolTipCrash() detection in both apps' DispatcherUnhandledException handlers to silently swallow the rare Hardcodet VisualTarget race condition without showing error dialogs. Co-Authored-By: Claude Opus 4.6 --- Dashboard/App.xaml.cs | 20 +++++++++++++ Dashboard/Services/NotificationService.cs | 30 ++++++++++++++++--- Lite/App.xaml.cs | 20 +++++++++++++ Lite/Services/SystemTrayService.cs | 36 +++++++++++++++++++---- 4 files changed, 96 insertions(+), 10 deletions(-) diff --git a/Dashboard/App.xaml.cs b/Dashboard/App.xaml.cs index f920d823..c87d0743 100644 --- a/Dashboard/App.xaml.cs +++ b/Dashboard/App.xaml.cs @@ -95,6 +95,16 @@ private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { + /* Silently swallow Hardcodet TrayToolTip race condition (issue #422). + The crash occurs in Popup.CreateWindow when showing the custom visual tooltip + and is harmless — the tooltip simply doesn't show that one time. */ + if (IsTrayToolTipCrash(e.Exception)) + { + Logger.Warning("Suppressed Hardcodet TrayToolTip crash (issue #422)"); + e.Handled = true; + return; + } + Logger.Error("Unhandled Dispatcher Exception", e.Exception); MessageBox.Show( @@ -114,6 +124,16 @@ private void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEv e.SetObserved(); // Prevent process termination } + /// + /// Detects the Hardcodet TrayToolTip race condition crash (issue #422). + /// + private static bool IsTrayToolTipCrash(Exception ex) + { + return ex is ArgumentException + && ex.Message.Contains("VisualTarget") + && ex.StackTrace?.Contains("TaskbarIcon") == true; + } + private void CreateCrashDump(Exception? exception) { try diff --git a/Dashboard/Services/NotificationService.cs b/Dashboard/Services/NotificationService.cs index 1a2e1be1..e174f311 100644 --- a/Dashboard/Services/NotificationService.cs +++ b/Dashboard/Services/NotificationService.cs @@ -42,10 +42,32 @@ public void Initialize() _trayIcon = new TaskbarIcon(); - /* Use plain string tooltip to avoid Hardcodet TrayToolTip crash (issue #422). - Custom visual tooltips trigger a race condition in Popup.CreateWindow - that throws "The root Visual of a VisualTarget cannot have a parent." */ - _trayIcon.ToolTipText = "SQL Server Performance Monitor"; + bool HasLightBackground = Helpers.ThemeManager.HasLightBackground; + + /* Custom tooltip styled to match current theme. + Note: Hardcodet TrayToolTip can rarely trigger a race condition in Popup.CreateWindow + that throws "The root Visual of a VisualTarget cannot have a parent." (issue #422). + The DispatcherUnhandledException handler silently swallows this specific crash. */ + _trayIcon.TrayToolTip = new Border + { + Background = new SolidColorBrush(HasLightBackground + ? (Color)ColorConverter.ConvertFromString("#FFFFFF") + : (Color)ColorConverter.ConvertFromString("#22252b")), + BorderBrush = new SolidColorBrush(HasLightBackground + ? (Color)ColorConverter.ConvertFromString("#DEE2E6") + : (Color)ColorConverter.ConvertFromString("#33363e")), + BorderThickness = new Thickness(1), + Padding = new Thickness(10, 8, 10, 8), + CornerRadius = new CornerRadius(4), + Child = new TextBlock + { + Text = "SQL Server Performance Monitor", + Foreground = new SolidColorBrush(HasLightBackground + ? (Color)ColorConverter.ConvertFromString("#1A1D23") + : (Color)ColorConverter.ConvertFromString("#E4E6EB")), + FontSize = 12 + } + }; // Load icon from embedded resource using pack URI try diff --git a/Lite/App.xaml.cs b/Lite/App.xaml.cs index bf9c405d..8cc53c40 100644 --- a/Lite/App.xaml.cs +++ b/Lite/App.xaml.cs @@ -324,6 +324,16 @@ private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { + /* Silently swallow Hardcodet TrayToolTip race condition (issue #422). + The crash occurs in Popup.CreateWindow when showing the custom visual tooltip + and is harmless — the tooltip simply doesn't show that one time. */ + if (IsTrayToolTipCrash(e.Exception)) + { + AppLogger.Warn("Dispatcher", "Suppressed Hardcodet TrayToolTip crash (issue #422)"); + e.Handled = true; + return; + } + AppLogger.Error("Dispatcher", "Unhandled exception", e.Exception); AppLogger.Flush(); @@ -344,6 +354,16 @@ private void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEv e.SetObserved(); /* Prevent process termination */ } + /// + /// Detects the Hardcodet TrayToolTip race condition crash (issue #422). + /// + private static bool IsTrayToolTipCrash(Exception ex) + { + return ex is System.ArgumentException + && ex.Message.Contains("VisualTarget") + && ex.StackTrace?.Contains("TaskbarIcon") == true; + } + private static string FormatExceptionDetails(Exception? ex) { if (ex == null) return "Unknown error"; diff --git a/Lite/Services/SystemTrayService.cs b/Lite/Services/SystemTrayService.cs index 77d8b9b9..8c1968b5 100644 --- a/Lite/Services/SystemTrayService.cs +++ b/Lite/Services/SystemTrayService.cs @@ -25,6 +25,7 @@ public class SystemTrayService : IDisposable private readonly CollectionBackgroundService? _backgroundService; private bool _disposed; private MenuItem? _pauseResumeItem; + private TextBlock? _tooltipText; public SystemTrayService(Window mainWindow, CollectionBackgroundService? backgroundService = null) { @@ -42,10 +43,33 @@ public void Initialize() _trayIcon = new TaskbarIcon(); - /* Use plain string tooltip to avoid Hardcodet TrayToolTip crash (issue #422). - Custom visual tooltips trigger a race condition in Popup.CreateWindow - that throws "The root Visual of a VisualTarget cannot have a parent." */ - _trayIcon.ToolTipText = "Performance Monitor Lite"; + bool HasLightBackground = Helpers.ThemeManager.HasLightBackground; + + /* Custom tooltip styled to match current theme. + Note: Hardcodet TrayToolTip can rarely trigger a race condition in Popup.CreateWindow + that throws "The root Visual of a VisualTarget cannot have a parent." (issue #422). + The DispatcherUnhandledException handler silently swallows this specific crash. */ + _tooltipText = new TextBlock + { + Text = "Performance Monitor Lite", + Foreground = new SolidColorBrush(HasLightBackground + ? (Color)ColorConverter.ConvertFromString("#1A1D23") + : (Color)ColorConverter.ConvertFromString("#E4E6EB")), + FontSize = 12 + }; + _trayIcon.TrayToolTip = new Border + { + Background = new SolidColorBrush(HasLightBackground + ? (Color)ColorConverter.ConvertFromString("#FFFFFF") + : (Color)ColorConverter.ConvertFromString("#22252b")), + BorderBrush = new SolidColorBrush(HasLightBackground + ? (Color)ColorConverter.ConvertFromString("#DEE2E6") + : (Color)ColorConverter.ConvertFromString("#33363e")), + BorderThickness = new Thickness(1), + Padding = new Thickness(10, 8, 10, 8), + CornerRadius = new CornerRadius(4), + Child = _tooltipText + }; /* Load icon */ try @@ -114,9 +138,9 @@ private void ToggleCollection() _pauseResumeItem.Icon = new TextBlock { Text = _backgroundService.IsPaused ? "▶" : "⏸", Background = Brushes.Transparent }; } - if (_trayIcon != null) + if (_tooltipText != null) { - _trayIcon.ToolTipText = _backgroundService.IsPaused + _tooltipText.Text = _backgroundService.IsPaused ? "Performance Monitor Lite (Paused)" : "Performance Monitor Lite"; }