using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using HalconDotNet; namespace Plugins.Halcon.Views { /// HALCON 画面框的显示结果类型(OK/NG/无)。 public enum HalconResultType { Null, OK, NG } /// 双击放大回调(与 VppWindow.MouseDoubleDelegate 对等)。 public delegate void HalconMouseDoubleDelegate(HalconWindow window, int number); /// /// 单个 HALCON 画面框(镜像 VppWindow/VmWindow 的角色):标题、状态圆点、图像区。 /// 图像区用 HALCON 原生 WPF 控件 (内置缩放/平移): /// - RefreshImage(image, overlay):经典模式——单底图 + 单叠加(主页画面框用); /// - ShowLayers(layers):图层模式——底图 + 多彩色叠加(编辑器大显示窗按图层筛选渲染); /// - 鼠标移动实时读取 行/列/灰度;支持拖入图片文件、右键保存图片、适应窗体; /// - 右键菜单可交互绘制 矩形/旋转矩形/圆形 ROI(HDrawingObject),结果存 。 /// 所有 HALCON 调用集中在 或本类;显示操作统一切到 UI 线程。 /// 所有权规则:外部传入的 HObject 只引用不释放;由本窗体从 GenImage 转换出的副本(owned)在本窗体负责释放。 /// public partial class HalconWindow : UserControl { /// 双击放大/还原回调(由 UCDispHalcon 挂接)。 public HalconMouseDoubleDelegate MouseDouble { get; set; } /// 画面框编号(1 起)。 public int Number { get; } // HALCON 显示面与右键菜单改为 code-behind 防御式创建(见 InitHalconSurface): // 机器缺 halcon.dll 等原生运行库时,XAML 声明式创建 + WPF 渲染期会抛出 // DllNotFoundException(首次可被吞掉,二次渲染异常直接致命闪退)。 private HSmartWindowControlWPF MyHwindow; private ContextMenu MyMenu; // —— 经典模式状态(单图 + 单叠加) —— private HObject _image; private bool _imageOwned; private HObject _overlay; private bool _overlayOwned; private int _imgW, _imgH; private bool _hasImage; // —— 图层模式状态(底图 + 多叠加) —— private readonly List _layerOwnedCopies = new List(); // 本窗体自有的转换副本(释放责任在本窗体) private HObject _layerBaseImage; private bool _layerBaseOwned; private readonly List> _layerOverlays = new List>(); private int _layerImgW, _layerImgH; private bool _layerMode; /// 图层缺省配色(按叠加顺序取色,可用 LayerItem.Color 覆盖)。 private static readonly string[] LayerPalette = { "red", "green", "blue", "yellow", "cyan", "magenta", "orange" }; // ROI 交互绘制 private HDrawingObject _drawing; private bool _drawingBusy; /// 最近一次交互绘制得到的 ROI(供节点读取;未绘制时为 null)。 public Models.HalconRoi LastRoi { get; private set; } public HalconWindow() : this(1) { } public HalconWindow(int number) { Number = number; InitializeComponent(); CaptionText.Text = $"画面 {number}"; InitHalconSurface(); SizeChanged += (s, e) => { if (e.NewSize.Width > 1) { FitCurrent(); } }; } /// /// 防御式创建 HALCON 显示面:先做一次原生调用探测(缺 halcon.dll 等运行库时立即失败), /// 可用才创建 HSmartWindowControlWPF 并挂事件/右键菜单;不可用则显示错误占位。 /// 保证 Halcon 流程编辑器等宿主在无原生运行库的机器上照常打开,不闪退。 /// private void InitHalconSurface() { if (!TryProbeHalconNative(out var probeError)) { NativeErrorText.Text = "HALCON 原生运行库不可用\n\n" + $"原因:{probeError}\n\n" + "请检查 Runtime\\dll\\x64 下的 halcon.dll 及配套运行库是否完整," + "或安装 HALCON 运行时。流程画布与属性编辑不受影响。"; NativeErrorText.Visibility = Visibility.Visible; return; } try { var hw = new HSmartWindowControlWPF { HDoubleClickToFitContent = false, AllowDrop = true, Cursor = Cursors.Cross }; hw.HMouseMove += MyHwindow_HMouseMove; hw.MouseLeave += MyHwindow_MouseLeave; hw.MouseRightButtonDown += MyHwindow_MouseRightButtonDown; hw.Drop += MyHwindow_Drop; hw.DragEnter += MyHwindow_DragEnter; MyMenu = BuildContextMenu(); hw.ContextMenu = MyMenu; MyHwindow = hw; HwindowHost.Content = hw; } catch (Exception ex) { MyHwindow = null; NativeErrorText.Text = $"HALCON 显示控件创建失败:{ex.Message}"; NativeErrorText.Visibility = Visibility.Visible; } } /// 原生运行库探测:一次无副作用的 HALCON 调用,失败=缺原生 DLL。 private static bool TryProbeHalconNative(out string error) { try { HOperatorSet.GetSystem("version", out HTuple version); error = null; return true; } catch (Exception ex) { error = ex.Message; return false; } } /// 构建显示区右键菜单(缩放/适应/保存/ROI 绘制)。 private ContextMenu BuildContextMenu() { var menu = new ContextMenu(); void AddHandler(string header, RoutedEventHandler onClick) { var mi = new MenuItem { Header = header }; mi.Click += onClick; menu.Items.Add(mi); } AddHandler("放大", MenuZoomIn_Click); AddHandler("缩小", MenuZoomOut_Click); AddHandler("适应窗体", MenuFit_Click); AddHandler("保存图片", MenuSave_Click); menu.Items.Add(new Separator()); AddHandler("绘制矩形ROI", MenuRoiRect1_Click); AddHandler("绘制旋转矩形ROI", MenuRoiRect2_Click); AddHandler("绘制圆形ROI", MenuRoiCircle_Click); AddHandler("清除ROI", MenuRoiClear_Click); return menu; } /// HALCON 窗口句柄(控件初始化后才有效;用前判空)。 private HWindow Hwn { get { try { return MyHwindow?.HalconWindow; } catch { return null; } } } #region 引擎/显示插件调用的公共接口(与 VmWindow 对齐) public void SetCaption(string caption) { if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => SetCaption(caption)); return; } CaptionText.Text = string.IsNullOrWhiteSpace(caption) ? $"画面 {Number}" : caption; } public void SetStatusDotVisible(bool visible) { if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => SetStatusDotVisible(visible)); return; } StatusDot.Visibility = visible ? Visibility.Visible : Visibility.Collapsed; } /// 按 OK/NG 刷状态圆点颜色(并置为可见)。 public void SetLable(HalconResultType type) { if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => SetLable(type)); return; } StatusDot.Visibility = Visibility.Visible; switch (type) { case HalconResultType.OK: StatusDot.Fill = new SolidColorBrush(Color.FromRgb(0x4C, 0xAF, 0x50)); break; case HalconResultType.NG: StatusDot.Fill = new SolidColorBrush(Color.FromRgb(0xF4, 0x43, 0x36)); break; default: StatusDot.Fill = new SolidColorBrush(Color.FromRgb(0x88, 0x88, 0x88)); break; } } /// /// 【经典模式】刷新显示:image=图像(HObject/GenImage/Bitmap),overlay=可选叠加对象(区域/XLD,红色边界绘制)。 /// 从流程传入的 HObject 只引用不释放;从 GenImage 转换的副本由本窗体负责释放。 /// public void RefreshImage(object image, object overlay = null) { if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => RefreshImage(image, overlay)); return; } // 经典调用接管显示:退出图层模式 ExitLayerMode(); var hw = Hwn; if (hw == null) return; // image 为 null:仅更新叠加层(保留当前底图),供只产出区域的节点使用 if (image == null) { if (overlay == null) return; try { bool ovOwned = !(overlay is HObject); var hover = HalconRuntime.ToHObject(overlay); if (_overlayOwned) HalconRuntime.Dispose(_overlay); _overlay = hover; _overlayOwned = ovOwned; Redraw(); } catch { } return; } try { bool imgOwned = !(image is HObject); var himg = HalconRuntime.ToHObject(image); HObject hover = null; bool overOwned = false; if (overlay != null) { overOwned = !(overlay is HObject); try { hover = HalconRuntime.ToHObject(overlay); } catch { hover = null; } } HOperatorSet.GetImageSize(himg, out HTuple wT, out HTuple hT); int w = wT.I, h = hT.I; // 释放上一帧「本窗体自有」的副本(流程对象不动) if (_imageOwned) HalconRuntime.Dispose(_image); if (_overlayOwned) HalconRuntime.Dispose(_overlay); _image = himg; _imageOwned = imgOwned; _overlay = hover; _overlayOwned = overOwned; _hasImage = true; bool needFit = w != _imgW || h != _imgH; _imgW = w; _imgH = h; if (needFit) FitPart(); Redraw(); } catch { /* 显示失败不影响流程 */ } } /// 清空画面显示。 public void ClearImage() { if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(new Action(ClearImage)); return; } try { ExitLayerMode(); if (_imageOwned) HalconRuntime.Dispose(_image); if (_overlayOwned) HalconRuntime.Dispose(_overlay); _image = null; _overlay = null; _hasImage = false; _imgW = _imgH = 0; Hwn?.ClearWindow(); } catch { } } #endregion #region 图层显示(编辑器大显示窗 / 图层筛选) /// /// 【图层模式】按图层集渲染:首个 Image 图层作底图,其余按 LayerItem.Color(缺省调色板)叠加。 /// 图层对象的「注册表自有」部分只引用不释放;本窗体从 GenImage 转换出的副本在下次刷新/清理时释放。 /// public void ShowLayers(IReadOnlyList layers) { if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => ShowLayers(layers)); return; } ExitLayerMode(); var hw = Hwn; if (hw == null || layers == null || layers.Count == 0) return; try { foreach (var layer in layers) { if (layer == null || layer.Content == null) continue; if (layer.Kind == TeamAAS.Vision.LayerKind.Image) { bool owned = !(layer.Content is HObject); var himg = HalconRuntime.ToHObject(layer.Content); if (owned) _layerOwnedCopies.Add(himg); if (_layerBaseOwned && _layerBaseImage != null) HalconRuntime.Dispose(_layerBaseImage); _layerBaseImage = himg; _layerBaseOwned = owned; } else { bool owned = !(layer.Content is HObject); var hobj = HalconRuntime.ToHObject(layer.Content); if (owned) _layerOwnedCopies.Add(hobj); var color = string.IsNullOrWhiteSpace(layer.Color) ? LayerPalette[_layerOverlays.Count % LayerPalette.Length] : layer.Color; _layerOverlays.Add(new KeyValuePair(hobj, color)); } } if (_layerBaseImage != null && _layerBaseImage.IsInitialized()) { HOperatorSet.GetImageSize(_layerBaseImage, out HTuple wT, out HTuple hT); int w = wT.I, h = hT.I; bool needFit = w != _layerImgW || h != _layerImgH; _layerImgW = w; _layerImgH = h; if (needFit) FitPart(); } _layerMode = true; Redraw(); } catch { ExitLayerMode(); } } /// 退出图层模式:释放本窗体自有的图层副本,恢复经典状态。 private void ExitLayerMode() { _layerMode = false; foreach (var copy in _layerOwnedCopies) HalconRuntime.Dispose(copy); _layerOwnedCopies.Clear(); if (_layerBaseOwned) HalconRuntime.Dispose(_layerBaseImage); _layerBaseImage = null; _layerBaseOwned = false; _layerOverlays.Clear(); _layerImgW = _layerImgH = 0; } /// 图层模式重绘:底图 + 多彩叠加。 private void RedrawLayers() { var hw = Hwn; if (hw == null) return; try { HOperatorSet.SetSystem("flush_graphic", "false"); hw.ClearWindow(); bool hasBase = _layerBaseImage != null && _layerBaseImage.IsInitialized(); HObject dispBase = hasBase ? ToDisplayImage(_layerBaseImage) : null; // 位深归一化显示 if (hasBase) { hw.SetColor("default"); hw.SetDraw("fill"); hw.DispObj(dispBase); } foreach (var kv in _layerOverlays) { if (kv.Key == null || !kv.Key.IsInitialized()) continue; hw.SetColor(string.IsNullOrWhiteSpace(kv.Value) ? "red" : kv.Value); hw.SetDraw("margin"); hw.SetLineWidth(2.0); hw.DispObj(kv.Key); } HOperatorSet.SetSystem("flush_graphic", "true"); if (dispBase != null && !ReferenceEquals(dispBase, _layerBaseImage)) dispBase.Dispose(); } catch { } } #endregion #region 显示绘制 /// 按当前模式重绘(保持当前缩放/平移的 part)。 private void Redraw() { if (_layerMode) { RedrawLayers(); return; } var hw = Hwn; if (hw == null) return; bool hasImg = _image != null && _image.IsInitialized(); bool hasOv = _overlay != null && _overlay.IsInitialized(); if (!hasImg && !hasOv) return; try { HOperatorSet.SetSystem("flush_graphic", "false"); hw.ClearWindow(); HObject dispImg = hasImg ? ToDisplayImage(_image) : null; // 位深归一化显示 if (hasImg) { hw.SetDraw("fill"); hw.DispObj(dispImg); } if (hasOv) { hw.SetColor("red"); hw.SetDraw("margin"); hw.SetLineWidth(2.0); hw.DispObj(_overlay); } HOperatorSet.SetSystem("flush_graphic", "true"); if (dispImg != null && !ReferenceEquals(dispImg, _image)) dispImg.Dispose(); } catch { } } /// 按当前模式的图像尺寸做等比适配。 private void FitCurrent() { if (_layerMode) { FitImage(_layerImgW, _layerImgH); return; } FitImage(_imgW, _imgH); } /// 按图像尺寸与控件尺寸做等比适配(set_part),不拉伸变形。 private void FitPart() { FitImage(_layerMode ? _layerImgW : _imgW, _layerMode ? _layerImgH : _imgH); } // 首次显示时控件可能尚未完成布局(ActualWidth=0),此时 Fit 只能挂起, // 等布局完成补做 —— 否则显示区域(part)停在默认值,大图只露出某个角落,肉眼上"全黑" private bool _pendingFit; private void FitImage(int imgW, int imgH) { var hw = Hwn; if (hw == null || imgW <= 0 || imgH <= 0) return; double W = MyHwindow?.ActualWidth ?? 0, H = MyHwindow?.ActualHeight ?? 0; if (W <= 1 || H <= 1) { if (!_pendingFit) { _pendingFit = true; MyHwindow.LayoutUpdated += OnLayoutPendingFit; } return; } _pendingFit = false; try { double winAspect = W / H; double imgAspect = (double)imgW / imgH; double partW, partH; if (imgAspect > winAspect) { partW = imgW; partH = imgW / winAspect; } else { partH = imgH; partW = imgH * winAspect; } double r1 = -(partH - imgH) / 2.0; double c1 = -(partW - imgW) / 2.0; hw.SetPart(r1, c1, r1 + partH - 1, c1 + partW - 1); } catch { } } /// 布局完成后补做挂起的适配(回到正确显示区域)。 private void OnLayoutPendingFit(object sender, EventArgs e) { if (!_pendingFit) { MyHwindow.LayoutUpdated -= OnLayoutPendingFit; return; } int imgW = _layerMode ? _layerImgW : _imgW; int imgH = _layerMode ? _layerImgH : _imgH; if (imgW <= 0 || MyHwindow == null || MyHwindow.ActualWidth <= 1) return; _pendingFit = false; MyHwindow.LayoutUpdated -= OnLayoutPendingFit; FitCurrent(); Redraw(); } /// /// 显示用规范化:uint2/int2/int4/real 图像按实际灰度范围拉伸到 0-255 再转 byte。 /// 背景:HALCON 显示 uint2 时灰度 100 只相当于位深的 0.15%,肉眼上"整幅是黑的", /// 而灰度读数(直接取像素值)完全正常 —— 16 位相机图像黑屏即此原因。byte 原样返回。 /// private static HObject ToDisplayImage(HObject img) { try { HOperatorSet.GetImageType(img, out HTuple t); var type = t.S; if (type == "byte" || type == "complex" || type == "vector_field") return img; HOperatorSet.MinMaxGray(img, img, 0, out HTuple mn, out HTuple mx, out HTuple _); double min = mn.D, range = mx.D - min; if (range < 1) range = 1; double scale = 255.0 / range; HOperatorSet.ScaleImage(img, out HObject scaled, scale, -min * scale); HOperatorSet.ConvertImageType(scaled, out HObject byteImg, "byte"); scaled.Dispose(); return byteImg; } catch { return img; } } #endregion #region 鼠标读取 / 拖放 / 双击 private void MyHwindow_HMouseMove(object sender, HSmartWindowControlWPF.HMouseEventArgsWPF e) { try { int row = (int)e.Row, col = (int)e.Column; string txt = $"行:{row} 列:{col}"; var img = _layerMode ? _layerBaseImage : _image; int imgH = _layerMode ? _layerImgH : _imgH; int imgW = _layerMode ? _layerImgW : _imgW; if (img != null && img.IsInitialized() && row >= 0 && col >= 0 && row < imgH && col < imgW) { try { HOperatorSet.GetGrayval(img, row, col, out HTuple gray); txt += $" 灰度:{gray.D:F0}"; } catch { } } CoordText.Text = txt; CoordText.Visibility = Visibility.Visible; } catch { } } private void MyHwindow_MouseLeave(object sender, MouseEventArgs e) => CoordText.Visibility = Visibility.Collapsed; private void MyHwindow_DragEnter(object sender, DragEventArgs e) => e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None; private void MyHwindow_Drop(object sender, DragEventArgs e) { try { if (e.Data.GetData(DataFormats.FileDrop) is string[] files && files.Length > 0) { HOperatorSet.ReadImage(out HObject img, files[0]); RefreshImage(img); // 拖入的图像由本窗体持有 _imageOwned = true; } } catch { } } private void UserControl_MouseDoubleClick(object sender, MouseButtonEventArgs e) => MouseDouble?.Invoke(this, Number); // 绘制 ROI 时右键用于「确认」,此时抑制右键菜单;非绘制态恢复菜单 private void MyHwindow_MouseRightButtonDown(object sender, MouseButtonEventArgs e) { if (_drawingBusy) MyHwindow.ContextMenu = null; else MyHwindow.ContextMenu = MyMenu; } #endregion #region 右键菜单:适应 / 保存 private void MenuFit_Click(object sender, RoutedEventArgs e) { FitCurrent(); Redraw(); } private void MenuZoomIn_Click(object sender, RoutedEventArgs e) => ZoomBy(0.8); // part 变小 = 放大 private void MenuZoomOut_Click(object sender, RoutedEventArgs e) => ZoomBy(1.25); // part 变大 = 缩小 /// /// 以画面中心为基准缩放:factor<1 放大、>1 缩小。读写窗口 part(GetPart/SetPart), /// 与 HSmartWindowControlWPF 内置的鼠标滚轮缩放共用同一 part 状态,不冲突。 /// public void ZoomBy(double factor) { var hw = Hwn; int refH = _layerMode ? _layerImgH : _imgH; int refW = _layerMode ? _layerImgW : _imgW; bool hasAny = _layerMode ? (_layerBaseImage != null && _layerBaseImage.IsInitialized()) : _hasImage; if (hw == null || !hasAny) return; try { hw.GetPart(out int r1, out int c1, out int r2, out int c2); double h = r2 - r1, w = c2 - c1; double nh = h * factor, nw = w * factor; if (nh < 4 || nw < 4) return; // 放大上限保护 if (nh > refH * 20 || nw > refW * 20) return; // 缩小上限保护 double cr = (r1 + r2) / 2.0, cc = (c1 + c2) / 2.0; hw.SetPart(cr - nh / 2, cc - nw / 2, cr + nh / 2, cc + nw / 2); Redraw(); } catch { } } private void MenuSave_Click(object sender, RoutedEventArgs e) { var img = _layerMode ? _layerBaseImage : _image; if (img == null || !img.IsInitialized()) return; var sfd = new Microsoft.Win32.SaveFileDialog { Filter = "PNG 图像|*.png|BMP 图像|*.bmp|TIFF 图像|*.tif|JPEG 图像|*.jpg", FileName = "halcon_image" }; if (sfd.ShowDialog() != true) return; try { string ext = Path.GetExtension(sfd.FileName).ToLowerInvariant(); string fmt = ext == ".bmp" ? "bmp" : ext == ".tif" || ext == ".tiff" ? "tiff" : ext == ".jpg" || ext == ".jpeg" ? "jpeg" : "png"; string noExt = Path.Combine(Path.GetDirectoryName(sfd.FileName) ?? "", Path.GetFileNameWithoutExtension(sfd.FileName)); HOperatorSet.WriteImage(img, fmt, 0, noExt); } catch (Exception ex) { MessageBox.Show($"保存失败:{ex.Message}"); } } #endregion #region 交互 ROI 绘制(HDrawingObject:左键拖动调整,右键确认) private void MenuRoiRect1_Click(object sender, RoutedEventArgs e) => _ = DrawAsync(Models.HalconRoiType.Rectangle1); private void MenuRoiRect2_Click(object sender, RoutedEventArgs e) => _ = DrawAsync(Models.HalconRoiType.Rectangle2); private void MenuRoiCircle_Click(object sender, RoutedEventArgs e) => _ = DrawAsync(Models.HalconRoiType.Circle); private void MenuRoiClear_Click(object sender, RoutedEventArgs e) { LastRoi = null; DetachDrawing(); Redraw(); } private async Task DrawAsync(Models.HalconRoiType type) { var hw = Hwn; var baseImg = _layerMode ? _layerBaseImage : _image; if (_drawingBusy || hw == null || baseImg == null || !baseImg.IsInitialized()) return; _drawingBusy = true; MyHwindow.ContextMenu = null; try { DetachDrawing(); _drawing = new HDrawingObject(); double h = _layerMode ? _layerImgH : _imgH; double w = _layerMode ? _layerImgW : _imgW; switch (type) { case Models.HalconRoiType.Rectangle1: _drawing.CreateDrawingObjectRectangle1(h * 0.25, w * 0.25, h * 0.75, w * 0.75); break; case Models.HalconRoiType.Rectangle2: _drawing.CreateDrawingObjectRectangle2(h * 0.5, w * 0.5, 0, Math.Min(w, h) * 0.25, Math.Min(w, h) * 0.15); break; case Models.HalconRoiType.Circle: _drawing.CreateDrawingObjectCircle(h * 0.5, w * 0.5, Math.Min(w, h) * 0.25); break; } _drawing.SetDrawingObjectParams("line_width", 2.5); _drawing.SetDrawingObjectParams("color", "green"); hw.AttachDrawingObjectToWindow(_drawing); // 后台等待右键确认(左键用于拖动 ROI 的控制点) await Task.Run(() => { int button = 0; while (true) { hw.GetMbutton(out int r, out int c, out button); if (button == 1) { while (button == 1) hw.GetMposition(out r, out c, out button); } else break; } }); LastRoi = ReadRoi(type); } catch { } finally { DetachDrawing(); _drawingBusy = false; MyHwindow.ContextMenu = MyMenu; Redraw(); } } private Models.HalconRoi ReadRoi(Models.HalconRoiType type) { if (_drawing == null) return null; var roi = new Models.HalconRoi { Type = type }; switch (type) { case Models.HalconRoiType.Rectangle1: roi.Row1 = _drawing.GetDrawingObjectParams("row1").D; roi.Col1 = _drawing.GetDrawingObjectParams("column1").D; roi.Row2 = _drawing.GetDrawingObjectParams("row2").D; roi.Col2 = _drawing.GetDrawingObjectParams("column2").D; break; case Models.HalconRoiType.Rectangle2: roi.Row = _drawing.GetDrawingObjectParams("row").D; roi.Col = _drawing.GetDrawingObjectParams("column").D; roi.Phi = _drawing.GetDrawingObjectParams("phi").D; roi.Length1 = _drawing.GetDrawingObjectParams("length1").D; roi.Length2 = _drawing.GetDrawingObjectParams("length2").D; break; case Models.HalconRoiType.Circle: roi.Row = _drawing.GetDrawingObjectParams("row").D; roi.Col = _drawing.GetDrawingObjectParams("column").D; roi.Radius = _drawing.GetDrawingObjectParams("radius").D; break; } return roi; } private void DetachDrawing() { try { if (_drawing != null) { Hwn?.DetachDrawingObjectFromWindow(_drawing); _drawing.Dispose(); } } catch { } finally { _drawing = null; } } #endregion } }