ShowVisionRender.xaml.cs 51 KB

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