ShowVisionRender.xaml.cs 44 KB

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