ShowVisionRender.xaml.cs 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249
  1. using Cognex.VisionPro;
  2. using Cognex.VisionPro.ImageFile;
  3. using Cognex.VisionPro3D;
  4. using log4net.Core;
  5. using MahApps.Metro.Controls;
  6. using Prism.Events;
  7. using Prism.Ioc;
  8. using System;
  9. using System.Collections.Concurrent;
  10. using System.Collections.Generic;
  11. using System.Drawing;
  12. using System.IO;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. using System.Windows;
  16. using System.Windows.Controls;
  17. using System.Windows.Forms.Integration;
  18. using System.Windows.Interop;
  19. using TeamAAS_VP;
  20. using TeamAAS_VP.Core;
  21. using TeamAAS_VP.Enums;
  22. using TeamAAS_VP.Events;
  23. using TeamAAS_VP.Interfaces;
  24. using TeamAAS_VP.Models;
  25. using TeamAAS_VP.Resources.Languages;
  26. using TeamAAS_VP.ViewModels;
  27. using TeamAAS_VP.ViewModels.Home;
  28. //using static MaterialDesignThemes.Wtf.ToolBar;
  29. namespace TeamAAS_VP.Views.Home
  30. {
  31. /// <summary>
  32. /// Interaction logic for ShowVisionRender
  33. /// </summary>
  34. public partial class ShowVisionRender : UserControl
  35. {
  36. private const string Render2DKeySuffix = ":2D";
  37. private const string Render3DKeySuffix = ":3D";
  38. private static string Get2DKey(Guid id) => id.ToString() + Render2DKeySuffix;
  39. private static string Get3DKey(Guid id) => id.ToString() + Render3DKeySuffix;
  40. ShowVisionRenderViewModel viewModel;
  41. Dictionary<string, WindowsFormsHost> vmRenders;
  42. Dictionary<Guid, TextBlock> Titles;
  43. IEventAggregator _eventAggregator;
  44. private readonly IProductService _productService;
  45. private readonly Prism.Services.Dialogs.IDialogService _dialogService;
  46. private CancellationTokenSource _saveImageCts = new CancellationTokenSource();
  47. private readonly object _saveImageCtsLock = new object();
  48. private readonly SemaphoreSlim _saveSemaphore = new SemaphoreSlim(5, 5);
  49. private readonly ConcurrentQueue<ShowRender> _pendingRenderUpdates = new ConcurrentQueue<ShowRender>();
  50. private int _renderDrainScheduled;
  51. private List<Models.DelaySaveImage> delaySaveImages = new List<Models.DelaySaveImage>();
  52. /// <summary>
  53. /// 安全获取有效的 CancellationToken。如果当前 CTS 已释放则自动创建新的。
  54. /// </summary>
  55. private CancellationToken GetSafeSaveToken()
  56. {
  57. lock (_saveImageCtsLock)
  58. {
  59. try
  60. {
  61. var token = _saveImageCts.Token;
  62. return token;
  63. }
  64. catch (ObjectDisposedException)
  65. {
  66. _saveImageCts = new CancellationTokenSource();
  67. return _saveImageCts.Token;
  68. }
  69. }
  70. }
  71. public ShowVisionRender()
  72. {
  73. InitializeComponent();
  74. vmRenders = new Dictionary<string, WindowsFormsHost>();
  75. Titles = new Dictionary<Guid, TextBlock>();
  76. viewModel = DataContext as ShowVisionRenderViewModel;
  77. _eventAggregator = viewModel._eventAggregator;
  78. _productService = ((Prism.PrismApplicationBase)App.Current).Container.Resolve<IProductService>();
  79. // 新增:DialogService
  80. _dialogService = ((Prism.PrismApplicationBase)App.Current).Container.Resolve<Prism.Services.Dialogs.IDialogService>();
  81. viewModel.UpdateLayout += ViewModel_UpdateLayout;
  82. // 后台线程仅负责准备渲染数据;真正的 UI 应用合并到单个 Background 优先级批处理里,
  83. // 避免每张图都以 Normal 优先级抢占用户输入。
  84. _eventAggregator.GetEvent<RenderUpdateNotification>().Subscribe(
  85. EnqueueRenderUpdate,
  86. ThreadOption.BackgroundThread);
  87. _eventAggregator.GetEvent<ProductChangedNotification>().Subscribe(ProductChanged);
  88. _eventAggregator.GetEvent<DelaySaveImageNotification>().Subscribe(UpdateDelaySaveImage);
  89. }
  90. private bool GetView3DUse()
  91. {
  92. try
  93. {
  94. var configService = ((Prism.PrismApplicationBase)App.Current).Container.Resolve<IConfigService>();
  95. if(configService.GetSystemConfiguration().PickPointNum==0)
  96. {
  97. return false;
  98. }
  99. else
  100. {
  101. return true;
  102. }
  103. //return true;
  104. }
  105. catch
  106. {
  107. return false;
  108. }
  109. }
  110. private void EnqueueRenderUpdate(ShowRender render)
  111. {
  112. if (render == null)
  113. return;
  114. ICogImage imageCopy = null;
  115. try
  116. {
  117. imageCopy = render.Image?.CopyBase(CogImageCopyModeConstants.CopyPixels);
  118. }
  119. catch (Exception ex)
  120. {
  121. LogHelper.WriteLogError("深拷贝 ICogImage 失败", ex);
  122. }
  123. _pendingRenderUpdates.Enqueue(new ShowRender
  124. {
  125. Id = render.Id,
  126. DisplayName = render.DisplayName,
  127. Image = imageCopy,
  128. Graphic = render.Graphic,
  129. Record = render.Record,
  130. Result = render.Result,
  131. IsShow = render.IsShow,
  132. IsSaveImage = render.IsSaveImage,
  133. SaveImageModel = render.SaveImageModel,
  134. SaveImagePathModel = render.SaveImagePathModel,
  135. SavePath = render.SavePath,
  136. IsCompress = render.IsCompress,
  137. ProceductName = render.ProceductName,
  138. ImageFileName = render.ImageFileName,
  139. Is3DModel = render.Is3DModel,
  140. RangeImage = render.RangeImage,
  141. GreyImage = render.GreyImage,
  142. IsDelaySaveImage = render.IsDelaySaveImage,
  143. });
  144. if (_pendingRenderUpdates.Count > 8)
  145. {
  146. LogHelper.WriteLogInfo($"[UI-RENDER] Pending render queue depth={_pendingRenderUpdates.Count}");
  147. }
  148. ScheduleRenderDrain();
  149. }
  150. private void ScheduleRenderDrain()
  151. {
  152. if (Interlocked.CompareExchange(ref _renderDrainScheduled, 1, 0) != 0)
  153. return;
  154. _ = this.gridRender.Dispatcher.InvokeAsync(async () =>
  155. {
  156. try
  157. {
  158. int processed = 0;
  159. while (_pendingRenderUpdates.TryDequeue(out var render))
  160. {
  161. ApplyRenderUpdate(render);
  162. processed++;
  163. if (processed % 2 == 0 && !_pendingRenderUpdates.IsEmpty)
  164. await System.Windows.Threading.Dispatcher.Yield(System.Windows.Threading.DispatcherPriority.Input);
  165. }
  166. }
  167. finally
  168. {
  169. Interlocked.Exchange(ref _renderDrainScheduled, 0);
  170. if (!_pendingRenderUpdates.IsEmpty)
  171. ScheduleRenderDrain();
  172. }
  173. }, System.Windows.Threading.DispatcherPriority.Background);
  174. }
  175. private void ApplyRenderUpdate(ShowRender render)
  176. {
  177. if (render.Is3DModel == true && GetView3DUse() == true)
  178. {
  179. if (!vmRenders.TryGetValue(Get3DKey(render.Id), out var host3D))
  180. return;
  181. var display3D = host3D.Child as Cognex.VisionPro3D.Cog3DDisplayV2WF;
  182. if (display3D == null)
  183. return;
  184. try
  185. {
  186. display3D.Clear();
  187. if (render.RangeImage == null || render.GreyImage == null)
  188. return;
  189. var rImgG = new Cog3DRangeImageGraphic(render.RangeImage, render.GreyImage);
  190. display3D.Add(rImgG, "__default");
  191. display3D.FitView();
  192. }
  193. catch (System.Windows.Forms.AxHost.InvalidActiveXStateException)
  194. {
  195. }
  196. }
  197. if (!vmRenders.TryGetValue(Get2DKey(render.Id), out var host2D))
  198. return;
  199. var recordDisplay = host2D.Child as Cognex.VisionPro.CogRecordDisplay;
  200. if (recordDisplay == null)
  201. return;
  202. try
  203. {
  204. recordDisplay.Record = null;
  205. recordDisplay.Image = render.Image;
  206. recordDisplay.StaticGraphics.Clear();
  207. if (render.Graphic != null)
  208. recordDisplay.StaticGraphics.AddList(render.Graphic, "");
  209. if (render.Record != null)
  210. recordDisplay.Record = render.Record;
  211. if (render.IsSaveImage)
  212. {
  213. RequestSaveImage(render, recordDisplay);
  214. }
  215. }
  216. catch (System.Windows.Forms.AxHost.InvalidActiveXStateException)
  217. {
  218. }
  219. }
  220. private void AddRenderPair(Guid id, string displayName)
  221. {
  222. // 2D
  223. var display2D = new Cognex.VisionPro.CogRecordDisplay() { Tag = displayName };
  224. display2D.HandleCreated += (s, e) =>
  225. {
  226. try
  227. {
  228. display2D.HorizontalScrollBar = false;
  229. display2D.VerticalScrollBar = false;
  230. display2D.AutoFit = true;
  231. display2D.BackColor = System.Drawing.SystemColors.ActiveCaption;
  232. }
  233. catch (System.Windows.Forms.AxHost.InvalidActiveXStateException)
  234. {
  235. }
  236. };
  237. // 绑定双击:弹出预览窗口
  238. display2D.DoubleClick += (s, e) => OnDisplayDoubleClick(display2D);
  239. vmRenders.Add(Get2DKey(id), new WindowsFormsHost() { Tag = displayName, Child = display2D });
  240. // 3D:按配置创建
  241. if (!GetView3DUse())
  242. return;
  243. // 3D
  244. var display3D = new Cognex.VisionPro3D.Cog3DDisplayV2WF() { Tag = displayName };
  245. display3D.HandleCreated += (s, e) =>
  246. {
  247. try
  248. {
  249. display3D.BackColor = System.Drawing.SystemColors.ActiveCaption;
  250. }
  251. catch (System.Windows.Forms.AxHost.InvalidActiveXStateException)
  252. {
  253. }
  254. };
  255. vmRenders.Add(Get3DKey(id), new WindowsFormsHost() { Tag = displayName, Child = display3D });
  256. }
  257. private Grid CreateCellGrid(Guid id, string displayName, System.Windows.Media.FontFamily font)
  258. {
  259. var is3DEnabled = GetView3DUse();
  260. var cell = new Grid();
  261. cell.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
  262. cell.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  263. var title = new TextBlock()
  264. {
  265. Text = displayName,
  266. HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
  267. FontWeight = FontWeights.Bold,
  268. FontFamily = font,
  269. FontSize = 12,
  270. Foreground = System.Windows.Media.Brushes.Black
  271. };
  272. Titles[id] = title;
  273. var content = new Grid();
  274. if(is3DEnabled)
  275. {
  276. content.ColumnDefinitions.Add(new ColumnDefinition()); // 2D
  277. content.ColumnDefinitions.Add(new ColumnDefinition()); // 3D
  278. content.Children.Add(vmRenders[Get2DKey(id)]);
  279. content.Children.Add(vmRenders[Get3DKey(id)]);
  280. Grid.SetColumn(vmRenders[Get2DKey(id)], 0);
  281. Grid.SetColumn(vmRenders[Get3DKey(id)], 1);
  282. }
  283. else
  284. {
  285. content.ColumnDefinitions.Add(new ColumnDefinition()); // 仅 2D
  286. content.Children.Add(vmRenders[Get2DKey(id)]);
  287. Grid.SetColumn(vmRenders[Get2DKey(id)], 0);
  288. }
  289. cell.Children.Add(title);
  290. cell.Children.Add(content);
  291. Grid.SetRow(title, 0);
  292. Grid.SetRow(content, 1);
  293. return cell;
  294. }
  295. Grid grid1 = new Grid();
  296. Grid grid2 = new Grid();
  297. Grid grid3 = new Grid();
  298. Grid grid4 = new Grid();
  299. Grid grid5 = new Grid();
  300. Grid grid6 = new Grid();
  301. Grid grid7 = new Grid();
  302. Grid grid8 = new Grid();
  303. Grid grid9 = new Grid();
  304. Grid grid10 = new Grid();
  305. Grid grid11 = new Grid();
  306. Grid grid12 = new Grid();
  307. Grid grid13 = new Grid();
  308. Grid grid14 = new Grid();
  309. Grid grid15 = new Grid();
  310. Grid grid16 = new Grid();
  311. Grid grid17 = new Grid();
  312. Grid grid18 = new Grid();
  313. Grid grid19 = new Grid();
  314. Grid grid20 = new Grid();
  315. private void ViewModel_UpdateLayout(System.Collections.ObjectModel.ObservableCollection<Models.ShowRender> obj)
  316. {
  317. System.Windows.Media.FontFamily font = Application.Current.Resources["DefaultFont"] as System.Windows.Media.FontFamily;
  318. if (vmRenders.Count > 0)
  319. {
  320. foreach (var render in vmRenders)
  321. {
  322. render.Value.Dispose();
  323. }
  324. }
  325. vmRenders.Clear();
  326. gridRender.Children.Clear();
  327. gridRender.RowDefinitions.Clear();
  328. gridRender.ColumnDefinitions.Clear();
  329. Titles.Clear();
  330. if (obj == null || obj.Count == 0)
  331. return;
  332. // 先创建所有相机的 2D/3D 对
  333. for (int i = 0; i < obj.Count; i++)
  334. AddRenderPair(obj[i].Id, obj[i].DisplayName);
  335. switch (obj.Count)
  336. {
  337. case 1:
  338. {
  339. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  340. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  341. var cell = CreateCellGrid(obj[0].Id, obj[0].DisplayName, font);
  342. gridRender.Children.Add(cell);
  343. Grid.SetRow(cell, 0);
  344. Grid.SetColumn(cell, 0);
  345. gridRender.ShowGridLines = false;
  346. break;
  347. }
  348. case 2:
  349. {
  350. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  351. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  352. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  353. gridRender.ShowGridLines = true;
  354. var cell0 = CreateCellGrid(obj[0].Id, obj[0].DisplayName, font);
  355. var cell1 = CreateCellGrid(obj[1].Id, obj[1].DisplayName, font);
  356. gridRender.Children.Add(cell0);
  357. gridRender.Children.Add(cell1);
  358. Grid.SetRow(cell0, 0);
  359. Grid.SetColumn(cell0, 0);
  360. Grid.SetRow(cell1, 0);
  361. Grid.SetColumn(cell1, 1);
  362. break;
  363. }
  364. case 3:
  365. {
  366. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  367. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  368. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  369. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  370. gridRender.ShowGridLines = true;
  371. var cell0 = CreateCellGrid(obj[0].Id, obj[0].DisplayName, font);
  372. var cell1 = CreateCellGrid(obj[1].Id, obj[1].DisplayName, font);
  373. var cell2 = CreateCellGrid(obj[2].Id, obj[2].DisplayName, font);
  374. gridRender.Children.Add(cell0);
  375. gridRender.Children.Add(cell1);
  376. gridRender.Children.Add(cell2);
  377. Grid.SetRow(cell0, 0);
  378. Grid.SetColumn(cell0, 0);
  379. Grid.SetRow(cell1, 0);
  380. Grid.SetColumn(cell1, 1);
  381. Grid.SetRow(cell2, 1);
  382. Grid.SetColumn(cell2, 0);
  383. Grid.SetColumnSpan(cell2, 2);
  384. break;
  385. }
  386. case 4:
  387. {
  388. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  389. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  390. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  391. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  392. gridRender.ShowGridLines = true;
  393. for (int i = 0; i < 4; i++)
  394. {
  395. var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font);
  396. gridRender.Children.Add(cell);
  397. Grid.SetRow(cell, i / 2);
  398. Grid.SetColumn(cell, i % 2);
  399. }
  400. break;
  401. }
  402. case 5:
  403. {
  404. // 你的原布局:上面2个,下面3个(通过嵌套grid6)
  405. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  406. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  407. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  408. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  409. gridRender.ShowGridLines = true;
  410. var top0 = CreateCellGrid(obj[0].Id, obj[0].DisplayName, font);
  411. var top1 = CreateCellGrid(obj[1].Id, obj[1].DisplayName, font);
  412. var bottom = new Grid();
  413. bottom.ColumnDefinitions.Add(new ColumnDefinition());
  414. bottom.ColumnDefinitions.Add(new ColumnDefinition());
  415. bottom.ColumnDefinitions.Add(new ColumnDefinition());
  416. var b0 = CreateCellGrid(obj[2].Id, obj[2].DisplayName, font);
  417. var b1 = CreateCellGrid(obj[3].Id, obj[3].DisplayName, font);
  418. var b2 = CreateCellGrid(obj[4].Id, obj[4].DisplayName, font);
  419. bottom.Children.Add(b0);
  420. bottom.Children.Add(b1);
  421. bottom.Children.Add(b2);
  422. Grid.SetColumn(b0, 0);
  423. Grid.SetColumn(b1, 1);
  424. Grid.SetColumn(b2, 2);
  425. gridRender.Children.Add(top0);
  426. gridRender.Children.Add(top1);
  427. gridRender.Children.Add(bottom);
  428. Grid.SetRow(top0, 0);
  429. Grid.SetColumn(top0, 0);
  430. Grid.SetRow(top1, 0);
  431. Grid.SetColumn(top1, 1);
  432. Grid.SetRow(bottom, 1);
  433. Grid.SetColumn(bottom, 0);
  434. Grid.SetColumnSpan(bottom, 2);
  435. break;
  436. }
  437. case 6:
  438. {
  439. // 你的原布局:2行3列
  440. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  441. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  442. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  443. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  444. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  445. gridRender.ShowGridLines = true;
  446. for (int i = 0; i < 6; i++)
  447. {
  448. var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font);
  449. gridRender.Children.Add(cell);
  450. Grid.SetRow(cell, i / 3);
  451. Grid.SetColumn(cell, i % 3);
  452. }
  453. break;
  454. }
  455. case 7:
  456. {
  457. // 你的原布局:第一行4个,第二行3个(但原来只放到 col 0/1/2)
  458. // 这里保持原样:第一行4列,第二行3列(第4列留空)
  459. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  460. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  461. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  462. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  463. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  464. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  465. gridRender.ShowGridLines = true;
  466. for (int i = 0; i < 7; i++)
  467. {
  468. var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font);
  469. gridRender.Children.Add(cell);
  470. if (i < 4)
  471. {
  472. Grid.SetRow(cell, 0);
  473. Grid.SetColumn(cell, i);
  474. }
  475. else
  476. {
  477. Grid.SetRow(cell, 1);
  478. Grid.SetColumn(cell, i - 4);
  479. }
  480. }
  481. break;
  482. }
  483. case 8:
  484. {
  485. // 2行4列
  486. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  487. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  488. for (int i = 0; i < 4; i++)
  489. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  490. gridRender.ShowGridLines = true;
  491. for (int i = 0; i < 8; i++)
  492. {
  493. var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font);
  494. gridRender.Children.Add(cell);
  495. Grid.SetRow(cell, i / 4);
  496. Grid.SetColumn(cell, i % 4);
  497. }
  498. break;
  499. }
  500. case 9:
  501. {
  502. // 3行3列
  503. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  504. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  505. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  506. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  507. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  508. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  509. gridRender.ShowGridLines = true;
  510. for (int i = 0; i < 9; i++)
  511. {
  512. var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font);
  513. gridRender.Children.Add(cell);
  514. Grid.SetRow(cell, i / 3);
  515. Grid.SetColumn(cell, i % 3);
  516. }
  517. break;
  518. }
  519. case 12:
  520. {
  521. // 3行4列
  522. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  523. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  524. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  525. for (int i = 0; i < 4; i++)
  526. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  527. gridRender.ShowGridLines = true;
  528. for (int i = 0; i < 12; i++)
  529. {
  530. var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font);
  531. gridRender.Children.Add(cell);
  532. Grid.SetRow(cell, i / 4);
  533. Grid.SetColumn(cell, i % 4);
  534. }
  535. break;
  536. }
  537. case 16:
  538. {
  539. // 4行4列
  540. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  541. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  542. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  543. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  544. for (int i = 0; i < 4; i++)
  545. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  546. gridRender.ShowGridLines = true;
  547. for (int i = 0; i < 16; i++)
  548. {
  549. var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font);
  550. gridRender.Children.Add(cell);
  551. Grid.SetRow(cell, i / 4);
  552. Grid.SetColumn(cell, i % 4);
  553. }
  554. break;
  555. }
  556. case 20:
  557. {
  558. // 5行4列
  559. for (int r = 0; r < 5; r++)
  560. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  561. for (int c = 0; c < 4; c++)
  562. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  563. gridRender.ShowGridLines = true;
  564. for (int i = 0; i < 20; i++)
  565. {
  566. var cell = CreateCellGrid(obj[i].Id, obj[i].DisplayName, font);
  567. gridRender.Children.Add(cell);
  568. Grid.SetRow(cell, i / 4);
  569. Grid.SetColumn(cell, i % 4);
  570. }
  571. break;
  572. }
  573. default:
  574. {
  575. // 未覆盖的数量:先用简单的 1列多行兜底(避免空白)
  576. gridRender.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  577. gridRender.ColumnDefinitions.Add(new ColumnDefinition());
  578. gridRender.ShowGridLines = false;
  579. var cell = CreateCellGrid(obj[0].Id, obj[0].DisplayName, font);
  580. gridRender.Children.Add(cell);
  581. Grid.SetRow(cell, 0);
  582. Grid.SetColumn(cell, 0);
  583. break;
  584. }
  585. }
  586. }
  587. /// <summary>
  588. /// 请求保存图像(在 WinForms 消息循环线程截图,然后派发到后台线程保存)
  589. /// 使用 SemaphoreSlim 控制并发,支持 CancellationToken 取消
  590. /// </summary>
  591. private void RequestSaveImage(ShowRender render, Cognex.VisionPro.CogRecordDisplay disp)
  592. {
  593. if (render == null) return;
  594. if (disp == null) return;
  595. if (!render.IsSaveImage)
  596. {
  597. LogHelper.WriteLogInfo($"[SAVE-IMAGE] 跳过保存: IsSaveImage=false, Id={render.Id}, DisplayName={render.DisplayName}");
  598. return;
  599. }
  600. if (!render.IsDelaySaveImage && string.IsNullOrEmpty(_productService.CurrentProductCode))
  601. {
  602. _eventAggregator.GetEvent<TaskMessageNotification>()
  603. .Publish(new MessageStruct()
  604. {
  605. Message = "当前产品SN不存在,无法保存图片!",
  606. level = MessageLevel.Alarm
  607. });
  608. return;
  609. }
  610. //LogHelper.WriteLogInfo($"[SAVE-IMAGE] 开始保存流程: Id={render.Id}, SaveModel={render.SaveImageModel}, SavePath={render.SavePath}, IsDelay={render.IsDelaySaveImage}, ProductCode={_productService.CurrentProductCode}");
  611. bool needRecordedBitmap =
  612. render.SaveImageModel == ProcedureSaveImageModel.Recorded ||
  613. render.SaveImageModel == ProcedureSaveImageModel.OriginalAndRecorded;
  614. var token = GetSafeSaveToken();
  615. disp.BeginInvoke(new Action(() =>
  616. {
  617. Bitmap clonedBitmap = null;
  618. try
  619. {
  620. if (token.IsCancellationRequested) return;
  621. if (needRecordedBitmap)
  622. {
  623. if (disp.IsDisposed || !disp.IsHandleCreated) return;
  624. Bitmap uiBitmap = (Bitmap)disp.CreateContentBitmap(
  625. Cognex.VisionPro.Display.CogDisplayContentBitmapConstants.Custom);
  626. clonedBitmap = (Bitmap)uiBitmap.Clone();
  627. uiBitmap.Dispose();
  628. }
  629. var capturedBitmap = clonedBitmap;
  630. var capturedRender = render;
  631. Task.Run(async () =>
  632. {
  633. await _saveSemaphore.WaitAsync(token);
  634. try
  635. {
  636. if (token.IsCancellationRequested) return;
  637. if (capturedRender.IsDelaySaveImage)
  638. {
  639. // 延迟保存:存入列表,等 DelaySaveImageNotification 时统一写入
  640. ICogImage imageDeepCopy = null;
  641. try
  642. {
  643. imageDeepCopy = capturedRender.Image?
  644. .CopyBase(CogImageCopyModeConstants.CopyPixels);
  645. }
  646. catch (Exception ex)
  647. {
  648. LogHelper.WriteLogError("延迟保存深拷贝 ICogImage 失败", ex);
  649. }
  650. var delayItem = new Models.DelaySaveImage()
  651. {
  652. SaveImageModel = capturedRender.SaveImageModel,
  653. SaveImagePathModel = capturedRender.SaveImagePathModel,
  654. SavePath = capturedRender.SavePath,
  655. Image = imageDeepCopy,
  656. clonedBitmap = capturedBitmap != null
  657. ? (Bitmap)capturedBitmap.Clone() : null,
  658. Result = capturedRender.Result,
  659. IsCompress = capturedRender.IsCompress,
  660. ImageFileName = capturedRender.ImageFileName,
  661. Is3DModel = capturedRender.Is3DModel,
  662. };
  663. lock (delaySaveImages)
  664. {
  665. delaySaveImages.Add(delayItem);
  666. }
  667. }
  668. else
  669. {
  670. if (string.IsNullOrEmpty(_productService.CurrentProductCode))
  671. {
  672. _eventAggregator.GetEvent<TaskMessageNotification>()
  673. .Publish(new MessageStruct()
  674. {
  675. Message = "当前产品SN不存在,无法保存图片!",
  676. level = MessageLevel.Alarm
  677. });
  678. return;
  679. }
  680. LogHelper.WriteLogInfo("开始异步保存图像");
  681. await SaveImageAsync(
  682. capturedRender.SaveImageModel,
  683. capturedRender.SaveImagePathModel,
  684. capturedRender.SavePath,
  685. capturedRender.Image,
  686. capturedBitmap,
  687. capturedRender.Result,
  688. capturedRender.IsCompress,
  689. capturedRender.ImageFileName,
  690. capturedRender.Is3DModel,
  691. token
  692. );
  693. }
  694. }
  695. catch (OperationCanceledException) { }
  696. catch (Exception ex)
  697. {
  698. LogHelper.WriteLogError("后台保存图像任务出错!", ex);
  699. }
  700. finally
  701. {
  702. try { capturedBitmap?.Dispose(); } catch { }
  703. _saveSemaphore.Release();
  704. }
  705. }, token);
  706. }
  707. catch (Exception ex)
  708. {
  709. LogHelper.WriteLogError("RequestSaveImage(UI截图阶段) 出错!", ex);
  710. try { clonedBitmap?.Dispose(); } catch { }
  711. }
  712. }));
  713. }
  714. /// <summary>
  715. /// 异步保存图像(调用者已在后台线程,不再嵌套 Task.Run;通过 CancellationToken 支持取消)
  716. /// </summary>
  717. private async Task SaveImageAsync(
  718. ProcedureSaveImageModel saveImageModel,
  719. ProcedureSaveImagePathModel saveImagePathModel,
  720. string path, ICogImage image, Bitmap reimage,
  721. bool result, bool isCompress, string imageFileName,
  722. bool is3Dmodel, CancellationToken cancellationToken)
  723. {
  724. if (string.IsNullOrEmpty(path) || string.IsNullOrWhiteSpace(path) || !Path.IsPathRooted(path))
  725. {
  726. LogHelper.WriteLogInfo($"[SAVE-IMAGE] 保存失败:路径无效 path=\"{path}\"");
  727. _eventAggregator.GetEvent<TaskMessageNotification>()
  728. .Publish(new Models.MessageStruct() { Message = "保存图像路径为无效的路径!", level = MessageLevel.Alarm });
  729. return;
  730. }
  731. //LogHelper.WriteLogInfo($"[SAVE-IMAGE] SaveImageAsync 开始: path={path}, model={saveImageModel}, pathModel={saveImagePathModel}, result={result}, is3D={is3Dmodel}, fileName={imageFileName}");
  732. try
  733. {
  734. await Task.CompletedTask; // 消除 CS1998:调用者已在后台线程,下方所有 I/O 为同步执行
  735. cancellationToken.ThrowIfCancellationRequested();
  736. DateTime now = DateTime.Now;
  737. //图片名
  738. string imgName = now.ToString("yyyy-MM-dd HH_mm_ss");
  739. if (!string.IsNullOrEmpty(imageFileName))
  740. {
  741. imgName = imageFileName;
  742. }
  743. if (saveImageModel == ProcedureSaveImageModel.Original)
  744. {
  745. string Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.bmp";
  746. if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_OkNG_Time)
  747. {
  748. Originalpath = result
  749. ? $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp"
  750. : $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
  751. }
  752. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ok_Time)
  753. {
  754. if (result)
  755. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
  756. else
  757. return;
  758. }
  759. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ng_Time)
  760. {
  761. if (!result)
  762. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
  763. else
  764. return;
  765. }
  766. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Formula_ProductSN_Time)
  767. {
  768. // 当前产品配方名(过滤非法字符)
  769. string formulaName = _productService.GetCurrentProduct().Name;
  770. foreach (char c in Path.GetInvalidFileNameChars())
  771. formulaName = formulaName.Replace(c, '_');
  772. // 产品SN(过滤非法字符)
  773. string productSN = _productService.CurrentProductCode;
  774. if (productSN != null)
  775. {
  776. foreach (char c in Path.GetInvalidFileNameChars())
  777. productSN = productSN.Replace(c, '_');
  778. }
  779. string resultStr = result ? "OK" : "NG";
  780. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.bmp";
  781. }
  782. cancellationToken.ThrowIfCancellationRequested();
  783. if (!Directory.Exists(Path.GetDirectoryName(Originalpath)))
  784. Directory.CreateDirectory(Path.GetDirectoryName(Originalpath));
  785. using (CogImageFileBMP _cogImageFileTool = new CogImageFileBMP())
  786. {
  787. _cogImageFileTool.Open(Originalpath, CogImageFileModeConstants.Write);
  788. _cogImageFileTool.Append(image);
  789. _cogImageFileTool.Close();
  790. }
  791. }
  792. else if (saveImageModel == ProcedureSaveImageModel.Recorded)
  793. {
  794. string Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.bmp";
  795. if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_OkNG_Time)
  796. {
  797. Recordedpath = result
  798. ? $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp"
  799. : $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
  800. }
  801. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ok_Time)
  802. {
  803. if (result)
  804. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
  805. else
  806. return;
  807. }
  808. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ng_Time)
  809. {
  810. if (!result)
  811. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
  812. else
  813. return;
  814. }
  815. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Formula_ProductSN_Time)
  816. {
  817. string formulaName = _productService.GetCurrentProduct().Name;
  818. foreach (char c in Path.GetInvalidFileNameChars())
  819. formulaName = formulaName.Replace(c, '_');
  820. string productSN = _productService.CurrentProductCode;
  821. foreach (char c in Path.GetInvalidFileNameChars())
  822. productSN = productSN.Replace(c, '_');
  823. string resultStr = result ? "OK" : "NG";
  824. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.bmp";
  825. }
  826. cancellationToken.ThrowIfCancellationRequested();
  827. if (!Directory.Exists(Path.GetDirectoryName(Recordedpath)))
  828. Directory.CreateDirectory(Path.GetDirectoryName(Recordedpath));
  829. if (isCompress)
  830. {
  831. ImageHelper.CompressImage(reimage, Recordedpath, 20);
  832. }
  833. else
  834. {
  835. reimage.Save(Recordedpath);
  836. }
  837. }
  838. else if (saveImageModel == ProcedureSaveImageModel.OriginalAndRecorded)
  839. {
  840. string Originalpath = "";
  841. if (is3Dmodel == true)
  842. {
  843. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.idb";
  844. }
  845. else
  846. {
  847. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.bmp";
  848. }
  849. string Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{imgName}.bmp";
  850. if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_OkNG_Time)
  851. {
  852. if (result)
  853. {
  854. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
  855. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
  856. }
  857. else
  858. {
  859. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
  860. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
  861. }
  862. }
  863. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ok_Time)
  864. {
  865. if (result)
  866. {
  867. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
  868. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\OK\\{imgName}.bmp";
  869. }
  870. else
  871. {
  872. return;
  873. }
  874. }
  875. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Ng_Time)
  876. {
  877. if (!result)
  878. {
  879. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
  880. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\NG\\{imgName}.bmp";
  881. }
  882. else
  883. {
  884. return;
  885. }
  886. }
  887. else if (saveImagePathModel == ProcedureSaveImagePathModel.Month_Day_Formula_ProductSN_Time)
  888. {
  889. string formulaName = _productService.GetCurrentProduct().Name;
  890. foreach (char c in Path.GetInvalidFileNameChars())
  891. formulaName = formulaName.Replace(c, '_');
  892. string productSN = _productService.CurrentProductCode;
  893. if (productSN != null)
  894. {
  895. foreach (char c in Path.GetInvalidFileNameChars())
  896. productSN = productSN.Replace(c, '_');
  897. }
  898. string resultStr = result ? "OK" : "NG";
  899. if (is3Dmodel == true)
  900. {
  901. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.idb";
  902. }
  903. else
  904. {
  905. Originalpath = $"{path}\\Original\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.bmp";
  906. }
  907. Recordedpath = $"{path}\\Recored\\{now:yyyy-MM}\\{now:MM-dd}\\{formulaName}\\{productSN}\\{resultStr}\\{imgName}.bmp";
  908. }
  909. cancellationToken.ThrowIfCancellationRequested();
  910. if (!Directory.Exists(Path.GetDirectoryName(Originalpath)))
  911. Directory.CreateDirectory(Path.GetDirectoryName(Originalpath));
  912. using (CogImageFile _cogImageFileTool = new CogImageFile())
  913. {
  914. _cogImageFileTool.Open(Originalpath, CogImageFileModeConstants.Write);
  915. _cogImageFileTool.Append(image);
  916. _cogImageFileTool.Close();
  917. }
  918. cancellationToken.ThrowIfCancellationRequested();
  919. if (!Directory.Exists(Path.GetDirectoryName(Recordedpath)))
  920. Directory.CreateDirectory(Path.GetDirectoryName(Recordedpath));
  921. if (isCompress)
  922. {
  923. ImageHelper.CompressImage(reimage, Recordedpath, 20);
  924. }
  925. else
  926. {
  927. reimage.Save(Recordedpath);
  928. }
  929. }
  930. }
  931. catch (OperationCanceledException) { }
  932. catch (Exception ex)
  933. {
  934. LogHelper.WriteLogError($"[SAVE-IMAGE] 保存流程运行结果的图片时出错!path={path}", ex);
  935. }
  936. finally
  937. {
  938. try { reimage?.Dispose(); } catch { }
  939. }
  940. }
  941. /// <summary>
  942. /// 延迟保存图像处理:isSave=true 时批量保存所有待保存项;false 时丢弃并清理
  943. /// </summary>
  944. private async void UpdateDelaySaveImage(bool isSave)
  945. {
  946. if (!isSave)
  947. {
  948. lock (delaySaveImages)
  949. {
  950. foreach (var item in delaySaveImages)
  951. {
  952. try { item.clonedBitmap?.Dispose(); } catch { }
  953. try { item.Image = null; } catch { }
  954. }
  955. delaySaveImages.Clear();
  956. }
  957. return;
  958. }
  959. List<Models.DelaySaveImage> itemsToSave;
  960. lock (delaySaveImages)
  961. {
  962. itemsToSave = new List<Models.DelaySaveImage>(delaySaveImages);
  963. delaySaveImages.Clear();
  964. }
  965. var token = GetSafeSaveToken();
  966. foreach (var item in itemsToSave)
  967. {
  968. try
  969. {
  970. if (token.IsCancellationRequested) break;
  971. if (string.IsNullOrEmpty(_productService.CurrentProductCode))
  972. {
  973. _eventAggregator.GetEvent<TaskMessageNotification>()
  974. .Publish(new MessageStruct()
  975. {
  976. Message = "当前产品SN不存在,无法保存图片!",
  977. level = MessageLevel.Alarm
  978. });
  979. break;
  980. }
  981. LogHelper.WriteLogInfo("开始异步保存延迟图像");
  982. await SaveImageAsync(
  983. item.SaveImageModel,
  984. item.SaveImagePathModel,
  985. item.SavePath,
  986. item.Image,
  987. item.clonedBitmap,
  988. item.Result,
  989. item.IsCompress,
  990. item.ImageFileName,
  991. item.Is3DModel,
  992. token
  993. );
  994. }
  995. catch (OperationCanceledException) { break; }
  996. catch (Exception ex)
  997. {
  998. LogHelper.WriteLogError("延迟保存图像任务出错!", ex);
  999. }
  1000. finally
  1001. {
  1002. try { item.clonedBitmap?.Dispose(); } catch { }
  1003. try { item.Image = null; } catch { }
  1004. }
  1005. }
  1006. }
  1007. private void ShowVisionRender_Unloaded(object sender, RoutedEventArgs e)
  1008. {
  1009. try { _saveImageCts.Cancel(); _saveImageCts.Dispose(); } catch { }
  1010. // 释放延迟保存列表中的 Bitmap 资源
  1011. lock (delaySaveImages)
  1012. {
  1013. foreach (var item in delaySaveImages)
  1014. {
  1015. try { item.clonedBitmap?.Dispose(); } catch { }
  1016. try { item.Image = null; } catch { }
  1017. }
  1018. delaySaveImages.Clear();
  1019. }
  1020. }
  1021. private void ProductChanged(ProductModel product)
  1022. {
  1023. this.gridRender.Dispatcher.BeginInvoke(new Action(() =>
  1024. {
  1025. var is3DEnabled = GetView3DUse();
  1026. foreach (var item in vmRenders)
  1027. {
  1028. try
  1029. {
  1030. // 2D:永远清理
  1031. var display2D = item.Value.Child as Cognex.VisionPro.CogRecordDisplay;
  1032. if (display2D != null)
  1033. {
  1034. display2D.Record = null;
  1035. display2D.Image = null;
  1036. display2D.StaticGraphics.Clear();
  1037. continue;
  1038. }
  1039. // 3D:仅在启用 3D 时清理
  1040. if (!is3DEnabled)
  1041. continue;
  1042. var display3D = item.Value.Child as Cognex.VisionPro3D.Cog3DDisplayV2WF;
  1043. if (display3D != null)
  1044. {
  1045. display3D.Clear();
  1046. }
  1047. }
  1048. catch (System.Windows.Forms.AxHost.InvalidActiveXStateException)
  1049. {
  1050. // 控件句柄/ActiveX 未就绪时忽略
  1051. }
  1052. catch (Exception ex)
  1053. {
  1054. LogHelper.WriteLogError(Lang.切换产品清除主页图像时出错, ex);
  1055. }
  1056. }
  1057. }));
  1058. }
  1059. private void OnDisplayDoubleClick(CogRecordDisplay disp)
  1060. {
  1061. if (disp == null)
  1062. return;
  1063. try
  1064. {
  1065. // 直接把当前 CogRecordDisplay 传给对话框 VM(VM 内部读取 Image/Record)
  1066. var parameters = new Prism.Services.Dialogs.DialogParameters
  1067. {
  1068. { "Cog", disp }
  1069. };
  1070. // Prism Dialog 必须在 WPF UI 线程调用
  1071. Dispatcher.BeginInvoke(new Action(() =>
  1072. {
  1073. try
  1074. {
  1075. _dialogService.ShowDialog("ImagePreviewWindow", parameters, r => { });
  1076. }
  1077. catch (Exception ex)
  1078. {
  1079. LogHelper.WriteLogError("打开 ImagePreviewWindow 失败", ex);
  1080. }
  1081. }));
  1082. }
  1083. catch (Exception ex)
  1084. {
  1085. LogHelper.WriteLogError("双击打开预览窗口失败", ex);
  1086. }
  1087. }
  1088. }
  1089. }