From 640814fdf67dc83a97a387522b939da45ec77709 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 9 Apr 2026 10:41:18 -0400 Subject: [PATCH] fix: marshal timer callbacks to UI thread to prevent GDI+ threading exception System.Timers.Timer fires on thread pool threads, causing GraphicsPath objects to be accessed concurrently by hover detection and OnPaint, triggering "Object is currently in use elsewhere" in DrawParts. Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenNest/Controls/PlateView.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/OpenNest/Controls/PlateView.cs b/OpenNest/Controls/PlateView.cs index 1c11ff8..387f781 100644 --- a/OpenNest/Controls/PlateView.cs +++ b/OpenNest/Controls/PlateView.cs @@ -621,30 +621,30 @@ namespace OpenNest.Controls private void redrawTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { - Invalidate(); + if (IsDisposed || !IsHandleCreated) return; + BeginInvoke(new System.Action(Invalidate)); } private void hoverTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + if (IsDisposed || !IsHandleCreated) return; + BeginInvoke(new System.Action(HoverCheck)); + } + + private void HoverCheck() { var graphPt = PointControlToGraph(hoverPoint); LayoutPart hitPart = null; - try + + for (var i = parts.Count - 1; i >= 0; --i) { - for (var i = parts.Count - 1; i >= 0; --i) + if (parts[i].Path.GetBounds().Contains(graphPt) && + parts[i].Path.IsVisible(graphPt)) { - if (parts[i].Path.GetBounds().Contains(graphPt) && - parts[i].Path.IsVisible(graphPt)) - { - hitPart = parts[i]; - break; - } + hitPart = parts[i]; + break; } } - catch (InvalidOperationException) - { - // GraphicsPath in use by paint thread — skip this hover tick - return; - } hoveredPart = hitPart; showTooltip = hitPart != null;