From af77af4aab5cdc5c3f9b74575f99ccef859440e9 Mon Sep 17 00:00:00 2001 From: "zhengxuan.zhang" Date: Wed, 12 Aug 2026 09:56:53 +0800 Subject: [PATCH] =?UTF-8?q?CNC=20=E6=A0=87=E8=AE=B0=E5=8F=B3=E9=94=AE?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=20Mark=20=E8=8F=9C=E5=8D=95=20=E4=BA=BA?= =?UTF-8?q?=E5=B7=A5=E6=A0=87=E8=AE=B0=E5=8F=B3=E9=94=AE=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=20Mark=20=E8=8F=9C=E5=8D=95=20=E7=BB=9F=E4=B8=80=E6=8F=90?= =?UTF-8?q?=E4=BE=9B=E7=BC=96=E8=BE=91=E5=B1=9E=E6=80=A7=E3=80=81=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E5=85=B3=E8=81=94=E5=9B=BE=E5=83=8F=E3=80=81=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E3=80=81=E6=81=A2=E5=A4=8D=E4=BD=8D=E7=BD=AE=E7=AD=89?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=20=E7=BB=A7=E7=BB=AD=E5=B1=8F=E8=94=BD?= =?UTF-8?q?=E5=BA=95=E5=B1=82=20RoiCanvas=20=E7=9A=84=E7=BC=A9=E6=94=BE/?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E8=8F=9C=E5=8D=95=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E4=B8=A4=E4=B8=AA=E8=8F=9C=E5=8D=95=E5=90=8C=E6=97=B6=E5=87=BA?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controls/PolygonRoiCanvas.xaml.cs | 24 ++++++-- .../Main/Overlays/InspectionMarkOverlay.cs | 59 +++++++++++-------- .../ViewportPanelView.InspectionOverlay.cs | 12 ++++ .../Views/Main/ViewportPanelView.xaml.cs | 17 +++++- 4 files changed, 80 insertions(+), 32 deletions(-) diff --git a/XP.ImageProcessing.RoiControl/Controls/PolygonRoiCanvas.xaml.cs b/XP.ImageProcessing.RoiControl/Controls/PolygonRoiCanvas.xaml.cs index 728413ea..bd951410 100644 --- a/XP.ImageProcessing.RoiControl/Controls/PolygonRoiCanvas.xaml.cs +++ b/XP.ImageProcessing.RoiControl/Controls/PolygonRoiCanvas.xaml.cs @@ -758,7 +758,13 @@ namespace XP.ImageProcessing.RoiControl.Controls ClearOverlayLayer(layer); if (overlayCanvas == null) return; - overlayCanvas.IsHitTestVisible = false; + // 结果/辅助叠加层默认不参与命中测试;检查标记层例外,保留其自身的命中配置, + // 以便标记图标可以处理右键,而空白区域仍由 Background=null 穿透到底层画布。 + if (layer != ViewportOverlayLayer.InspectionMarks) + overlayCanvas.IsHitTestVisible = false; + System.Diagnostics.Debug.WriteLine( + $"[ViewportMouse] AttachOverlay layer={layer}, hitTest={overlayCanvas.IsHitTestVisible}, " + + $"background={overlayCanvas.Background ?? System.Windows.Media.Brushes.Transparent}"); overlayCanvas.SetBinding(Canvas.WidthProperty, new System.Windows.Data.Binding("CanvasWidth") { Source = this }); overlayCanvas.SetBinding(Canvas.HeightProperty, new System.Windows.Data.Binding("CanvasHeight") { Source = this }); if (layer == ViewportOverlayLayer.InspectionMarks) @@ -2744,9 +2750,12 @@ namespace XP.ImageProcessing.RoiControl.Controls isDragging = false; } - private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e) - { - // BGA 模式下右键切换气泡/焊球 + private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + System.Diagnostics.Debug.WriteLine( + $"[ViewportMouse] RoiCanvas.MouseRightButtonDown source={e.OriginalSource?.GetType().Name}, " + + $"handled={e.Handled}, point={e.GetPosition(mainCanvas)}"); + // BGA 模式下右键切换气泡/焊球 if (CurrentMeasureMode == Models.MeasureMode.BgaVoid && _bgaCurrent != null) { HandleBgaRightClick(); @@ -2758,8 +2767,11 @@ namespace XP.ImageProcessing.RoiControl.Controls // 不设 e.Handled,让 ContextMenu 正常弹出 } - private void Canvas_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e) - { + private void Canvas_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e) + { + System.Diagnostics.Debug.WriteLine( + $"[ViewportMouse] RoiCanvas.PreviewMouseRightButtonUp source={e.OriginalSource?.GetType().Name}, " + + $"handled={e.Handled}, suppress={SuppressContextMenu}"); // BGA 模式下阻止 ContextMenu 弹出 if (CurrentMeasureMode == Models.MeasureMode.BgaVoid && _bgaCurrent != null) { diff --git a/XplorePlane/Views/Main/Overlays/InspectionMarkOverlay.cs b/XplorePlane/Views/Main/Overlays/InspectionMarkOverlay.cs index aa2d7c77..97aa83f9 100644 --- a/XplorePlane/Views/Main/Overlays/InspectionMarkOverlay.cs +++ b/XplorePlane/Views/Main/Overlays/InspectionMarkOverlay.cs @@ -27,11 +27,12 @@ namespace XplorePlane.Views.Main.Overlays private INotifyCollectionChanged? _notifyingMarks; - public InspectionMarkOverlay() - { - // 默认状态:无工具激活 → 不拦截鼠标事件,避免阻挡下层的测量拖拽/图像平移等 - IsHitTestVisible = false; - Background = null; + public InspectionMarkOverlay() + { + // 背景保持 null,不拦截空白区域的鼠标事件;标记图标自身仍需可命中, + // 这样实时预览区右键标记时可以先由标记层处理,避免落到底层 RoiCanvas 菜单。 + IsHitTestVisible = true; + Background = null; MouseLeftButtonDown += OnMouseLeftButtonDown; MouseRightButtonDown += OnMouseRightButtonDown; SizeChanged += (_, _) => RenderMarks(); @@ -84,16 +85,18 @@ namespace XplorePlane.Views.Main.Overlays overlay.RenderMarks(); } - private static void OnVisualStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var overlay = (InspectionMarkOverlay)d; - // 仅当标记工具激活时才允许命中测试,否则 Background=Transparent 会拦截 - // 下层所有鼠标事件(测量拖拽、图像平移、ROI 编辑等)。 - bool isInteractive = !overlay.IsReadOnly && overlay.ActiveTool != InspectionMapActiveTool.None; - overlay.IsHitTestVisible = isInteractive; - overlay.Background = isInteractive ? Brushes.Transparent : null; - overlay.RenderMarks(); - } + private static void OnVisualStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var overlay = (InspectionMarkOverlay)d; + // 背景为 null 时,空白区域继续穿透到下层;但标记图标必须保持命中, + // 否则实时预览区的右键会直接触发 RoiCanvas 的缩放/保存菜单。 + bool canHitMarks = !overlay.IsReadOnly && overlay.ShowMarks; + overlay.IsHitTestVisible = canHitMarks; + overlay.Background = canHitMarks && overlay.ActiveTool != InspectionMapActiveTool.None + ? Brushes.Transparent + : null; + overlay.RenderMarks(); + } private void OnMarksCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) => RenderMarks(); @@ -121,11 +124,14 @@ namespace XplorePlane.Views.Main.Overlays icon.ToolTip = $"{mark.Type} {mark.Status}\n{mark.CreatedAtUtc:yyyy-MM-dd HH:mm:ss}\n{mark.Comment}"; icon.MouseEnter += (_, _) => icon.Opacity = 0.75; icon.MouseLeave += (_, _) => icon.Opacity = 1.0; - icon.MouseRightButtonDown += (s, e) => - { - e.Handled = true; - MarkContextMenuRequested?.Invoke(this, mark); - }; + icon.MouseRightButtonDown += (s, e) => + { + System.Diagnostics.Debug.WriteLine( + $"[ViewportMouse] Mark.MouseRightButtonDown type={mark.Type}, cncNode={mark.CncNodeId}, " + + $"source={e.OriginalSource?.GetType().Name}, handled={e.Handled}"); + e.Handled = true; + MarkContextMenuRequested?.Invoke(this, mark); + }; SetLeft(icon, position.Value.X - icon.Width / 2); SetTop(icon, position.Value.Y - icon.Height / 2); @@ -209,11 +215,14 @@ namespace XplorePlane.Views.Main.Overlays e.Handled = true; } - private void OnMouseRightButtonDown(object sender, MouseButtonEventArgs e) - { - var hit = Children.OfType() - .FirstOrDefault(c => IsNear(e.GetPosition(this), c)); - if (hit?.Tag is InspectionMarkRecord mark) + private void OnMouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + var hit = Children.OfType() + .FirstOrDefault(c => IsNear(e.GetPosition(this), c)); + System.Diagnostics.Debug.WriteLine( + $"[ViewportMouse] MarkOverlay.MouseRightButtonDown hit={hit?.Tag is InspectionMarkRecord}, " + + $"source={e.OriginalSource?.GetType().Name}, handled={e.Handled}, point={e.GetPosition(this)}"); + if (hit?.Tag is InspectionMarkRecord mark) { MarkContextMenuRequested?.Invoke(this, mark); e.Handled = true; diff --git a/XplorePlane/Views/Main/ViewportPanelView.InspectionOverlay.cs b/XplorePlane/Views/Main/ViewportPanelView.InspectionOverlay.cs index 81cf79f9..ede386d7 100644 --- a/XplorePlane/Views/Main/ViewportPanelView.InspectionOverlay.cs +++ b/XplorePlane/Views/Main/ViewportPanelView.InspectionOverlay.cs @@ -328,6 +328,13 @@ namespace XplorePlane.Views.Main }; _inspectionMarkOverlay.MarkContextMenuRequested += (_, mark) => { + _log.Information("[ViewportMouse] MarkContextMenuRequested markId={MarkId}, type={Type}, " + + "status={Status}, cncNodeId={CncNodeId}, cncRunId={CncRunId}", + mark.MarkId, + mark.Type, + mark.Status, + mark.CncNodeId, + mark.CncRunId); vm.SelectedMark = mark; ShowInspectionMarkContextMenu(mark, vm); }; @@ -345,6 +352,11 @@ namespace XplorePlane.Views.Main private void ShowInspectionMarkContextMenu(InspectionMarkRecord mark, ViewModels.Inspection.Documentation.InspectionMapViewModel vm) { + // CNC 标记和人工标记统一使用 Mark 菜单;仅屏蔽底层 RoiCanvas 的默认菜单。 + _log.Information("[ViewportMouse] Show Mark context menu markId={MarkId}, cncNodeId={CncNodeId}", + mark.MarkId, + mark.CncNodeId); + var menu = new System.Windows.Controls.ContextMenu(); var properties = new System.Windows.Controls.MenuItem { Header = "编辑属性" }; properties.Click += (_, _) => diff --git a/XplorePlane/Views/Main/ViewportPanelView.xaml.cs b/XplorePlane/Views/Main/ViewportPanelView.xaml.cs index e4f877bd..ca1b5519 100644 --- a/XplorePlane/Views/Main/ViewportPanelView.xaml.cs +++ b/XplorePlane/Views/Main/ViewportPanelView.xaml.cs @@ -233,10 +233,25 @@ namespace XplorePlane.Views.Main RoiCanvas.ContextMenu = menu; RoiCanvas.ContextMenuOpening += (s, e) => { - if (RoiCanvas.SuppressContextMenu) + var originalSource = e.OriginalSource as System.Windows.DependencyObject; + bool isInspectionMarkSource = _inspectionMarkOverlay != null && + originalSource is System.Windows.Media.Visual visual && + (ReferenceEquals(visual, _inspectionMarkOverlay) || + visual.IsDescendantOf(_inspectionMarkOverlay)); + _log.Information("[ViewportMouse] RoiCanvas.ContextMenuOpening source={Source}, original={OriginalSource}, " + + "handled={Handled}, suppress={SuppressContextMenu}, markSource={MarkSource}, markHitTest={MarkHitTest}", + s?.GetType().Name, + e.OriginalSource?.GetType().Name, + e.Handled, + RoiCanvas.SuppressContextMenu, + isInspectionMarkSource, + _inspectionMarkOverlay?.IsHitTestVisible); + if (RoiCanvas.SuppressContextMenu || isInspectionMarkSource) { RoiCanvas.SuppressContextMenu = false; e.Handled = true; + _log.Information("[ViewportMouse] RoiCanvas context menu suppressed, reason={Reason}", + isInspectionMarkSource ? "inspection-mark" : "external-request"); } };