diff --git a/Dashboard/Controls/MemoryContent.xaml.cs b/Dashboard/Controls/MemoryContent.xaml.cs index 1ce108c..691cbee 100644 --- a/Dashboard/Controls/MemoryContent.xaml.cs +++ b/Dashboard/Controls/MemoryContent.xaml.cs @@ -106,11 +106,10 @@ public MemoryContent() SetupChartContextMenus(); Loaded += OnLoaded; Helpers.ThemeManager.ThemeChanged += OnThemeChanged; - Unloaded += (_, _) => - { - Helpers.ThemeManager.ThemeChanged -= OnThemeChanged; - DisposeChartHelpers(); - }; + /* WPF fires Unloaded on every TabControl tab switch, not just on destruction. + Tearing down chart hover helpers here unsubscribes their MouseMove handlers + and they are never re-registered when the user returns — this is the + root cause of #916. Final disposal happens via ServerTab.CleanupOnClose. */ // Apply dark theme immediately so charts don't flash white before data loads TabHelpers.ApplyThemeToChart(MemoryStatsOverviewChart); @@ -136,6 +135,7 @@ public void DisposeChartHelpers() _memoryClerksHover?.Dispose(); _planCacheHover?.Dispose(); _memoryPressureEventsHover?.Dispose(); + Helpers.ThemeManager.ThemeChanged -= OnThemeChanged; } private void OnLoaded(object sender, RoutedEventArgs e) diff --git a/Dashboard/Controls/QueryPerformanceContent.xaml.cs b/Dashboard/Controls/QueryPerformanceContent.xaml.cs index 3d998fd..e500bf8 100644 --- a/Dashboard/Controls/QueryPerformanceContent.xaml.cs +++ b/Dashboard/Controls/QueryPerformanceContent.xaml.cs @@ -241,8 +241,10 @@ private void OnUnloaded(object sender, RoutedEventArgs e) _qsRegressionsUnfilteredData = null; _lrqPatternsUnfilteredData = null; - DisposeChartHelpers(); - Helpers.ThemeManager.ThemeChanged -= OnThemeChanged; + /* WPF fires Unloaded on every TabControl tab switch, not just on destruction. + Tearing down chart hover helpers or unsubscribing ThemeManager here breaks + tooltips and theme refresh after a tab switch (#916). Final cleanup happens + via ServerTab.CleanupOnClose → DisposeChartHelpers. */ } public void DisposeChartHelpers() @@ -251,6 +253,7 @@ public void DisposeChartHelpers() _procDurationHover?.Dispose(); _qsDurationHover?.Dispose(); _execTrendsHover?.Dispose(); + Helpers.ThemeManager.ThemeChanged -= OnThemeChanged; } private void OnThemeChanged(string _) diff --git a/Dashboard/Controls/ResourceMetricsContent.xaml.cs b/Dashboard/Controls/ResourceMetricsContent.xaml.cs index 3d4c3af..45dc3ca 100644 --- a/Dashboard/Controls/ResourceMetricsContent.xaml.cs +++ b/Dashboard/Controls/ResourceMetricsContent.xaml.cs @@ -130,11 +130,10 @@ public ResourceMetricsContent() SetupChartContextMenus(); Loaded += OnLoaded; Helpers.ThemeManager.ThemeChanged += OnThemeChanged; - Unloaded += (_, _) => - { - Helpers.ThemeManager.ThemeChanged -= OnThemeChanged; - DisposeChartHelpers(); - }; + /* WPF fires Unloaded on every TabControl tab switch, not just on destruction. + Tearing down chart hover helpers here unsubscribes their MouseMove handlers + and they are never re-registered when the user returns — this is the + root cause of #916. Final disposal happens via ServerTab.CleanupOnClose. */ // Apply dark theme immediately so charts don't flash white before data loads TabHelpers.ApplyThemeToChart(LatchStatsChart); @@ -175,6 +174,7 @@ public void DisposeChartHelpers() _waitStatsHover?.Dispose(); _tempdbStatsHover?.Dispose(); _tempDbLatencyHover?.Dispose(); + Helpers.ThemeManager.ThemeChanged -= OnThemeChanged; } private void OnLoaded(object sender, RoutedEventArgs e) diff --git a/Dashboard/Controls/SystemEventsContent.xaml.cs b/Dashboard/Controls/SystemEventsContent.xaml.cs index 726f3d2..2ddb8af 100644 --- a/Dashboard/Controls/SystemEventsContent.xaml.cs +++ b/Dashboard/Controls/SystemEventsContent.xaml.cs @@ -175,20 +175,40 @@ public SystemEventsContent() private void OnUnloaded(object sender, RoutedEventArgs e) { - /* Unsubscribe from filter popup events to prevent memory leaks */ + /* WPF fires Unloaded on every TabControl tab switch, not just on destruction. + Unsubscribing ThemeManager or filter-popup events here breaks them on + return to the tab (#916 family). Final cleanup happens via + ServerTab.CleanupOnClose → DisposeChartHelpers. */ + } + + public void DisposeChartHelpers() + { + _badPagesHover?.Dispose(); + _dumpRequestsHover?.Dispose(); + _accessViolationsHover?.Dispose(); + _writeAccessViolationsHover?.Dispose(); + _nonYieldingTasksHover?.Dispose(); + _latchWarningsHover?.Dispose(); + _sickSpinlocksHover?.Dispose(); + _cpuComparisonHover?.Dispose(); + _severeErrorsHover?.Dispose(); + _ioIssuesHover?.Dispose(); + _longestPendingIoHover?.Dispose(); + _schedulerIssuesHover?.Dispose(); + _memoryConditionsHover?.Dispose(); + _cpuTasksHover?.Dispose(); + _memoryBrokerHover?.Dispose(); + _memoryBrokerRatioHover?.Dispose(); + _memoryNodeOomHover?.Dispose(); + _memoryNodeOomUtilHover?.Dispose(); + _memoryNodeOomMemoryHover?.Dispose(); + if (_filterPopupContent != null) { _filterPopupContent.FilterApplied -= FilterPopup_FilterApplied; _filterPopupContent.FilterCleared -= FilterPopup_FilterCleared; } - /* Clear large data collections to free memory */ - _systemHealthUnfilteredData = null; - _severeErrorsUnfilteredData = null; - _ioIssuesUnfilteredData = null; - _memoryBrokerUnfilteredData = null; - _memoryNodeOOMUnfilteredData = null; - Helpers.ThemeManager.ThemeChanged -= OnThemeChanged; } diff --git a/Dashboard/ServerTab.xaml.cs b/Dashboard/ServerTab.xaml.cs index 335954b..3c6974f 100644 --- a/Dashboard/ServerTab.xaml.cs +++ b/Dashboard/ServerTab.xaml.cs @@ -275,6 +275,7 @@ public void DisposeChartHelpers() MemoryTab.DisposeChartHelpers(); ResourceMetricsContent.DisposeChartHelpers(); PerformanceTab.DisposeChartHelpers(); + SystemEventsContent.DisposeChartHelpers(); } public void RefreshAutoRefreshSettings()