ShowVisionRender.xaml.cs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  1. using Cognex.VisionPro;
  2. using Cognex.VisionPro.ImageFile;
  3. using Prism.Events;
  4. using Prism.Ioc;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Drawing;
  8. using System.IO;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using System.Windows;
  12. using System.Windows.Controls;
  13. using System.Windows.Forms.Integration;
  14. using TeamAAS_VP.Core;
  15. using TeamAAS_VP.Enums;
  16. using TeamAAS_VP.Events;
  17. using TeamAAS_VP.Interfaces;
  18. using TeamAAS_VP.Models;
  19. using TeamAAS_VP.Resources.Languages;
  20. using TeamAAS_VP.ViewModels.Home;
  21. namespace TeamAAS_VP.Views.Home
  22. {
  23. /// <summary>
  24. /// Interaction logic for ShowVisionRender
  25. /// </summary>
  26. public partial class ShowVisionRender : UserControl
  27. {
  28. private ShowVisionRenderViewModel viewModel;
  29. // key=ShowRender.Id
  30. private readonly Dictionary<Guid, WindowsFormsHost> vmRenders;
  31. private readonly Dictionary<Guid, TextBlock> Titles;
  32. private readonly IEventAggregator _eventAggregator;
  33. private readonly ISystemDatabaseService _systemDatabaseService;
  34. private readonly IProductService _productService;
  35. // ======================= 【新增】保存任务控制:防闪退/防资源泄露 =======================
  36. private CancellationTokenSource _saveImageCts = new CancellationTokenSource();
  37. private readonly SemaphoreSlim _saveSemaphore = new SemaphoreSlim(2, 2);
  38. public ShowVisionRender()
  39. {
  40. InitializeComponent();
  41. vmRenders = new Dictionary<Guid, WindowsFormsHost>();
  42. Titles = new Dictionary<Guid, TextBlock>();
  43. viewModel = DataContext as ShowVisionRenderViewModel;
  44. _eventAggregator = viewModel._eventAggregator;
  45. _systemDatabaseService = ((Prism.PrismApplicationBase)App.Current).Container.Resolve<ISystemDatabaseService>();
  46. _productService = ((Prism.PrismApplicationBase)App.Current).Container.Resolve<IProductService>();
  47. viewModel.UpdateLayout += ViewModel_UpdateLayout;
  48. _eventAggregator.GetEvent<RenderUpdateNotification>().Subscribe(UpdateRenderModuleSource);
  49. _eventAggregator.GetEvent<ProductChangedNotification>().Subscribe(ProductChanged);
  50. // 【可选】控件卸载时取消所有保存任务,避免页面关闭后仍在截图/保存导致闪退
  51. this.Unloaded += ShowVisionRender_Unloaded;
  52. }
  53. private void ShowVisionRender_Unloaded(object sender, RoutedEventArgs e)
  54. {
  55. try
  56. {
  57. _saveImageCts.Cancel();
  58. _saveImageCts.Dispose();
  59. }
  60. catch { }
  61. }
  62. /// <summary>
  63. /// 更新某个工位/模块的显示内容(图像/图形/Record/保存)
  64. /// </summary>
  65. private void UpdateRenderModuleSource(ShowRender render)
  66. {
  67. this.gridRender.Dispatcher.BeginInvoke(new Action(() =>
  68. {
  69. foreach (var item in vmRenders)
  70. {
  71. if (item.Value?.Child is CogRecordDisplay disp)
  72. {
  73. // 通过 Tag(产品名) 匹配当前需要更新的窗口
  74. if (disp.Tag != null && disp.Tag.ToString() == render.ProceductName)
  75. {
  76. disp.Record = null;
  77. disp.Image = render.Image;
  78. disp.StaticGraphics.Clear();
  79. if (render.Graphic != null)
  80. disp.StaticGraphics.AddList(render.Graphic, "");
  81. if (render.Record != null)
  82. disp.Record = render.Record;
  83. // ======================= 【关键优化】保存图像走安全入口 =======================
  84. RequestSaveImage(render, disp);
  85. break;
  86. }
  87. }
  88. }
  89. //foreach (var item in vmRenders)
  90. //{
  91. // if (((Cognex.VisionPro.CogRecordDisplay)item.Value.Child).Tag!=null && ((Cognex.VisionPro.CogRecordDisplay)item.Value.Child).Tag.ToString() == render.CameraName)
  92. // {
  93. // ((Cognex.VisionPro.CogRecordDisplay)item.Value.Child).Record = null;
  94. // ((Cognex.VisionPro.CogRecordDisplay)item.Value.Child).Image = render.Image;
  95. // ((Cognex.VisionPro.CogRecordDisplay)item.Value.Child).StaticGraphics.Clear();
  96. // if (render.Graphic!=null)
  97. // ((Cognex.VisionPro.CogRecordDisplay)item.Value.Child).StaticGraphics.AddList(render.Graphic, "");
  98. // if (render.Record != null)
  99. // ((Cognex.VisionPro.CogRecordDisplay)item.Value.Child).Record = render.Record;
  100. // if (render.IsSaveImage)
  101. // {
  102. // SaveImage(render.SaveImageModel, render.SaveImagePathModel, render.SavePath, render.Image, (Bitmap)((Cognex.VisionPro.CogRecordDisplay)item.Value.Child).CreateContentBitmap(Cognex.VisionPro.Display.CogDisplayContentBitmapConstants.Custom), render.Result, render.IsCompress, render.ImgPara);
  103. // }
  104. // break;
  105. // }
  106. //}
  107. /*
  108. foreach (var item in gridRender.Children)
  109. {
  110. if (item is WindowsFormsHost && ((WindowsFormsHost)item).Child is Cognex.VisionPro.CogRecordDisplay)
  111. {
  112. if (((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item).Child).Tag != null && ((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item).Child).Tag.ToString() == render.CameraName)
  113. {
  114. ((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item).Child).Image = render.Image;
  115. ((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item).Child).StaticGraphics.Clear();
  116. ((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item).Child).StaticGraphics.AddList(render.Graphic, "");
  117. if (render.Record != null)
  118. ((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item).Child).Record = render.Record;
  119. if (render.IsSaveImage)
  120. {
  121. SaveImage(render.SaveImageModel, render.SaveImagePathModel, render.SavePath, render.Image, (Bitmap)((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item).Child).CreateContentBitmap(Cognex.VisionPro.Display.CogDisplayContentBitmapConstants.Custom), render.Result, render.IsCompress);
  122. }
  123. break;
  124. }
  125. }
  126. else if (item is Grid)
  127. {
  128. foreach (var item1 in ((Grid)item).Children)
  129. {
  130. if (item1 is WindowsFormsHost && ((WindowsFormsHost)item1).Child is Cognex.VisionPro.CogRecordDisplay)
  131. {
  132. if (((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item1).Child).Tag != null && ((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item1).Child).Tag.ToString() == render.CameraName)
  133. {
  134. ((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item1).Child).Image = render.Image;
  135. ((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item1).Child).StaticGraphics.Clear();
  136. ((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item1).Child).StaticGraphics.AddList(render.Graphic, "");
  137. if (render.Record != null)
  138. ((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item).Child).Record = render.Record;
  139. if (render.IsSaveImage)
  140. {
  141. SaveImage(render.SaveImageModel, render.SaveImagePathModel, render.SavePath, render.Image, (Bitmap)((Cognex.VisionPro.CogRecordDisplay)((WindowsFormsHost)item).Child).CreateContentBitmap(Cognex.VisionPro.Display.CogDisplayContentBitmapConstants.Custom), render.Result, render.IsCompress);
  142. }
  143. break;
  144. }
  145. }
  146. }
  147. break;
  148. }
  149. }*/
  150. }));
  151. }
  152. /// <summary>
  153. /// 【核心】根据 obj.Count 自动生成 1~16 个显示格子(标题+CogRecordDisplay)
  154. /// </summary>
  155. private async void ViewModel_UpdateLayout(System.Collections.ObjectModel.ObservableCollection<Models.ShowRender> obj)
  156. {
  157. // ======================= 【关键优化】布局刷新前先取消旧保存任务 =======================
  158. try
  159. {
  160. _saveImageCts.Cancel();
  161. _saveImageCts.Dispose();
  162. }
  163. catch { }
  164. _saveImageCts = new CancellationTokenSource();
  165. System.Windows.Media.FontFamily font = Application.Current.Resources["DefaultFont"] as System.Windows.Media.FontFamily;
  166. // 1) 清理旧控件
  167. if (vmRenders.Count > 0)
  168. {
  169. foreach (var render in vmRenders)
  170. {
  171. // WindowsFormsHost.Dispose() 会销毁 WinForms 句柄,防止资源泄漏
  172. render.Value?.Dispose();
  173. }
  174. }
  175. vmRenders.Clear();
  176. Titles.Clear();
  177. gridRender.Children.Clear();
  178. gridRender.RowDefinitions.Clear();
  179. gridRender.ColumnDefinitions.Clear();
  180. // 2) 判空
  181. if (obj == null || obj.Count == 0) return;
  182. // 3) 限制最大 16(防止越界)
  183. int count = Math.Min(obj.Count, 16);
  184. // 4) 计算行列(最大 4*4)
  185. GetGridSize(count, out int rows, out int cols);
  186. // 5) 创建主Grid的行列
  187. for (int r = 0; r < rows; r++)
  188. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  189. for (int c = 0; c < cols; c++)
  190. gridRender.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
  191. gridRender.ShowGridLines = true;
  192. // 6) 逐个创建子格子(每个子格子两行:标题/画面)
  193. for (int i = 0; i < count; i++)
  194. {
  195. var sr = obj[i];
  196. // (1) 创建显示控件(WindowsFormsHost + CogRecordDisplay)
  197. var disp = new CogRecordDisplay()
  198. {
  199. Tag = sr.ProceductName,
  200. };
  201. disp.HandleCreated += (s, e) =>
  202. {
  203. try
  204. {
  205. ConfigureCogDisplay(disp); // 句柄创建后再配置,最稳
  206. // 绑定 WinForms 双击事件
  207. //disp.MouseDoubleClick += (r, w) =>
  208. //{
  209. // try
  210. // {
  211. // OnDisplayDoubleClick(disp);
  212. // }
  213. // catch (Exception ex)
  214. // {
  215. // LogHelper.WriteLogError("双击预览弹窗出错", ex);
  216. // }
  217. //};
  218. }
  219. catch (Exception ex)
  220. {
  221. LogHelper.WriteLogError("ConfigureCogDisplay 失败", ex);
  222. }
  223. };
  224. var host = new WindowsFormsHost()
  225. {
  226. Tag = sr.ProceductName,
  227. Child = disp
  228. };
  229. vmRenders.Add(sr.Id, host);
  230. // (2) 创建标题
  231. var title = new TextBlock()
  232. {
  233. Text = sr.ProceductName,
  234. HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
  235. FontWeight = FontWeights.Bold,
  236. FontFamily = font,
  237. FontSize = 12,
  238. Foreground = System.Windows.Media.Brushes.Black
  239. };
  240. Titles.Add(sr.Id, title);
  241. // (3) 子Grid(标题 + 显示)
  242. var cell = new Grid();
  243. cell.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) }); // 标题
  244. cell.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); // 图像
  245. cell.Children.Add(title);
  246. cell.Children.Add(host);
  247. Grid.SetRow(title, 0);
  248. Grid.SetColumn(title, 0);
  249. Grid.SetRow(host, 1);
  250. Grid.SetColumn(host, 0);
  251. // (4) 放到主 gridRender 中
  252. int row = i / cols;
  253. int col = i % cols;
  254. gridRender.Children.Add(cell);
  255. Grid.SetRow(cell, row);
  256. Grid.SetColumn(cell, col);
  257. }
  258. }
  259. /// <summary>
  260. /// 根据数量返回最合适的 rows/cols(最大 4*4)
  261. /// </summary>
  262. private void GetGridSize(int count, out int rows, out int cols)
  263. {
  264. rows = 1;
  265. cols = 1;
  266. if (count <= 1) { rows = 1; cols = 1; return; }
  267. if (count <= 2) { rows = 1; cols = 2; return; }
  268. if (count <= 4) { rows = 2; cols = 2; return; }
  269. if (count <= 6) { rows = 2; cols = 3; return; }
  270. if (count <= 9) { rows = 3; cols = 3; return; }
  271. if (count <= 12) { rows = 3; cols = 4; return; }
  272. rows = 4;
  273. cols = 4;
  274. }
  275. /// <summary>
  276. /// 统一配置 VisionPro 显示属性
  277. /// </summary>
  278. private void ConfigureCogDisplay(CogRecordDisplay disp)
  279. {
  280. disp.HorizontalScrollBar = false;
  281. disp.VerticalScrollBar = false;
  282. disp.AutoFit = true;
  283. disp.AutoFitWithGraphics = true;
  284. disp.BackColor = System.Drawing.SystemColors.ActiveCaption;
  285. }
  286. // ======================= 【新增】安全保存入口:解决闪退 =======================
  287. private void RequestSaveImage(ShowRender render, CogRecordDisplay disp)
  288. {
  289. if (render == null) return;
  290. if (disp == null) return;
  291. if (!render.IsSaveImage) return;
  292. // Original 模式不需要 CreateContentBitmap(你的 SaveImage 里 Original 只写 ICogImage)
  293. bool needRecordedBitmap =
  294. render.SaveImageModel == ProcedureSaveImageModel.Recorded ||
  295. render.SaveImageModel == ProcedureSaveImageModel.OriginalAndRecorded;
  296. _saveImageCts = new CancellationTokenSource();
  297. var token = _saveImageCts.Token;
  298. // 必须在控件的 UI 线程上执行 CreateContentBitmap
  299. disp.BeginInvoke(new Action(() =>
  300. {
  301. Bitmap clonedBitmap = null;
  302. try
  303. {
  304. token.ThrowIfCancellationRequested();
  305. if (needRecordedBitmap)
  306. {
  307. // UI线程创建
  308. Bitmap uiBitmap = (Bitmap)disp.CreateContentBitmap(
  309. Cognex.VisionPro.Display.CogDisplayContentBitmapConstants.Custom);
  310. // 立刻 Clone,后台线程只用 Clone(防止控件刷新/释放导致崩溃)
  311. clonedBitmap = (Bitmap)uiBitmap.Clone();
  312. // UI线程立刻释放
  313. uiBitmap.Dispose();
  314. }
  315. // 后台保存(限并发,防止GDI/内存瞬时飙升)
  316. Task.Run(async () =>
  317. {
  318. await _saveSemaphore.WaitAsync(token);
  319. try
  320. {
  321. token.ThrowIfCancellationRequested();
  322. SaveImage(
  323. render.SaveImageModel,
  324. render.SaveImagePathModel,
  325. render.SavePath,
  326. render.Image,
  327. clonedBitmap,
  328. render.Result,
  329. render.IsCompress
  330. );
  331. }
  332. catch (OperationCanceledException)
  333. {
  334. // 取消属于正常情况,不记录
  335. }
  336. catch (Exception ex)
  337. {
  338. LogHelper.WriteLogError("后台保存图像任务出错!", ex);
  339. }
  340. finally
  341. {
  342. // 确保释放 Clone 的 Bitmap,避免 GDI 泄漏导致闪退
  343. try { clonedBitmap?.Dispose(); } catch { }
  344. _saveSemaphore.Release();
  345. }
  346. }, token);
  347. }
  348. catch (OperationCanceledException)
  349. {
  350. try { clonedBitmap?.Dispose(); } catch { }
  351. }
  352. catch (Exception ex)
  353. {
  354. try { clonedBitmap?.Dispose(); } catch { }
  355. LogHelper.WriteLogError("RequestSaveImage(UI截图阶段) 出错!", ex);
  356. }
  357. }));
  358. }
  359. /// <summary>
  360. /// 保存图像(保持你原有逻辑不变,补充 Dispose 兜底,防止压缩分支不释放导致闪退)
  361. /// </summary>
  362. /// <param name="saveImageModel">要保存图像的模式</param>
  363. /// <param name="saveImagePathModel">图像保存路径类型</param>
  364. /// <param name="path">保存图像的文件夹路径</param>
  365. /// <param name="image">原图像</param>
  366. /// <param name="reimage">渲染图像</param>
  367. /// <param name="result">拍照结果</param>
  368. /// <param name="isCompress">是否压缩</param>
  369. //private void SaveImage(ProcedureSaveImageModel saveImageModel, ProcedureSaveImagePathModel saveImagePathModel, string path, ICogImage image, Bitmap reimage, bool result, bool isCompress, ImageParameters imgPara)
  370. private void SaveImage(ProcedureSaveImageModel saveImageModel,
  371. ProcedureSaveImagePathModel saveImagePathModel,
  372. string path,
  373. ICogImage image,
  374. Bitmap reimage,
  375. bool result,
  376. bool isCompress)
  377. {
  378. if (string.IsNullOrEmpty(path) || string.IsNullOrWhiteSpace(path) || !Path.IsPathRooted(path))
  379. {
  380. _eventAggregator.GetEvent<TaskMessageNotification>()
  381. .Publish(new Models.MessageStruct() { Message = "保存图像路径为无效的路径!", level = MessageLevel.Alarm });
  382. return;
  383. }
  384. Task.Run(() =>
  385. {
  386. try
  387. {
  388. DateTime now = DateTime.Now;
  389. if (saveImageModel == ProcedureSaveImageModel.Original)
  390. {
  391. string Originalpath = $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  392. if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_OkNG_Time)
  393. {
  394. Originalpath = result
  395. ? $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{now:yyyy-MM-dd HH_mm_ss}.bmp"
  396. : $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  397. }
  398. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ok_Time)
  399. {
  400. if (result)
  401. Originalpath = $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  402. else
  403. return;
  404. }
  405. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ng_Time)
  406. {
  407. if (!result)
  408. Originalpath = $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  409. else
  410. return;
  411. }
  412. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Formula_ProductSN_Time)
  413. {
  414. // 当前产品配方名(过滤非法字符)
  415. string formulaName = _productService.GetCurrentProduct().Name;
  416. foreach (char c in Path.GetInvalidFileNameChars())
  417. formulaName = formulaName.Replace(c, '_');
  418. // 产品SN(过滤非法字符)
  419. string productSN = _productService.CurrentProductCode;
  420. foreach (char c in Path.GetInvalidFileNameChars())
  421. productSN = productSN.Replace(c, '_');
  422. string resultStr = result ? "OK" : "NG";
  423. Originalpath = $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{productSN}-{now:yyyy-MM-dd HH_mm_ss}-{resultStr}.bmp";
  424. }
  425. if (!Directory.Exists(Path.GetDirectoryName(Originalpath)))
  426. Directory.CreateDirectory(Path.GetDirectoryName(Originalpath));
  427. using (CogImageFileBMP _cogImageFileTool = new CogImageFileBMP())
  428. {
  429. _cogImageFileTool.Open(Originalpath, CogImageFileModeConstants.Write);
  430. _cogImageFileTool.Append(image);
  431. _cogImageFileTool.Close();
  432. }
  433. }
  434. else if (saveImageModel == ProcedureSaveImageModel.Recorded)
  435. {
  436. string Recordedpath = $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  437. if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_OkNG_Time)
  438. {
  439. Recordedpath = result
  440. ? $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{now:yyyy-MM-dd HH_mm_ss}.bmp"
  441. : $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  442. }
  443. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ok_Time)
  444. {
  445. if (result)
  446. Recordedpath = $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  447. else
  448. return;
  449. }
  450. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ng_Time)
  451. {
  452. if (!result)
  453. Recordedpath = $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  454. else
  455. return;
  456. }
  457. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Formula_ProductSN_Time)
  458. {
  459. string formulaName = _productService.GetCurrentProduct().Name;
  460. foreach (char c in Path.GetInvalidFileNameChars())
  461. formulaName = formulaName.Replace(c, '_');
  462. string productSN = _productService.CurrentProductCode;
  463. foreach (char c in Path.GetInvalidFileNameChars())
  464. productSN = productSN.Replace(c, '_');
  465. string resultStr = result ? "OK" : "NG";
  466. Recordedpath = $"{path}\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{productSN}-{now:yyyy-MM-dd HH_mm_ss}-{resultStr}.bmp";
  467. }
  468. if (!Directory.Exists(Path.GetDirectoryName(Recordedpath)))
  469. Directory.CreateDirectory(Path.GetDirectoryName(Recordedpath));
  470. if (isCompress)
  471. {
  472. ImageHelper.CompressImage(reimage, Recordedpath, 100);
  473. }
  474. else
  475. {
  476. reimage.Save(Recordedpath);
  477. }
  478. }
  479. else if (saveImageModel == ProcedureSaveImageModel.OriginalAndRecorded)
  480. {
  481. string Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  482. string Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  483. if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_OkNG_Time)
  484. {
  485. if (result)
  486. {
  487. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  488. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  489. }
  490. else
  491. {
  492. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  493. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  494. }
  495. }
  496. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ok_Time)
  497. {
  498. if (result)
  499. {
  500. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  501. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  502. }
  503. else
  504. {
  505. return;
  506. }
  507. }
  508. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ng_Time)
  509. {
  510. if (!result)
  511. {
  512. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  513. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{now:yyyy-MM-dd HH_mm_ss}.bmp";
  514. }
  515. else
  516. {
  517. return;
  518. }
  519. }
  520. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Formula_ProductSN_Time)
  521. {
  522. string formulaName = _productService.GetCurrentProduct().Name;
  523. foreach (char c in Path.GetInvalidFileNameChars())
  524. formulaName = formulaName.Replace(c, '_');
  525. string productSN = _productService.CurrentProductCode;
  526. foreach (char c in Path.GetInvalidFileNameChars())
  527. productSN = productSN.Replace(c, '_');
  528. string resultStr = result ? "OK" : "NG";
  529. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{productSN}-{now:yyyy-MM-dd HH_mm_ss}-{resultStr}.bmp";
  530. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{productSN}-{now:yyyy-MM-dd HH_mm_ss}-{resultStr}.bmp";
  531. }
  532. if (!Directory.Exists(Path.GetDirectoryName(Originalpath)))
  533. Directory.CreateDirectory(Path.GetDirectoryName(Originalpath));
  534. using (CogImageFileBMP _cogImageFileTool = new CogImageFileBMP())
  535. {
  536. _cogImageFileTool.Open(Originalpath, CogImageFileModeConstants.Write);
  537. _cogImageFileTool.Append(image);
  538. _cogImageFileTool.Close();
  539. }
  540. if (!Directory.Exists(Path.GetDirectoryName(Recordedpath)))
  541. Directory.CreateDirectory(Path.GetDirectoryName(Recordedpath));
  542. if (isCompress)
  543. {
  544. ImageHelper.CompressImage(reimage, Recordedpath, 100);
  545. }
  546. else
  547. {
  548. reimage.Save(Recordedpath);
  549. }
  550. }
  551. }
  552. catch (Exception ex)
  553. {
  554. LogHelper.WriteLogError("保存流程运行结果的图片时出错!", ex);
  555. }
  556. finally
  557. {
  558. // ======================= 【关键修复】无论是否压缩都释放Bitmap =======================
  559. try { reimage?.Dispose(); } catch { }
  560. }
  561. });
  562. }
  563. /// <summary>
  564. /// 切换产品时清空主页显示(保持你原有逻辑)
  565. /// </summary>
  566. private void ProductChanged(ProductModel product)
  567. {
  568. this.gridRender.Dispatcher.BeginInvoke(new Action(() =>
  569. {
  570. foreach (var item in vmRenders)
  571. {
  572. try
  573. {
  574. if (item.Value?.Child is CogRecordDisplay disp)
  575. {
  576. if (disp.Tag != null)
  577. {
  578. disp.Image = null;
  579. disp.StaticGraphics.Clear();
  580. // 你原来 break 只清一次;这里保留你的行为
  581. break;
  582. }
  583. }
  584. }
  585. catch (Exception ex)
  586. {
  587. LogHelper.WriteLogError(Lang.切换产品清除主页图像时出错, ex);
  588. }
  589. }
  590. }));
  591. }
  592. private void OnDisplayDoubleClick(CogRecordDisplay disp)
  593. {
  594. if (disp == null) return;
  595. // 必须在 WinForms/ActiveX 线程截图
  596. disp.BeginInvoke(new Action(() =>
  597. {
  598. Bitmap bmp = null;
  599. try
  600. {
  601. if (disp.IsDisposed || !disp.IsHandleCreated)
  602. return;
  603. // 优先使用显示内容截图(带图形/record叠加)
  604. try
  605. {
  606. var uiBmp = (Bitmap)disp.CreateContentBitmap(
  607. Cognex.VisionPro.Display.CogDisplayContentBitmapConstants.Custom);
  608. bmp = (Bitmap)uiBmp.Clone();
  609. uiBmp.Dispose();
  610. }
  611. catch
  612. {
  613. // 如果 ActiveX 状态不允许截图,退化:尝试从 Image 转 Bitmap
  614. bmp = TryConvertCogImageToBitmap(disp.Image);
  615. }
  616. if (bmp == null) return;
  617. // 回到 WPF UI 线程弹窗
  618. this.Dispatcher.BeginInvoke(new Action(() =>
  619. {
  620. //var win = new ImagePreviewWindow(bmp, $"预览:{disp.Tag ?? "Display"}");
  621. //win.Owner = Window.GetWindow(this); // 让弹窗归属当前窗口
  622. //win.Show();
  623. }));
  624. }
  625. catch (Exception ex)
  626. {
  627. LogHelper.WriteLogError("OnDisplayDoubleClick 截图/弹窗出错", ex);
  628. }
  629. finally
  630. {
  631. // 这里 bmp 不要 Dispose,因为传给窗口了
  632. // 窗口关闭时会释放
  633. }
  634. }));
  635. }
  636. /// <summary>
  637. /// 将 VisionPro 的 ICogImage 尝试转换成 Bitmap(作为 CreateContentBitmap 失败时的兜底)
  638. /// </summary>
  639. private Bitmap TryConvertCogImageToBitmap(ICogImage cogImage)
  640. {
  641. try
  642. {
  643. if (cogImage == null) return null;
  644. // VisionPro 常用转换:CogImage8Grey / CogImage24PlanarColor / CogImage24PackedColor 等
  645. // 这里给通用兜底:用 CogImageFileBMP 落地到内存流,再读回 Bitmap
  646. using (var ms = new MemoryStream())
  647. {
  648. // 需要引用 Cognex.VisionPro.ImageFile
  649. using (var bmpFile = new CogImageFileBMP())
  650. {
  651. // CogImageFileBMP 只能写文件路径,不直接写流
  652. // 所以用临时文件方式最稳(如你不想落盘,可以另写转换器)
  653. var tmp = Path.Combine(Path.GetTempPath(), $"vp_preview_{Guid.NewGuid():N}.bmp");
  654. try
  655. {
  656. bmpFile.Open(tmp, CogImageFileModeConstants.Write);
  657. bmpFile.Append(cogImage);
  658. bmpFile.Close();
  659. var bitmap = (Bitmap)Bitmap.FromFile(tmp);
  660. return (Bitmap)bitmap.Clone();
  661. }
  662. finally
  663. {
  664. try { if (File.Exists(tmp)) File.Delete(tmp); } catch { }
  665. }
  666. }
  667. }
  668. }
  669. catch
  670. {
  671. return null;
  672. }
  673. }
  674. }
  675. }