ShowVisionRender.xaml.cs 50 KB

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