using Cognex.VisionPro; using Cognex.VisionPro.ImageFile; using Cognex.VisionPro3D; using log4net.Core; using MahApps.Metro.Controls; using Prism.Events; using Prism.Ioc; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Forms.Integration; using System.Windows.Interop; using TeamAAS_VP; using TeamAAS_VP.Core; using TeamAAS_VP.Enums; using TeamAAS_VP.Events; using TeamAAS_VP.Interfaces; using TeamAAS_VP.Models; using TeamAAS_VP.Resources.Languages; using TeamAAS_VP.ViewModels; using TeamAAS_VP.ViewModels.Home; //using static MaterialDesignThemes.Wtf.ToolBar; namespace TeamAAS_VP.Views.Home { /// /// Interaction logic for ShowVisionRender /// public partial class ShowVisionRender : UserControl { private const string Render2DKeySuffix = ":2D"; private const string Render3DKeySuffix = ":3D"; private static string Get2DKey(Guid id) => id.ToString() + Render2DKeySuffix; private static string Get3DKey(Guid id) => id.ToString() + Render3DKeySuffix; ShowVisionRenderViewModel viewModel; Dictionary vmRenders; Dictionary Titles; IEventAggregator _eventAggregator; private readonly IProductService _productService; private readonly Prism.Services.Dialogs.IDialogService _dialogService; private CancellationTokenSource _saveImageCts = new CancellationTokenSource(); private readonly object _saveImageCtsLock = new object(); private readonly SemaphoreSlim _saveSemaphore = new SemaphoreSlim(5, 5); private readonly ConcurrentQueue _pendingRenderUpdates = new ConcurrentQueue(); private int _renderDrainScheduled; private List delaySaveImages = new List(); /// /// 安全获取有效的 CancellationToken。如果当前 CTS 已释放则自动创建新的。 /// private CancellationToken GetSafeSaveToken() { lock (_saveImageCtsLock) { try { var token = _saveImageCts.Token; return token; } catch (ObjectDisposedException) { _saveImageCts = new CancellationTokenSource(); return _saveImageCts.Token; } } } public ShowVisionRender() { InitializeComponent(); vmRenders = new Dictionary(); Titles = new Dictionary(); viewModel = DataContext as ShowVisionRenderViewModel; _eventAggregator = viewModel._eventAggregator; _productService = ((Prism.PrismApplicationBase)App.Current).Container.Resolve(); // 新增:DialogService _dialogService = ((Prism.PrismApplicationBase)App.Current).Container.Resolve(); viewModel.UpdateLayout += ViewModel_UpdateLayout; // 后台线程仅负责准备渲染数据;真正的 UI 应用合并到单个 Background 优先级批处理里, // 避免每张图都以 Normal 优先级抢占用户输入。 _eventAggregator.GetEvent().Subscribe( EnqueueRenderUpdate, ThreadOption.BackgroundThread); _eventAggregator.GetEvent().Subscribe(ProductChanged); _eventAggregator.GetEvent().Subscribe(UpdateDelaySaveImage); } private bool GetView3DUse() { try { var configService = ((Prism.PrismApplicationBase)App.Current).Container.Resolve(); if(configService.GetSystemConfiguration().PickPointNum==0) { return false; } else { return true; } //return true; } catch { return false; } } private void EnqueueRenderUpdate(ShowRender render) { if (render == null) return; ICogImage imageCopy = null; try { imageCopy = render.Image?.CopyBase(CogImageCopyModeConstants.CopyPixels); } catch (Exception ex) { LogHelper.WriteLogError("深拷贝 ICogImage 失败", ex); } _pendingRenderUpdates.Enqueue(new ShowRender { Id = render.Id, DisplayName = render.DisplayName, Image = imageCopy, Graphic = render.Graphic, Record = render.Record, Result = render.Result, IsShow = render.IsShow, IsSaveImage = render.IsSaveImage, SaveImageModel = render.SaveImageModel, SaveImagePathModel = render.SaveImagePathModel, SavePath = render.SavePath, IsCompress = render.IsCompress, ProceductName = render.ProceductName, ImageFileName = render.ImageFileName, Is3DModel = render.Is3DModel, RangeImage = render.RangeImage, GreyImage = render.GreyImage, IsDelaySaveImage = render.IsDelaySaveImage, }); if (_pendingRenderUpdates.Count > 8) { LogHelper.WriteLogInfo($"[UI-RENDER] Pending render queue depth={_pendingRenderUpdates.Count}"); } ScheduleRenderDrain(); } private void ScheduleRenderDrain() { if (Interlocked.CompareExchange(ref _renderDrainScheduled, 1, 0) != 0) return; _ = this.gridRender.Dispatcher.InvokeAsync(async () => { try { int processed = 0; while (_pendingRenderUpdates.TryDequeue(out var render)) { ApplyRenderUpdate(render); processed++; if (processed % 2 == 0 && !_pendingRenderUpdates.IsEmpty) await System.Windows.Threading.Dispatcher.Yield(System.Windows.Threading.DispatcherPriority.Input); } } finally { Interlocked.Exchange(ref _renderDrainScheduled, 0); if (!_pendingRenderUpdates.IsEmpty) ScheduleRenderDrain(); } }, System.Windows.Threading.DispatcherPriority.Background); } private void ApplyRenderUpdate(ShowRender render) { if (render.Is3DModel == true && GetView3DUse() == true) { if (!vmRenders.TryGetValue(Get3DKey(render.Id), out var host3D)) return; var display3D = host3D.Child as Cognex.VisionPro3D.Cog3DDisplayV2WF; if (display3D == null) return; try { display3D.Clear(); if (render.RangeImage == null || render.GreyImage == null) return; var rImgG = new Cog3DRangeImageGraphic(render.RangeImage, render.GreyImage); display3D.Add(rImgG, "__default"); display3D.FitView(); } catch (System.Windows.Forms.AxHost.InvalidActiveXStateException) { } } if (!vmRenders.TryGetValue(Get2DKey(render.Id), out var host2D)) return; var recordDisplay = host2D.Child as Cognex.VisionPro.CogRecordDisplay; if (recordDisplay == null) return; try { recordDisplay.Record = null; recordDisplay.Image = render.Image; recordDisplay.StaticGraphics.Clear(); if (render.Graphic != null) recordDisplay.StaticGraphics.AddList(render.Graphic, ""); if (render.Record != null) recordDisplay.Record = render.Record; if (render.IsSaveImage) { RequestSaveImage(render, recordDisplay); } } catch (System.Windows.Forms.AxHost.InvalidActiveXStateException) { } } private void AddRenderPair(Guid id, string displayName) { // 2D var display2D = new Cognex.VisionPro.CogRecordDisplay() { Tag = displayName }; display2D.HandleCreated += (s, e) => { try { display2D.HorizontalScrollBar = false; display2D.VerticalScrollBar = false; display2D.AutoFit = true; display2D.BackColor = System.Drawing.SystemColors.ActiveCaption; } catch (System.Windows.Forms.AxHost.InvalidActiveXStateException) { } }; // 绑定双击:弹出预览窗口 display2D.DoubleClick += (s, e) => OnDisplayDoubleClick(display2D); vmRenders.Add(Get2DKey(id), new WindowsFormsHost() { Tag = displayName, Child = display2D }); // 3D:按配置创建 if (!GetView3DUse()) return; // 3D var display3D = new Cognex.VisionPro3D.Cog3DDisplayV2WF() { Tag = displayName }; display3D.HandleCreated += (s, e) => { try { display3D.BackColor = System.Drawing.SystemColors.ActiveCaption; } catch (System.Windows.Forms.AxHost.InvalidActiveXStateException) { } }; vmRenders.Add(Get3DKey(id), new WindowsFormsHost() { Tag = displayName, Child = display3D }); } private Grid CreateCellGrid(Guid id, string displayName, System.Windows.Media.FontFamily font) { var is3DEnabled = GetView3DUse(); var cell = new Grid(); cell.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) }); cell.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); var title = new TextBlock() { Text = displayName, HorizontalAlignment = System.Windows.HorizontalAlignment.Center, FontWeight = FontWeights.Bold, FontFamily = font, FontSize = 12, Foreground = System.Windows.Media.Brushes.Black }; Titles[id] = title; var content = new Grid(); if(is3DEnabled) { content.ColumnDefinitions.Add(new ColumnDefinition()); // 2D content.ColumnDefinitions.Add(new ColumnDefinition()); // 3D content.Children.Add(vmRenders[Get2DKey(id)]); content.Children.Add(vmRenders[Get3DKey(id)]); Grid.SetColumn(vmRenders[Get2DKey(id)], 0); Grid.SetColumn(vmRenders[Get3DKey(id)], 1); } else { content.ColumnDefinitions.Add(new ColumnDefinition()); // 仅 2D content.Children.Add(vmRenders[Get2DKey(id)]); Grid.SetColumn(vmRenders[Get2DKey(id)], 0); } cell.Children.Add(title); cell.Children.Add(content); Grid.SetRow(title, 0); Grid.SetRow(content, 1); return cell; } Grid grid1 = new Grid(); Grid grid2 = new Grid(); Grid grid3 = new Grid(); Grid grid4 = new Grid(); Grid grid5 = new Grid(); Grid grid6 = new Grid(); Grid grid7 = new Grid(); Grid grid8 = new Grid(); Grid grid9 = new Grid(); Grid grid10 = new Grid(); Grid grid11 = new Grid(); Grid grid12 = new Grid(); Grid grid13 = new Grid(); Grid grid14 = new Grid(); Grid grid15 = new Grid(); Grid grid16 = new Grid(); Grid grid17 = new Grid(); Grid grid18 = new Grid(); Grid grid19 = new Grid(); Grid grid20 = new Grid(); private void ViewModel_UpdateLayout(System.Collections.ObjectModel.ObservableCollection obj) { System.Windows.Media.FontFamily font = Application.Current.Resources["DefaultFont"] as System.Windows.Media.FontFamily; if (vmRenders.Count > 0) { foreach (var render in vmRenders) { render.Value.Dispose(); } } vmRenders.Clear(); gridRender.Children.Clear(); gridRender.RowDefinitions.Clear(); gridRender.ColumnDefinitions.Clear(); Titles.Clear(); if (obj == null || obj.Count == 0) return; // 先创建所有相机的 2D/3D 对 for (int i = 0; i < obj.Count; i++) AddRenderPair(obj[i].Id, obj[i].DisplayName); switch (obj.Count) { case 1: { gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); var cell = CreateCellGrid(obj[0].Id, obj[0].DisplayName, font); gridRender.Children.Add(cell); Grid.SetRow(cell, 0); Grid.SetColumn(cell, 0); gridRender.ShowGridLines = false; break; } case 2: { gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ShowGridLines = true; var cell0 = CreateCellGrid(obj[0].Id, obj[0].DisplayName, font); var cell1 = CreateCellGrid(obj[1].Id, obj[1].DisplayName, font); gridRender.Children.Add(cell0); gridRender.Children.Add(cell1); Grid.SetRow(cell0, 0); Grid.SetColumn(cell0, 0); Grid.SetRow(cell1, 0); Grid.SetColumn(cell1, 1); break; } case 3: { gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ShowGridLines = true; var cell0 = CreateCellGrid(obj[0].Id, obj[0].DisplayName, font); var cell1 = CreateCellGrid(obj[1].Id, obj[1].DisplayName, font); var cell2 = CreateCellGrid(obj[2].Id, obj[2].DisplayName, font); gridRender.Children.Add(cell0); gridRender.Children.Add(cell1); gridRender.Children.Add(cell2); Grid.SetRow(cell0, 0); Grid.SetColumn(cell0, 0); Grid.SetRow(cell1, 0); Grid.SetColumn(cell1, 1); Grid.SetRow(cell2, 1); Grid.SetColumn(cell2, 0); Grid.SetColumnSpan(cell2, 2); break; } case 4: { gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ShowGridLines = true; for (int i = 0; i < 4; i++) { var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font); gridRender.Children.Add(cell); Grid.SetRow(cell, i / 2); Grid.SetColumn(cell, i % 2); } break; } case 5: { // 你的原布局:上面2个,下面3个(通过嵌套grid6) gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ShowGridLines = true; var top0 = CreateCellGrid(obj[0].Id, obj[0].DisplayName, font); var top1 = CreateCellGrid(obj[1].Id, obj[1].DisplayName, font); var bottom = new Grid(); bottom.ColumnDefinitions.Add(new ColumnDefinition()); bottom.ColumnDefinitions.Add(new ColumnDefinition()); bottom.ColumnDefinitions.Add(new ColumnDefinition()); var b0 = CreateCellGrid(obj[2].Id, obj[2].DisplayName, font); var b1 = CreateCellGrid(obj[3].Id, obj[3].DisplayName, font); var b2 = CreateCellGrid(obj[4].Id, obj[4].DisplayName, font); bottom.Children.Add(b0); bottom.Children.Add(b1); bottom.Children.Add(b2); Grid.SetColumn(b0, 0); Grid.SetColumn(b1, 1); Grid.SetColumn(b2, 2); gridRender.Children.Add(top0); gridRender.Children.Add(top1); gridRender.Children.Add(bottom); Grid.SetRow(top0, 0); Grid.SetColumn(top0, 0); Grid.SetRow(top1, 0); Grid.SetColumn(top1, 1); Grid.SetRow(bottom, 1); Grid.SetColumn(bottom, 0); Grid.SetColumnSpan(bottom, 2); break; } case 6: { // 你的原布局:2行3列 gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ShowGridLines = true; for (int i = 0; i < 6; i++) { var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font); gridRender.Children.Add(cell); Grid.SetRow(cell, i / 3); Grid.SetColumn(cell, i % 3); } break; } case 7: { // 你的原布局:第一行4个,第二行3个(但原来只放到 col 0/1/2) // 这里保持原样:第一行4列,第二行3列(第4列留空) gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ShowGridLines = true; for (int i = 0; i < 7; i++) { var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font); gridRender.Children.Add(cell); if (i < 4) { Grid.SetRow(cell, 0); Grid.SetColumn(cell, i); } else { Grid.SetRow(cell, 1); Grid.SetColumn(cell, i - 4); } } break; } case 8: { // 2行4列 gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); for (int i = 0; i < 4; i++) gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ShowGridLines = true; for (int i = 0; i < 8; i++) { var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font); gridRender.Children.Add(cell); Grid.SetRow(cell, i / 4); Grid.SetColumn(cell, i % 4); } break; } case 9: { // 3行3列 gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ShowGridLines = true; for (int i = 0; i < 9; i++) { var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font); gridRender.Children.Add(cell); Grid.SetRow(cell, i / 3); Grid.SetColumn(cell, i % 3); } break; } case 12: { // 3行4列 gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); for (int i = 0; i < 4; i++) gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ShowGridLines = true; for (int i = 0; i < 12; i++) { var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font); gridRender.Children.Add(cell); Grid.SetRow(cell, i / 4); Grid.SetColumn(cell, i % 4); } break; } case 16: { // 4行4列 gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); for (int i = 0; i < 4; i++) gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ShowGridLines = true; for (int i = 0; i < 16; i++) { var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font); gridRender.Children.Add(cell); Grid.SetRow(cell, i / 4); Grid.SetColumn(cell, i % 4); } break; } case 20: { // 5行4列 for (int r = 0; r < 5; r++) gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); for (int c = 0; c < 4; c++) gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ShowGridLines = true; for (int i = 0; i < 20; i++) { var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font); gridRender.Children.Add(cell); Grid.SetRow(cell, i / 4); Grid.SetColumn(cell, i % 4); } break; } default: { // 未覆盖的数量:先用简单的 1列多行兜底(避免空白) gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); gridRender.ColumnDefinitions.Add(new ColumnDefinition()); gridRender.ShowGridLines = false; var cell = CreateCellGrid(obj[0].Id, obj[0].DisplayName, font); gridRender.Children.Add(cell); Grid.SetRow(cell, 0); Grid.SetColumn(cell, 0); break; } } } /// /// 请求保存图像(在 WinForms 消息循环线程截图,然后派发到后台线程保存) /// 使用 SemaphoreSlim 控制并发,支持 CancellationToken 取消 /// private void RequestSaveImage(ShowRender render, Cognex.VisionPro.CogRecordDisplay disp) { if (render == null) return; if (disp == null) return; if (!render.IsSaveImage) { LogHelper.WriteLogInfo($"[SAVE-IMAGE] 跳过保存: IsSaveImage=false, Id={render.Id}, DisplayName={render.DisplayName}"); return; } if (!render.IsDelaySaveImage && string.IsNullOrEmpty(_productService.CurrentProductCode)) { _eventAggregator.GetEvent() .Publish(new MessageStruct() { Message = "当前产品SN不存在,无法保存图片!", level = MessageLevel.Alarm }); return; } //LogHelper.WriteLogInfo($"[SAVE-IMAGE] 开始保存流程: Id={render.Id}, SaveModel={render.SaveImageModel}, SavePath={render.SavePath}, IsDelay={render.IsDelaySaveImage}, ProductCode={_productService.CurrentProductCode}"); bool needRecordedBitmap = render.SaveImageModel == ProcedureSaveImageModel.Recorded || render.SaveImageModel == ProcedureSaveImageModel.OriginalAndRecorded; var token = GetSafeSaveToken(); disp.BeginInvoke(new Action(() => { Bitmap clonedBitmap = null; try { if (token.IsCancellationRequested) return; if (needRecordedBitmap) { if (disp.IsDisposed || !disp.IsHandleCreated) return; Bitmap uiBitmap = (Bitmap)disp.CreateContentBitmap( Cognex.VisionPro.Display.CogDisplayContentBitmapConstants.Custom); clonedBitmap = (Bitmap)uiBitmap.Clone(); uiBitmap.Dispose(); } var capturedBitmap = clonedBitmap; var capturedRender = render; Task.Run(async () => { await _saveSemaphore.WaitAsync(token); try { if (token.IsCancellationRequested) return; if (capturedRender.IsDelaySaveImage) { // 延迟保存:存入列表,等 DelaySaveImageNotification 时统一写入 ICogImage imageDeepCopy = null; try { imageDeepCopy = capturedRender.Image? .CopyBase(CogImageCopyModeConstants.CopyPixels); } catch (Exception ex) { LogHelper.WriteLogError("延迟保存深拷贝 ICogImage 失败", ex); } var delayItem = new Models.DelaySaveImage() { SaveImageModel = capturedRender.SaveImageModel, SaveImagePathModel = capturedRender.SaveImagePathModel, SavePath = capturedRender.SavePath, Image = imageDeepCopy, clonedBitmap = capturedBitmap != null ? (Bitmap)capturedBitmap.Clone() : null, Result = capturedRender.Result, IsCompress = capturedRender.IsCompress, ImageFileName = capturedRender.ImageFileName, Is3DModel = capturedRender.Is3DModel, }; lock (delaySaveImages) { delaySaveImages.Add(delayItem); } } else { if (string.IsNullOrEmpty(_productService.CurrentProductCode)) { _eventAggregator.GetEvent() .Publish(new MessageStruct() { Message = "当前产品SN不存在,无法保存图片!", level = MessageLevel.Alarm }); return; } LogHelper.WriteLogInfo("开始异步保存图像"); await SaveImageAsync( capturedRender.SaveImageModel, capturedRender.SaveImagePathModel, capturedRender.SavePath, capturedRender.Image, capturedBitmap, capturedRender.Result, capturedRender.IsCompress, capturedRender.ImageFileName, capturedRender.Is3DModel, token ); } } catch (OperationCanceledException) { } catch (Exception ex) { LogHelper.WriteLogError("后台保存图像任务出错!", ex); } finally { try { capturedBitmap?.Dispose(); } catch { } _saveSemaphore.Release(); } }, token); } catch (Exception ex) { LogHelper.WriteLogError("RequestSaveImage(UI截图阶段) 出错!", ex); try { clonedBitmap?.Dispose(); } catch { } } })); } /// /// 异步保存图像(调用者已在后台线程,不再嵌套 Task.Run;通过 CancellationToken 支持取消) /// private async Task SaveImageAsync( ProcedureSaveImageModel saveImageModel, ProcedureSaveImagePathModel saveImagePathModel, string path, ICogImage image, Bitmap reimage, bool result, bool isCompress, string imageFileName, bool is3Dmodel, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(path) || string.IsNullOrWhiteSpace(path) || !Path.IsPathRooted(path)) { LogHelper.WriteLogInfo($"[SAVE-IMAGE] 保存失败:路径无效 path=\"{path}\""); _eventAggregator.GetEvent() .Publish(new Models.MessageStruct() { Message = "保存图像路径为无效的路径!", level = MessageLevel.Alarm }); return; } //LogHelper.WriteLogInfo($"[SAVE-IMAGE] SaveImageAsync 开始: path={path}, model={saveImageModel}, pathModel={saveImagePathModel}, result={result}, is3D={is3Dmodel}, fileName={imageFileName}"); try { await Task.CompletedTask; // 消除 CS1998:调用者已在后台线程,下方所有 I/O 为同步执行 cancellationToken.ThrowIfCancellationRequested(); DateTime now = DateTime.Now; //图片名 string imgName = now.ToString("yyyy-MM-dd HH_mm_ss"); if (!string.IsNullOrEmpty(imageFileName)) { imgName = imageFileName; } if (saveImageModel == ProcedureSaveImageModel.Original) { string Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.bmp"; if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_OkNG_Time) { Originalpath = result ? $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp" : $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp"; } else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ok_Time) { if (result) Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp"; else return; } else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ng_Time) { if (!result) Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp"; else return; } else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Formula_ProductSN_Time) { // 当前产品配方名(过滤非法字符) string formulaName = _productService.GetCurrentProduct().Name; foreach (char c in Path.GetInvalidFileNameChars()) formulaName = formulaName.Replace(c, '_'); // 产品SN(过滤非法字符) string productSN = _productService.CurrentProductCode; if (productSN != null) { foreach (char c in Path.GetInvalidFileNameChars()) productSN = productSN.Replace(c, '_'); } string resultStr = result ? "OK" : "NG"; Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.bmp"; } cancellationToken.ThrowIfCancellationRequested(); if (!Directory.Exists(Path.GetDirectoryName(Originalpath))) Directory.CreateDirectory(Path.GetDirectoryName(Originalpath)); using (CogImageFileBMP _cogImageFileTool = new CogImageFileBMP()) { _cogImageFileTool.Open(Originalpath, CogImageFileModeConstants.Write); _cogImageFileTool.Append(image); _cogImageFileTool.Close(); } } else if (saveImageModel == ProcedureSaveImageModel.Recorded) { string Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.bmp"; if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_OkNG_Time) { Recordedpath = result ? $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp" : $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp"; } else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ok_Time) { if (result) Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp"; else return; } else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ng_Time) { if (!result) Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp"; else return; } else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Formula_ProductSN_Time) { string formulaName = _productService.GetCurrentProduct().Name; foreach (char c in Path.GetInvalidFileNameChars()) formulaName = formulaName.Replace(c, '_'); string productSN = _productService.CurrentProductCode; foreach (char c in Path.GetInvalidFileNameChars()) productSN = productSN.Replace(c, '_'); string resultStr = result ? "OK" : "NG"; Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.bmp"; } cancellationToken.ThrowIfCancellationRequested(); if (!Directory.Exists(Path.GetDirectoryName(Recordedpath))) Directory.CreateDirectory(Path.GetDirectoryName(Recordedpath)); if (isCompress) { ImageHelper.CompressImage(reimage, Recordedpath, 20); } else { reimage.Save(Recordedpath); } } else if (saveImageModel == ProcedureSaveImageModel.OriginalAndRecorded) { string Originalpath = ""; if (is3Dmodel == true) { Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.idb"; } else { Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.bmp"; } string Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.bmp"; if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_OkNG_Time) { if (result) { Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp"; Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp"; } else { Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp"; Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp"; } } else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ok_Time) { if (result) { Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp"; Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp"; } else { return; } } else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ng_Time) { if (!result) { Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp"; Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp"; } else { return; } } else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Formula_ProductSN_Time) { string formulaName = _productService.GetCurrentProduct().Name; foreach (char c in Path.GetInvalidFileNameChars()) formulaName = formulaName.Replace(c, '_'); string productSN = _productService.CurrentProductCode; if (productSN != null) { foreach (char c in Path.GetInvalidFileNameChars()) productSN = productSN.Replace(c, '_'); } string resultStr = result ? "OK" : "NG"; if (is3Dmodel == true) { Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.idb"; } else { Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.bmp"; } Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.bmp"; } cancellationToken.ThrowIfCancellationRequested(); if (!Directory.Exists(Path.GetDirectoryName(Originalpath))) Directory.CreateDirectory(Path.GetDirectoryName(Originalpath)); using (CogImageFile _cogImageFileTool = new CogImageFile()) { _cogImageFileTool.Open(Originalpath, CogImageFileModeConstants.Write); _cogImageFileTool.Append(image); _cogImageFileTool.Close(); } cancellationToken.ThrowIfCancellationRequested(); if (!Directory.Exists(Path.GetDirectoryName(Recordedpath))) Directory.CreateDirectory(Path.GetDirectoryName(Recordedpath)); if (isCompress) { ImageHelper.CompressImage(reimage, Recordedpath, 20); } else { reimage.Save(Recordedpath); } } } catch (OperationCanceledException) { } catch (Exception ex) { LogHelper.WriteLogError($"[SAVE-IMAGE] 保存流程运行结果的图片时出错!path={path}", ex); } finally { try { reimage?.Dispose(); } catch { } } } /// /// 延迟保存图像处理:isSave=true 时批量保存所有待保存项;false 时丢弃并清理 /// private async void UpdateDelaySaveImage(bool isSave) { if (!isSave) { lock (delaySaveImages) { foreach (var item in delaySaveImages) { try { item.clonedBitmap?.Dispose(); } catch { } try { item.Image = null; } catch { } } delaySaveImages.Clear(); } return; } List itemsToSave; lock (delaySaveImages) { itemsToSave = new List(delaySaveImages); delaySaveImages.Clear(); } var token = GetSafeSaveToken(); foreach (var item in itemsToSave) { try { if (token.IsCancellationRequested) break; if (string.IsNullOrEmpty(_productService.CurrentProductCode)) { _eventAggregator.GetEvent() .Publish(new MessageStruct() { Message = "当前产品SN不存在,无法保存图片!", level = MessageLevel.Alarm }); break; } LogHelper.WriteLogInfo("开始异步保存延迟图像"); await SaveImageAsync( item.SaveImageModel, item.SaveImagePathModel, item.SavePath, item.Image, item.clonedBitmap, item.Result, item.IsCompress, item.ImageFileName, item.Is3DModel, token ); } catch (OperationCanceledException) { break; } catch (Exception ex) { LogHelper.WriteLogError("延迟保存图像任务出错!", ex); } finally { try { item.clonedBitmap?.Dispose(); } catch { } try { item.Image = null; } catch { } } } } private void ShowVisionRender_Unloaded(object sender, RoutedEventArgs e) { try { _saveImageCts.Cancel(); _saveImageCts.Dispose(); } catch { } // 释放延迟保存列表中的 Bitmap 资源 lock (delaySaveImages) { foreach (var item in delaySaveImages) { try { item.clonedBitmap?.Dispose(); } catch { } try { item.Image = null; } catch { } } delaySaveImages.Clear(); } } private void ProductChanged(ProductModel product) { this.gridRender.Dispatcher.BeginInvoke(new Action(() => { var is3DEnabled = GetView3DUse(); foreach (var item in vmRenders) { try { // 2D:永远清理 var display2D = item.Value.Child as Cognex.VisionPro.CogRecordDisplay; if (display2D != null) { display2D.Record = null; display2D.Image = null; display2D.StaticGraphics.Clear(); continue; } // 3D:仅在启用 3D 时清理 if (!is3DEnabled) continue; var display3D = item.Value.Child as Cognex.VisionPro3D.Cog3DDisplayV2WF; if (display3D != null) { display3D.Clear(); } } catch (System.Windows.Forms.AxHost.InvalidActiveXStateException) { // 控件句柄/ActiveX 未就绪时忽略 } catch (Exception ex) { LogHelper.WriteLogError(Lang.切换产品清除主页图像时出错, ex); } } })); } private void OnDisplayDoubleClick(CogRecordDisplay disp) { if (disp == null) return; try { // 直接把当前 CogRecordDisplay 传给对话框 VM(VM 内部读取 Image/Record) var parameters = new Prism.Services.Dialogs.DialogParameters { { "Cog", disp } }; // Prism Dialog 必须在 WPF UI 线程调用 Dispatcher.BeginInvoke(new Action(() => { try { _dialogService.ShowDialog("ImagePreviewWindow", parameters, r => { }); } catch (Exception ex) { LogHelper.WriteLogError("打开 ImagePreviewWindow 失败", ex); } })); } catch (Exception ex) { LogHelper.WriteLogError("双击打开预览窗口失败", ex); } } } }