Skip to content

Commit 5b833ef

Browse files
Apply erikdarlingdata#916 popup-wedge fix to CorrelatedCrosshairManager (Dashboard + Lite)
Sweeping for repeats of the WPF Popup wedge bug found CorrelatedCrosshairManager has the same shape as ChartHoverHelper — multiple charts feed a single tooltip popup, IsOpen = true is set on every mouse move with no re-anchor toggle, and no Loaded/Unloaded/IsVisibleChanged subscriptions on the chart lanes. CorrelatedTimelineLanesControl is hosted inside Resource Metrics → Server Trends (a TabItem inside a TabItem), so the same wedge applies: WPF unloads the parent on tab switch without firing MouseLeave, leaving _tooltip.IsOpen stuck at true with a stale anchor. Both Dashboard and Lite copies updated in lockstep per the file's own SYNC WARNING: - Subscribe to chart Loaded / Unloaded / IsVisibleChanged in AddLane and force _tooltip.IsOpen = false on each event. - In OnMouseMove, toggle IsOpen off then on so WPF re-evaluates placement even when the popup believes it is already open. - Unhook the new event handlers in Dispose. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b9361f3 commit 5b833ef

2 files changed

Lines changed: 54 additions & 0 deletions

File tree

Dashboard/Helpers/CorrelatedCrosshairManager.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,28 @@ public void AddLane(ScottPlot.WPF.WpfPlot chart, string label, string unit)
7474
chart.MouseMove += (s, e) => OnMouseMove(lane, e);
7575
chart.MouseLeave += (s, e) => OnMouseLeave();
7676

77+
/* Tab switching can leave the popup wedged: WPF unloads the parent TabItem
78+
without firing MouseLeave, so IsOpen stays true with a stale anchor.
79+
When a chart becomes visible again, OnMouseMove sets IsOpen = true but
80+
it is already true, so the popup never re-anchors. Force-close on every
81+
visibility/load transition for any lane so the next mouse move re-opens
82+
cleanly. */
83+
chart.IsVisibleChanged += OnLaneVisibilityChanged;
84+
chart.Unloaded += OnLaneUnloaded;
85+
chart.Loaded += OnLaneLoaded;
86+
7787
_lanes.Add(lane);
7888
}
7989

90+
private void OnLaneVisibilityChanged(object sender, DependencyPropertyChangedEventArgs e) =>
91+
_tooltip.IsOpen = false;
92+
93+
private void OnLaneUnloaded(object sender, RoutedEventArgs e) =>
94+
_tooltip.IsOpen = false;
95+
96+
private void OnLaneLoaded(object sender, RoutedEventArgs e) =>
97+
_tooltip.IsOpen = false;
98+
8099
/// <summary>
81100
/// Sets the expected baseline range for a lane (upper/lower bounds).
82101
/// Values outside this range get ▲/▼ indicators in the tooltip.
@@ -287,6 +306,11 @@ private void OnMouseMove(LaneInfo sourceLane, MouseEventArgs e)
287306
_tooltip.PlacementTarget = sourceLane.Chart;
288307
_tooltip.HorizontalOffset = pos.X + 15;
289308
_tooltip.VerticalOffset = pos.Y + 15;
309+
/* Toggle if already open so WPF re-evaluates the placement target.
310+
Without this, a popup that was IsOpen = true when its TabItem was
311+
unloaded stays "open" with a stale anchor and never appears on
312+
return — the assignment below is a no-op. */
313+
if (_tooltip.IsOpen) _tooltip.IsOpen = false;
290314
_tooltip.IsOpen = true;
291315
}
292316

@@ -373,6 +397,9 @@ public void Dispose()
373397
_tooltip.IsOpen = false;
374398
foreach (var lane in _lanes)
375399
{
400+
lane.Chart.IsVisibleChanged -= OnLaneVisibilityChanged;
401+
lane.Chart.Unloaded -= OnLaneUnloaded;
402+
lane.Chart.Loaded -= OnLaneLoaded;
376403
lane.Series.Clear();
377404
lane.VLine = null;
378405
}

Lite/Helpers/CorrelatedCrosshairManager.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,28 @@ public void AddLane(ScottPlot.WPF.WpfPlot chart, string label, string unit)
7474
chart.MouseMove += (s, e) => OnMouseMove(lane, e);
7575
chart.MouseLeave += (s, e) => OnMouseLeave();
7676

77+
/* Tab switching can leave the popup wedged: WPF unloads the parent TabItem
78+
without firing MouseLeave, so IsOpen stays true with a stale anchor.
79+
When a chart becomes visible again, OnMouseMove sets IsOpen = true but
80+
it is already true, so the popup never re-anchors. Force-close on every
81+
visibility/load transition for any lane so the next mouse move re-opens
82+
cleanly. */
83+
chart.IsVisibleChanged += OnLaneVisibilityChanged;
84+
chart.Unloaded += OnLaneUnloaded;
85+
chart.Loaded += OnLaneLoaded;
86+
7787
_lanes.Add(lane);
7888
}
7989

90+
private void OnLaneVisibilityChanged(object sender, DependencyPropertyChangedEventArgs e) =>
91+
_tooltip.IsOpen = false;
92+
93+
private void OnLaneUnloaded(object sender, RoutedEventArgs e) =>
94+
_tooltip.IsOpen = false;
95+
96+
private void OnLaneLoaded(object sender, RoutedEventArgs e) =>
97+
_tooltip.IsOpen = false;
98+
8099
/// <summary>
81100
/// Sets the expected baseline range for a lane (upper/lower bounds).
82101
/// Values outside this range get ▲/▼ indicators in the tooltip.
@@ -287,6 +306,11 @@ private void OnMouseMove(LaneInfo sourceLane, MouseEventArgs e)
287306
_tooltip.PlacementTarget = sourceLane.Chart;
288307
_tooltip.HorizontalOffset = pos.X + 15;
289308
_tooltip.VerticalOffset = pos.Y + 15;
309+
/* Toggle if already open so WPF re-evaluates the placement target.
310+
Without this, a popup that was IsOpen = true when its TabItem was
311+
unloaded stays "open" with a stale anchor and never appears on
312+
return — the assignment below is a no-op. */
313+
if (_tooltip.IsOpen) _tooltip.IsOpen = false;
290314
_tooltip.IsOpen = true;
291315
}
292316

@@ -373,6 +397,9 @@ public void Dispose()
373397
_tooltip.IsOpen = false;
374398
foreach (var lane in _lanes)
375399
{
400+
lane.Chart.IsVisibleChanged -= OnLaneVisibilityChanged;
401+
lane.Chart.Unloaded -= OnLaneUnloaded;
402+
lane.Chart.Loaded -= OnLaneLoaded;
376403
lane.Series.Clear();
377404
lane.VLine = null;
378405
}

0 commit comments

Comments
 (0)