VisionStaticAccuracyAnalyzerViewModel.cs 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191
  1. using Cognex.VisionPro;
  2. using Cognex.VisionPro.ToolBlock;
  3. using MaterialDesignThemes.Wpf;
  4. using Prism.Commands;
  5. using Prism.Events;
  6. using Prism.Ioc;
  7. using Prism.Mvvm;
  8. using Prism.Regions;
  9. using Prism.Services.Dialogs;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Collections.ObjectModel;
  13. using System.Data;
  14. using System.IO;
  15. using System.Linq;
  16. using System.Threading;
  17. using System.Threading.Tasks;
  18. using Team.FFFeederService;
  19. using Team.FFFeederService.Interfaces;
  20. using TeamAAS_VP.Core;
  21. using TeamAAS_VP.Data;
  22. using TeamAAS_VP.Enums;
  23. using TeamAAS_VP.Events;
  24. using TeamAAS_VP.Interfaces;
  25. using TeamAAS_VP.Models;
  26. using TeamAAS_VP.Models.Calibration;
  27. using TeamAAS_VP.Resources.Languages;
  28. using TeamAAS_VP.Services;
  29. using TeamAAS_VP.Views.Setting;
  30. using static TeamAAS_VP.Core.StabilityAnalyzer;
  31. namespace TeamAAS_VP.ViewModels.Product
  32. {
  33. public class VisionStaticAccuracyAnalyzerViewModel : BindableBase, IDialogAware
  34. {
  35. IRegionManager _regionManager;
  36. IEventAggregator _eventAggregator;
  37. IContainerProvider _container;
  38. IDialogService _dialogService;
  39. IFeederService _feederService;
  40. IRobotService _robotService;
  41. ICameraService _cameraService;
  42. ISystemDatabaseService _systemDatabaseService;
  43. ICalibrationService _calibrationService;
  44. IConfigService _configService;
  45. //测试过程中控制取消的CTS
  46. CancellationTokenSource _cts;
  47. #region 属性
  48. private ICogImage _Image;
  49. public ICogImage Image
  50. {
  51. get { return _Image; }
  52. set { SetProperty(ref _Image, value); }
  53. }
  54. private Cognex.VisionPro.CogGraphicCollection _Graphic;
  55. public Cognex.VisionPro.CogGraphicCollection Graphic
  56. {
  57. get { return _Graphic; }
  58. set { SetProperty(ref _Graphic, value); }
  59. }
  60. private ProcedureModel _SelectProcedure;
  61. /// <summary>
  62. /// 选中的流程
  63. /// </summary>
  64. public ProcedureModel SelectProcedure
  65. {
  66. get { return _SelectProcedure; }
  67. set
  68. {
  69. SetProperty(ref _SelectProcedure, value);
  70. }
  71. }
  72. private ICamera _Camera;
  73. public ICamera Camera
  74. {
  75. get { return _Camera; }
  76. set { SetProperty(ref _Camera, value); }
  77. }
  78. private string _Message = "等待开始...";
  79. public string Message
  80. {
  81. get { return _Message; }
  82. set { SetProperty(ref _Message, value); }
  83. }
  84. private bool _IsRunning;
  85. public bool IsRunning
  86. {
  87. get { return _IsRunning; }
  88. set { SetProperty(ref _IsRunning, value); }
  89. }
  90. private CogToolBlock _VisionTool;
  91. public CogToolBlock VisionTool
  92. {
  93. get { return _VisionTool; }
  94. set { SetProperty(ref _VisionTool, value); }
  95. }
  96. private int _RepeatCount = 10;
  97. /// <summary>
  98. /// 重复次数
  99. /// </summary>
  100. public int RepeatCount
  101. {
  102. get { return _RepeatCount; }
  103. set { SetProperty(ref _RepeatCount, value); }
  104. }
  105. private int _DelayTime = 200;
  106. /// <summary>
  107. /// 延时时间
  108. /// </summary>
  109. public int DelayTime
  110. {
  111. get { return _DelayTime; }
  112. set { SetProperty(ref _DelayTime, value); }
  113. }
  114. private int _CurrentProgress;
  115. /// <summary>
  116. /// 当前进度
  117. /// </summary>
  118. public int CurrentProgress
  119. {
  120. get { return _CurrentProgress; }
  121. set { SetProperty(ref _CurrentProgress, value); }
  122. }
  123. private int _SuccessCount;
  124. /// <summary>
  125. /// 成功次数
  126. /// </summary>
  127. public int SuccessCount
  128. {
  129. get { return _SuccessCount; }
  130. set { SetProperty(ref _SuccessCount, value); }
  131. }
  132. private int _FailCount;
  133. /// <summary>
  134. /// 失败次数
  135. /// </summary>
  136. public int FailCount
  137. {
  138. get { return _FailCount; }
  139. set { SetProperty(ref _FailCount, value); }
  140. }
  141. private double _SuccessRate;
  142. /// <summary>
  143. /// 成功率
  144. /// </summary>
  145. public double SuccessRate
  146. {
  147. get { return _SuccessRate; }
  148. set { SetProperty(ref _SuccessRate, value); }
  149. }
  150. private DataTable _TestResult;
  151. /// <summary>
  152. /// 测试结果列表DataTable
  153. /// </summary>
  154. public DataTable TestResult
  155. {
  156. get { return _TestResult; }
  157. set { SetProperty(ref _TestResult, value); }
  158. }
  159. private ObservableCollection<DataColumnInfo> _ResultDataColumns;
  160. /// <summary>
  161. /// DataTable的列集合
  162. /// </summary>
  163. public ObservableCollection<DataColumnInfo> ResultDataColumns
  164. {
  165. get { return _ResultDataColumns; }
  166. set { SetProperty(ref _ResultDataColumns, value); }
  167. }
  168. //
  169. private DataColumnInfo _SelectedDataColumn;
  170. /// <summary>
  171. /// 选中的列
  172. /// </summary>
  173. public DataColumnInfo SelectedDataColumn
  174. {
  175. get { return _SelectedDataColumn; }
  176. set { SetProperty(ref _SelectedDataColumn, value); }
  177. }
  178. private SnackbarMessageQueue _MessageQueue;
  179. public SnackbarMessageQueue MessageQueue
  180. {
  181. get { return _MessageQueue; }
  182. set { SetProperty(ref _MessageQueue, value); }
  183. }
  184. //选中列分析结果
  185. private StabilityMetrics _StabilityMetrics;
  186. public StabilityMetrics StabilityMetrics
  187. {
  188. get { return _StabilityMetrics; }
  189. set { SetProperty(ref _StabilityMetrics, value); }
  190. }
  191. #endregion
  192. #region 命令
  193. private DelegateCommand _ConfirmCommand;
  194. public DelegateCommand ConfirmCommand =>
  195. _ConfirmCommand ?? (_ConfirmCommand = new DelegateCommand(ExecuteConfirmCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  196. private DelegateCommand _CancelCommand;
  197. public DelegateCommand CancelCommand =>
  198. _CancelCommand ?? (_CancelCommand = new DelegateCommand(ExecuteCancelCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  199. private DelegateCommand _StartTestCommand;
  200. public DelegateCommand StartTestCommand =>
  201. _StartTestCommand ?? (_StartTestCommand = new DelegateCommand(ExecuteStartTestCommand, CanExecuteStartTestCommand).ObservesProperty(() => IsRunning));
  202. private DelegateCommand _StopTestCommand;
  203. public DelegateCommand StopTestCommand =>
  204. _StopTestCommand ?? (_StopTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
  205. //暂停测试命令
  206. private DelegateCommand _PauseTestCommand;
  207. public DelegateCommand PauseTestCommand =>
  208. _PauseTestCommand ?? (_PauseTestCommand = new DelegateCommand(() => { _cts?.Cancel(); }, () => IsRunning).ObservesProperty(() => IsRunning));
  209. private DelegateCommand _ExportCSVCommand;
  210. public DelegateCommand ExportCSVCommand =>
  211. _ExportCSVCommand ?? (_ExportCSVCommand = new DelegateCommand(ExecuteExportCSVCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  212. private DelegateCommand _ExportReportCommand;
  213. public DelegateCommand ExportReportCommand =>
  214. _ExportReportCommand ?? (_ExportReportCommand = new DelegateCommand(ExecuteExportReportCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  215. private DelegateCommand _ClearDataCommand;
  216. public DelegateCommand ClearDataCommand =>
  217. _ClearDataCommand ?? (_ClearDataCommand = new DelegateCommand(ExecuteClearDataCommand, () => !IsRunning).ObservesProperty(() => IsRunning));
  218. private DelegateCommand _SelectColumnCommand;
  219. public DelegateCommand SelectColumnCommand =>
  220. _SelectColumnCommand ?? (_SelectColumnCommand = new DelegateCommand(ExecuteSelectColumnCommand));
  221. #endregion
  222. #region 事件
  223. #endregion
  224. public VisionStaticAccuracyAnalyzerViewModel(IRegionManager regionManager, IEventAggregator ea, IContainerProvider container, IDialogService dialogService,
  225. IFeederService feederService, IRobotService robotService, ICameraService cameraService, ISystemDatabaseService systemDatabaseService, ICalibrationService calibrationService,
  226. IConfigService configService)
  227. {
  228. _regionManager = regionManager;
  229. _eventAggregator = ea;
  230. _container = container;
  231. _dialogService = dialogService;
  232. _feederService = feederService;
  233. _robotService = robotService;
  234. _cameraService = cameraService;
  235. _systemDatabaseService = systemDatabaseService;
  236. MessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(1));
  237. _calibrationService = calibrationService;
  238. _configService = configService;
  239. }
  240. #region 方法
  241. /// <summary>
  242. /// 确定
  243. /// </summary>
  244. void ExecuteConfirmCommand()
  245. {
  246. _cts?.Cancel();
  247. IDialogParameters parameters = new DialogParameters();
  248. //parameters.Add("Tool", Tool);
  249. //parameters.Add("RobotCameraRotationMatrix", RobotCameraRotationMatrix);
  250. //parameters.Add("Angle", Angle);
  251. RequestClose?.Invoke(new DialogResult(ButtonResult.OK, parameters));
  252. }
  253. /// <summary>
  254. /// 取消
  255. /// </summary>
  256. void ExecuteCancelCommand()
  257. {
  258. _cts?.Cancel();
  259. IDialogParameters parameters = new DialogParameters();
  260. //parameters.Add("Tool", Tool);
  261. RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel, parameters));
  262. }
  263. /// <summary>
  264. /// 开始测试
  265. /// </summary>
  266. async void ExecuteStartTestCommand()
  267. {
  268. IsRunning = true;
  269. _cts = new CancellationTokenSource();
  270. CurrentProgress = 0;
  271. SuccessCount = 0;
  272. SuccessRate = 0;
  273. FailCount = 0;
  274. Message = "测试进行中...";
  275. try
  276. {
  277. while (CurrentProgress < RepeatCount)
  278. {
  279. if (_cts.Token.IsCancellationRequested)
  280. {
  281. Message = "测试已取消!";
  282. break;
  283. }
  284. await Task.Delay(DelayTime);
  285. (bool isSuccess, object[] Result) = await ExecutePhotoEx(CurrentProgress);
  286. if (isSuccess)
  287. {
  288. SuccessCount++;
  289. }
  290. else
  291. {
  292. FailCount++;
  293. }
  294. TestResult.Rows.Add(Result);
  295. ExecuteSelectColumnCommand();
  296. CurrentProgress++;
  297. SuccessRate = (double)SuccessCount / CurrentProgress * 100;
  298. SendTaskMessage($"测试进行中... {CurrentProgress}/{RepeatCount}");
  299. }
  300. if (CurrentProgress >= RepeatCount)
  301. {
  302. Message = "测试完成!";
  303. }
  304. }
  305. catch (Exception ex)
  306. {
  307. LogHelper.WriteLogError("视觉静态重复测试时出错!", ex);
  308. }
  309. finally
  310. {
  311. IsRunning = false;
  312. }
  313. }
  314. bool CanExecuteStartTestCommand()
  315. {
  316. return !IsRunning;
  317. }
  318. /// <summary>
  319. /// 清除数据
  320. /// </summary>
  321. void ExecuteClearDataCommand()
  322. {
  323. if(TestResult!=null)
  324. {
  325. TestResult.Rows.Clear();
  326. }
  327. }
  328. /// <summary>
  329. /// 导出测试报告
  330. /// 使用 iTextSharp 生成 PDF,包含:标题、测试摘要、数据表格、每列稳定性分析结果
  331. /// 详细伪代码:
  332. /// 1. 校验 TestResult 是否存在数据,若无则提示并返回。
  333. /// 2. 弹出保存对话框,获取保存路径,若取消返回。
  334. /// 3. 创建 iTextSharp Document 与 PdfWriter,打开文档。
  335. /// 4. 创建支持中文的 BaseFont(例如 STSongStd-Light + UniGB-UCS2-H)。
  336. /// 5. 写入标题(居中、大号字体)。
  337. /// 6. 写入测试摘要信息(测试时间、重复次数、成功/失败/成功率等),每项为单独段落或表格。
  338. /// 7. 构建 PdfPTable:列数 = TestResult.Columns.Count,写入表头(加粗),逐行写入数据:
  339. /// - 对于数值使用 InvariantCulture 格式化,空值写空字符串。
  340. /// 8. 写入分析结果标题。
  341. /// 9. 对于 ResultDataColumns 中的每列:
  342. /// - 从 TestResult 读取该列所有数值(尝试转换为 double,忽略无法转换的项)。
  343. /// - 如果样本数为 0,写入“样本数为0”提示并跳过。
  344. /// - 调用 StabilityAnalyzer.AnalyzeStability(values) 获取指标对象。
  345. /// - 遍历指标对象的公开属性,将属性名与值写入一个两列的 PdfPTable(或段落)。
  346. /// 10. 关闭文档并释放资源。
  347. /// 11. 捕获异常并记录日志,提示用户失败信息。
  348. /// </summary>
  349. void ExecuteExportReportCommand()
  350. {
  351. try
  352. {
  353. if (TestResult == null || TestResult.Columns.Count == 0 || TestResult.Rows.Count == 0)
  354. {
  355. SendTaskMessage("无测试数据,无法导出报告。");
  356. return;
  357. }
  358. var dlg = new Microsoft.Win32.SaveFileDialog
  359. {
  360. DefaultExt = "pdf",
  361. Filter = "PDF 文件 (*.pdf)|*.pdf|所有文件 (*.*)|*.*",
  362. FileName = "TestReport.pdf",
  363. Title = "保存测试报告为 PDF"
  364. };
  365. bool? dlgResult = dlg.ShowDialog();
  366. if (dlgResult != true)
  367. return;
  368. string path = dlg.FileName;
  369. // 创建文档(A4, 边距)
  370. var doc = new iTextSharp.text.Document(iTextSharp.text.PageSize.A4, 36, 36, 54, 54);
  371. using (var fs = System.IO.File.Create(path))
  372. {
  373. var writer = iTextSharp.text.pdf.PdfWriter.GetInstance(doc, fs);
  374. doc.Open();
  375. // --- 字体加载:优先载入系统中文字体并以 IDENTITY_H 编码嵌入,保证中文显示 ---
  376. iTextSharp.text.Font titleFont;
  377. iTextSharp.text.Font headerFont;
  378. iTextSharp.text.Font normalFont;
  379. iTextSharp.text.pdf.BaseFont baseFont = null;
  380. try
  381. {
  382. var fontsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
  383. var candidates = new[]
  384. {
  385. "msyh.ttf",
  386. "msyhbd.ttf",
  387. "simsun.ttc,0", // 添加 ",0" 指定第一个字体
  388. "simsun.ttc,1", // 第二个字体
  389. "Microsoft YaHei.ttf",
  390. "msyh.ttc,0", // 添加 ",0"
  391. "msyh.ttc,1", // 添加 ",1"
  392. "simhei.ttf"
  393. };
  394. foreach (var f in candidates)
  395. {
  396. var path1 = Path.Combine(fontsFolder, f);
  397. try
  398. {
  399. baseFont = iTextSharp.text.pdf.BaseFont.CreateFont(
  400. path1,
  401. iTextSharp.text.pdf.BaseFont.IDENTITY_H,
  402. iTextSharp.text.pdf.BaseFont.EMBEDDED
  403. );
  404. if (baseFont != null)
  405. {
  406. Console.WriteLine($"成功加载字体: {f}");
  407. break;
  408. }
  409. }
  410. catch (Exception ex)
  411. {
  412. Console.WriteLine($"字体 {f} 加载失败: {ex.Message}");
  413. continue;
  414. }
  415. }
  416. }
  417. catch
  418. {
  419. baseFont = null;
  420. }
  421. if (baseFont != null)
  422. {
  423. titleFont = new iTextSharp.text.Font(baseFont, 16, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  424. headerFont = new iTextSharp.text.Font(baseFont, 10, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  425. normalFont = new iTextSharp.text.Font(baseFont, 10, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
  426. }
  427. else
  428. {
  429. // 回退:如果未找到系统中文字体,使用内置 Helvetica(注意:可能无法正确显示中文)
  430. titleFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 16, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  431. headerFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 10, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK);
  432. normalFont = iTextSharp.text.FontFactory.GetFont(iTextSharp.text.FontFactory.HELVETICA, 10, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK);
  433. }
  434. // 标题
  435. var title = new iTextSharp.text.Paragraph("视觉静态精度测试报告", titleFont)
  436. {
  437. Alignment = iTextSharp.text.Element.ALIGN_CENTER,
  438. SpacingAfter = 12f
  439. };
  440. doc.Add(title);
  441. // 测试摘要
  442. var metaTable = new iTextSharp.text.pdf.PdfPTable(2) { WidthPercentage = 100f };
  443. metaTable.SetWidths(new float[] { 1f, 2f });
  444. void AddMeta(string name, string value)
  445. {
  446. var cellName = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(name, headerFont)) { Border = 0, PaddingBottom = 6f };
  447. var cellVal = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(value, normalFont)) { Border = 0, PaddingBottom = 6f };
  448. metaTable.AddCell(cellName);
  449. metaTable.AddCell(cellVal);
  450. }
  451. // include device info (line, station, fixture id) but exclude MES flags/urls
  452. var device = _configService?.GetDeviceInfo() ?? _configService?.GetDeviceInfo();
  453. if (device != null)
  454. {
  455. AddMeta("线别", device.Line ?? string.Empty);
  456. AddMeta("站别", device.Station ?? string.Empty);
  457. AddMeta("机台号", device.FixtureId ?? string.Empty);
  458. }
  459. AddMeta("导出时间", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
  460. AddMeta("测试次数(目标)", RepeatCount.ToString());
  461. AddMeta("当前进度", $"{CurrentProgress} / {RepeatCount}");
  462. AddMeta("成功次数", SuccessCount.ToString());
  463. AddMeta("失败次数", FailCount.ToString());
  464. AddMeta("成功率(%)", SuccessRate.ToString("F2", System.Globalization.CultureInfo.InvariantCulture));
  465. doc.Add(metaTable);
  466. doc.Add(new iTextSharp.text.Paragraph(" ")); // 空行
  467. // 数据表格
  468. int colCount = TestResult.Columns.Count;
  469. var table = new iTextSharp.text.pdf.PdfPTable(colCount) { WidthPercentage = 100f };
  470. // 表头
  471. foreach (System.Data.DataColumn col in TestResult.Columns)
  472. {
  473. var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(col.ColumnName, headerFont))
  474. {
  475. HorizontalAlignment = iTextSharp.text.Element.ALIGN_CENTER,
  476. BackgroundColor = new iTextSharp.text.BaseColor(230, 230, 230),
  477. Padding = 4f
  478. };
  479. table.AddCell(cell);
  480. }
  481. // 数据行
  482. foreach (System.Data.DataRow row in TestResult.Rows)
  483. {
  484. for (int c = 0; c < colCount; c++)
  485. {
  486. object val = row[c];
  487. string s;
  488. if (val == null || val == DBNull.Value)
  489. s = "";
  490. else if (val is double || val is float || val is decimal)
  491. s = Convert.ToDouble(val).ToString("G", System.Globalization.CultureInfo.InvariantCulture);
  492. else
  493. s = val.ToString();
  494. var cell = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(s, normalFont)) { Padding = 4f };
  495. table.AddCell(cell);
  496. }
  497. }
  498. doc.Add(table);
  499. doc.NewPage();
  500. // 分析结果
  501. var analysisTitle = new iTextSharp.text.Paragraph("分析结果", titleFont) { SpacingAfter = 8f };
  502. doc.Add(analysisTitle);
  503. if (ResultDataColumns != null && ResultDataColumns.Count > 0)
  504. {
  505. foreach (var colInfo in ResultDataColumns)
  506. {
  507. try
  508. {
  509. // 收集数值
  510. var values = new System.Collections.Generic.List<double>();
  511. foreach (System.Data.DataRow row in TestResult.Rows)
  512. {
  513. object v = row[colInfo.ColumnIndex];
  514. if (v == null || v == DBNull.Value) continue;
  515. double d;
  516. if (v is double) d = (double)v;
  517. else if (v is float) d = Convert.ToDouble((float)v);
  518. else if (v is decimal) d = Convert.ToDouble((decimal)v);
  519. else if (v is int) d = Convert.ToDouble((int)v);
  520. else if (v is long) d = Convert.ToDouble((long)v);
  521. else
  522. {
  523. if (!double.TryParse(v.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out d))
  524. continue;
  525. }
  526. values.Add(d);
  527. }
  528. var colHeader = new iTextSharp.text.Paragraph(colInfo.ColumnName, headerFont) { SpacingBefore = 6f, SpacingAfter = 4f };
  529. doc.Add(colHeader);
  530. if (values.Count == 0)
  531. {
  532. doc.Add(new iTextSharp.text.Paragraph("样本数为 0,无法计算。", normalFont));
  533. continue;
  534. }
  535. // 计算稳定性指标
  536. StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(values.ToArray());
  537. // 将指标写成两列表(属性名 / 值)
  538. var metricsTable = new iTextSharp.text.pdf.PdfPTable(2) { WidthPercentage = 60f, SpacingAfter = 6f };
  539. metricsTable.SetWidths(new float[] { 1f, 1f });
  540. var props = metrics.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
  541. foreach (var p in props)
  542. {
  543. object pv = p.GetValue(metrics);
  544. string pvStr;
  545. if (pv == null) pvStr = "";
  546. else if (pv is double) pvStr = ((double)pv).ToString("G", System.Globalization.CultureInfo.InvariantCulture);
  547. else pvStr = pv.ToString();
  548. var pc = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(p.Name, normalFont)) { Padding = 4f };
  549. var vc = new iTextSharp.text.pdf.PdfPCell(new iTextSharp.text.Phrase(pvStr, normalFont)) { Padding = 4f };
  550. metricsTable.AddCell(pc);
  551. metricsTable.AddCell(vc);
  552. }
  553. doc.Add(metricsTable);
  554. }
  555. catch (Exception exCol)
  556. {
  557. LogHelper.WriteLogError($"导出报告时处理列[{colInfo.ColumnName}]出错", exCol);
  558. doc.Add(new iTextSharp.text.Paragraph($"处理列 {colInfo.ColumnName} 时出错: {exCol.Message}", normalFont));
  559. }
  560. }
  561. }
  562. else
  563. {
  564. doc.Add(new iTextSharp.text.Paragraph("无用于分析的数值列。", normalFont));
  565. }
  566. doc.Close();
  567. writer.Close();
  568. }
  569. SendTaskMessage($"已导出测试报告到:{path}");
  570. }
  571. catch (Exception ex)
  572. {
  573. LogHelper.WriteLogError("导出测试报告时出错", ex);
  574. SendTaskMessage($"导出测试报告失败:{ex.Message}");
  575. }
  576. }
  577. /// <summary>
  578. /// 导出测试数据为CSV文件
  579. /// </summary>
  580. void ExecuteExportCSVCommand()
  581. {
  582. try
  583. {
  584. if (TestResult == null || TestResult.Columns.Count == 0 || TestResult.Rows.Count == 0)
  585. {
  586. SendTaskMessage("无测试数据,无法导出。");
  587. return;
  588. }
  589. var dlg = new Microsoft.Win32.SaveFileDialog
  590. {
  591. DefaultExt = "csv",
  592. Filter = "CSV 文件 (*.csv)|*.csv|所有文件 (*.*)|*.*",
  593. FileName = "TestResult.csv",
  594. Title = "保存测试结果为 CSV"
  595. };
  596. bool? dlgResult = dlg.ShowDialog();
  597. if (dlgResult != true)
  598. return;
  599. string path = dlg.FileName;
  600. var sb = new System.Text.StringBuilder();
  601. // 辅助:CSV 字段转义
  602. Func<string, string> EscapeCsv = (s) =>
  603. {
  604. if (s == null) return "";
  605. bool mustQuote = s.Contains(",") || s.Contains("\"") || s.Contains("\r") || s.Contains("\n");
  606. string esc = s.Replace("\"", "\"\"");
  607. return mustQuote ? $"\"{esc}\"" : esc;
  608. };
  609. // include device info at top of CSV
  610. var device = _configService?.GetDeviceInfo();
  611. if (device != null)
  612. {
  613. sb.AppendLine("设备信息");
  614. sb.AppendLine($"线别,{EscapeCsv(device.Line)}");
  615. sb.AppendLine($"站别,{EscapeCsv(device.Station)}");
  616. sb.AppendLine($"机台号,{EscapeCsv(device.FixtureId)}");
  617. sb.AppendLine();
  618. }
  619. // 1. 写入表头
  620. for (int c = 0; c < TestResult.Columns.Count; c++)
  621. {
  622. if (c > 0) sb.Append(",");
  623. sb.Append(EscapeCsv(TestResult.Columns[c].ColumnName));
  624. }
  625. sb.AppendLine();
  626. // 2. 写入数据行
  627. foreach (System.Data.DataRow row in TestResult.Rows)
  628. {
  629. for (int c = 0; c < TestResult.Columns.Count; c++)
  630. {
  631. if (c > 0) sb.Append(",");
  632. object val = row[c];
  633. if (val == DBNull.Value || val == null)
  634. {
  635. sb.Append("");
  636. }
  637. else
  638. {
  639. // 保持数值格式,其他转为字符串
  640. string outStr;
  641. if (val is double || val is float || val is decimal)
  642. outStr = Convert.ToString(val, System.Globalization.CultureInfo.InvariantCulture);
  643. else if (val is int || val is long || val is short || val is byte)
  644. outStr = val.ToString();
  645. else if (val is bool)
  646. outStr = (bool)val ? "True" : "False";
  647. else
  648. outStr = val.ToString();
  649. sb.Append(EscapeCsv(outStr));
  650. }
  651. }
  652. sb.AppendLine();
  653. }
  654. // 3. 在末尾追加分析结果
  655. sb.AppendLine(); // 空行
  656. sb.AppendLine("分析结果");
  657. sb.AppendLine("列名,指标,值"); // CSV 表头:列名,指标,值
  658. if (ResultDataColumns != null)
  659. {
  660. foreach (var colInfo in ResultDataColumns)
  661. {
  662. try
  663. {
  664. List<double> values = new List<double>();
  665. foreach (System.Data.DataRow row in TestResult.Rows)
  666. {
  667. object v = row[colInfo.ColumnIndex];
  668. if (v != DBNull.Value && v != null)
  669. {
  670. double d;
  671. // 支持不同数字类型
  672. if (v is double) d = (double)v;
  673. else if (v is float) d = Convert.ToDouble((float)v);
  674. else if (v is decimal) d = Convert.ToDouble((decimal)v);
  675. else if (v is int) d = Convert.ToDouble((int)v);
  676. else if (v is long) d = Convert.ToDouble((long)v);
  677. else
  678. {
  679. if (!double.TryParse(v.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out d))
  680. continue;
  681. }
  682. values.Add(d);
  683. }
  684. }
  685. if (values.Count == 0)
  686. {
  687. sb.AppendLine($"{EscapeCsv(colInfo.ColumnName)},{EscapeCsv("样本数")},{0}");
  688. continue;
  689. }
  690. // 调用稳定性分析
  691. StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(values.ToArray());
  692. // 使用反射枚举 metrics 的公开属性并写入 CSV
  693. var props = metrics.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
  694. foreach (var p in props)
  695. {
  696. object pv = p.GetValue(metrics);
  697. string pvStr = pv == null ? "" : (pv is double ? Convert.ToString((double)pv, System.Globalization.CultureInfo.InvariantCulture) : pv.ToString());
  698. sb.AppendLine($"{EscapeCsv(colInfo.ColumnName)},{EscapeCsv(p.Name)},{EscapeCsv(pvStr)}");
  699. }
  700. }
  701. catch (Exception exCol)
  702. {
  703. LogHelper.WriteLogError($"导出分析结果时处理列[{colInfo.ColumnName}]出错", exCol);
  704. sb.AppendLine($"{EscapeCsv(colInfo.ColumnName)},{EscapeCsv("Error")},{EscapeCsv(exCol.Message)}");
  705. }
  706. }
  707. }
  708. // 写入文件(UTF8,无 BOM,若需 BOM 可使用 new UTF8Encoding(true))
  709. System.IO.File.WriteAllText(path, sb.ToString(), System.Text.Encoding.UTF8);
  710. SendTaskMessage($"已导出测试结果到:{path}");
  711. }
  712. catch (Exception ex)
  713. {
  714. LogHelper.WriteLogError("导出CSV时出错", ex);
  715. SendTaskMessage($"导出CSV失败:{ex.Message}");
  716. }
  717. }
  718. /// <summary>
  719. /// 选中列时触发
  720. /// </summary>
  721. void ExecuteSelectColumnCommand()
  722. {
  723. try
  724. {
  725. //如果选中列为空,则直接返回
  726. if (SelectedDataColumn == null)
  727. return;
  728. //获取表的指定列的所有数据集合
  729. List<double> dataList = new List<double>();
  730. foreach (DataRow row in TestResult.Rows)
  731. {
  732. if (row[SelectedDataColumn.ColumnIndex] != DBNull.Value)
  733. {
  734. dataList.Add(Convert.ToDouble(row[SelectedDataColumn.ColumnIndex]));
  735. }
  736. }
  737. //如果数据量小于10,则不进行分析
  738. if (dataList.Count < 10)
  739. {
  740. //MessageQueue.Enqueue("选中列的数据量小于10,无法进行统计分析!");
  741. return;
  742. }
  743. //进行稳定性分析
  744. StabilityMetrics metrics = StabilityAnalyzer.AnalyzeStability(dataList.ToArray());
  745. App.Current.Dispatcher.Invoke(() =>
  746. {
  747. StabilityMetrics = metrics;
  748. });
  749. }
  750. catch (Exception ex)
  751. {
  752. LogHelper.WriteLogError("选中列的数据分析数据稳定性时出错!", ex);
  753. }
  754. }
  755. /// <summary>
  756. /// 执行相机拍照并运行 ToolBlock,返回是否成功并通过 out 参数返回 Outputs、图像和图形集合。
  757. /// </summary>
  758. /// <param name="index"></param>
  759. /// <returns></returns>
  760. public Task<(bool isSuccess, object[] Result)> ExecutePhotoEx(int index)
  761. {
  762. return Task.Run(() =>
  763. {
  764. CogToolBlockTerminalCollection outputCollection;
  765. List<object> result = new List<object>();
  766. result.Add(index);
  767. try
  768. {
  769. // 1. 采集图像
  770. bool succed = Camera.SetExposureTime(SelectProcedure.ExposureTime);
  771. if (!succed)
  772. {
  773. SendTaskMessage(Lang.相机曝光设置失败);
  774. }
  775. succed = Camera.SetGain(SelectProcedure.Gain);
  776. if (!succed)
  777. {
  778. SendTaskMessage(Lang.相机增益设置失败);
  779. }
  780. DateTime nowtime = DateTime.Now;
  781. LogHelper.WriteLogInfo(Lang.开始采集图像);
  782. var image = Camera.Grab();
  783. if (image == null)
  784. {
  785. SendTaskMessage(Lang.图像采集失败);
  786. outputCollection = null;
  787. return (false, result.ToArray());
  788. }
  789. //Image = image;
  790. VisionTool.Inputs["InputImage"].Value = image;
  791. LogHelper.WriteLogInfo(string.Format(Lang.采集图像完成用时0ms, (DateTime.Now - nowtime).Milliseconds));
  792. // 2. 为ToolBlock传入其他输入终端
  793. //if (InputTerminal != null)
  794. //{
  795. // LogHelper.WriteLogInfo(Lang.为ToolBlock传入输入终端);
  796. // foreach (var item in InputTerminal)
  797. // {
  798. // if (VisionTool.Inputs.Contains(item.Key))
  799. // {
  800. // LogHelper.WriteLogInfo($"传入[{item.Key}]={item.Value}");
  801. // VisionTool.Inputs[item.Key].Value = item.Value;
  802. // }
  803. // else
  804. // {
  805. // LogHelper.WriteLogInfo($"创建并传入[{item.Key}]={item.Value}");
  806. // VisionTool.Inputs.Add(new CogToolBlockTerminal(item.Key, item.Value));
  807. // }
  808. // }
  809. //}
  810. Image = VisionTool.Inputs["InputImage"].Value as ICogImage;
  811. // 3. 运行视觉工具
  812. LogHelper.WriteLogInfo(Lang.开始运行视觉工具);
  813. VisionTool.Run(); //运行ToolBlock
  814. if (VisionTool.RunStatus.Result == CogToolResultConstants.Accept)
  815. {
  816. SendTaskMessage(Lang.视觉流程执行耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
  817. outputCollection = VisionTool.Outputs; //获取输出终端集合
  818. foreach (CogToolBlockTerminal terminal in outputCollection)
  819. {
  820. //如果类型是常见的字符串、数字、布尔类型,则创建对应的列
  821. if (terminal.ValueType == typeof(string))
  822. {
  823. //如果输出的名称是"Point",则要判断是否是点位格式,点位格式为"x1,y1,u1;x2,y2,u2;...."
  824. if (terminal.Name == "Point")
  825. {
  826. //是否需要转换为绝对坐标
  827. needConvertToAbsolute = false;
  828. //如果当前流程的校准为空,则不需要转换为绝对坐标
  829. if (SelectProcedure.CalibrationId != Guid.Empty)
  830. {
  831. var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
  832. if (calibration != null)
  833. {
  834. needConvertToAbsolute = true;
  835. }
  836. }
  837. //判断是否是点位格式
  838. string strpoints = terminal.Value.ToString();
  839. string[] items = strpoints.Split(';');
  840. bool isPointFormat = true;
  841. foreach (string item in items)
  842. {
  843. string[] subitems = item.Split(',');
  844. if (subitems.Length < 3)
  845. {
  846. isPointFormat = false;
  847. break;
  848. }
  849. double x, y, u;
  850. if (!double.TryParse(subitems[0], out x) || !double.TryParse(subitems[1], out y) || !double.TryParse(subitems[2], out u))
  851. {
  852. isPointFormat = false;
  853. break;
  854. }
  855. }
  856. if (isPointFormat)
  857. {
  858. //是点位格式,则创建多列
  859. for (int i = 0; i < items.Length; i++)
  860. {
  861. string[] subitems = items[i].Split(',');
  862. //先分别创建像素X、像素Y、角度U三列
  863. result.Add(double.Parse(subitems[0]));
  864. result.Add(double.Parse(subitems[1]));
  865. result.Add(double.Parse(subitems[2]));
  866. //如果需要转换为绝对坐标,则再创建绝对X、绝对Y两列
  867. if (needConvertToAbsolute)
  868. {
  869. double[] point = new double[3];
  870. var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
  871. var Robot = _robotService.GetRobot(SelectProcedure.RobotId);
  872. if (Robot!=null)
  873. {
  874. //获取机器人当前坐标系
  875. RPoint rPoint = Robot.GetRobotPos();
  876. point[0] = rPoint.X;
  877. point[1] = rPoint.Y;
  878. point[2] = rPoint.U;
  879. if (calibration.CameraMount == CameraMount.FixedUp || calibration.CameraMount == CameraMount.FixedDown)
  880. {
  881. point = null;
  882. }
  883. }
  884. (bool IsSucceed, double X, double Y, double U) = _calibrationService.ConvertPixelToPosition((double.Parse(subitems[0]), double.Parse(subitems[1]), double.Parse(subitems[2])), point, calibration);
  885. result.Add(Math.Round(X, 5));
  886. result.Add(Math.Round(Y, 5));
  887. }
  888. }
  889. }
  890. else
  891. {
  892. result.Add(terminal.Value.ToString());
  893. }
  894. }
  895. else
  896. {
  897. result.Add(terminal.Value.ToString());
  898. }
  899. }
  900. else if (terminal.ValueType == typeof(int))
  901. result.Add((int)terminal.Value);
  902. else if (terminal.ValueType == typeof(double))
  903. result.Add((double)terminal.Value);
  904. else if (terminal.ValueType == typeof(bool))
  905. result.Add((bool)terminal.Value);
  906. }
  907. Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
  908. return (true, result.ToArray());
  909. }
  910. else
  911. {
  912. SendTaskMessage(Lang.视觉流程执行出错耗时.Replace("{0}", SelectProcedure.Name).Replace("{1}", $"{VisionTool.RunStatus.ProcessingTime:F1}"));
  913. SendTaskMessage(VisionTool.RunStatus.Message);
  914. outputCollection = null;
  915. Graphic = outputCollection["Graphic"].Value as CogGraphicCollection;
  916. return (false, result.ToArray());
  917. }
  918. }
  919. catch (Exception ex)
  920. {
  921. SendTaskMessage(ex.Message);
  922. LogHelper.WriteLogError(Lang.执行相机取图并执行视觉工具组时出错, ex);
  923. outputCollection = null;
  924. return (false, result.ToArray());
  925. }
  926. });
  927. }
  928. public void SendTaskMessage(string msg)
  929. {
  930. App.Current.Dispatcher.Invoke(() =>
  931. {
  932. MessageQueue.Clear();
  933. MessageQueue.Enqueue(msg);
  934. });
  935. }
  936. bool needConvertToAbsolute = false;
  937. /// <summary>
  938. /// 根据VisionTool的输出终端,创建DataTable的列
  939. /// </summary>
  940. /// <param name="outputCollection"></param>
  941. /// <returns></returns>
  942. private DataTable CreateResultDataTable(CogToolBlockTerminalCollection outputCollection)
  943. {
  944. DataTable dt = new DataTable();
  945. ResultDataColumns = new ObservableCollection<DataColumnInfo>();
  946. //第一列添加序号
  947. dt.Columns.Add("序号", typeof(int));
  948. //列索引
  949. foreach (CogToolBlockTerminal terminal in outputCollection)
  950. {
  951. //如果类型是常见的字符串、数字、布尔类型,则创建对应的列
  952. if (terminal.ValueType == typeof(string))
  953. {
  954. //如果输出的名称是"Point",则要判断是否是点位格式,点位格式为"x1,y1,u1;x2,y2,u2;...."
  955. if (terminal.Name == "Point")
  956. {
  957. //是否需要转换为绝对坐标
  958. needConvertToAbsolute = false;
  959. //如果当前流程的校准为空,则不需要转换为绝对坐标
  960. if (SelectProcedure.CalibrationId != Guid.Empty)
  961. {
  962. var calibration = _calibrationService.GetCalibration(SelectProcedure.CalibrationId);
  963. if (calibration != null)
  964. {
  965. needConvertToAbsolute = true;
  966. }
  967. }
  968. //判断是否是点位格式
  969. string strpoints = terminal.Value.ToString();
  970. string[] items = strpoints.Split(';');
  971. bool isPointFormat = true;
  972. foreach (string item in items)
  973. {
  974. string[] subitems = item.Split(',');
  975. if (subitems.Length < 3)
  976. {
  977. isPointFormat = false;
  978. break;
  979. }
  980. double x, y, u;
  981. if (!double.TryParse(subitems[0], out x) || !double.TryParse(subitems[1], out y) || !double.TryParse(subitems[2], out u))
  982. {
  983. isPointFormat = false;
  984. break;
  985. }
  986. }
  987. if (isPointFormat)
  988. {
  989. //是点位格式,则创建多列
  990. for (int i = 0; i < items.Length; i++)
  991. {
  992. //先分别创建像素X、像素Y、角度U三列
  993. dt.Columns.Add($"{terminal.Name}_Pixel_X{i + 1}", typeof(double));
  994. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Pixel_X{i + 1}", dt.Columns.Count - 1));
  995. dt.Columns.Add($"{terminal.Name}_Pixel_Y{i + 1}", typeof(double));
  996. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Pixel_Y{i + 1}", dt.Columns.Count - 1));
  997. dt.Columns.Add($"{terminal.Name}_Angle_U{i + 1}", typeof(double));
  998. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Angle_U{i + 1}", dt.Columns.Count - 1));
  999. //如果需要转换为绝对坐标,则再创建绝对X、绝对Y两列
  1000. if (needConvertToAbsolute)
  1001. {
  1002. dt.Columns.Add($"{terminal.Name}_Absolute_X{i + 1}", typeof(double));
  1003. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Absolute_X{i + 1}", dt.Columns.Count - 1));
  1004. dt.Columns.Add($"{terminal.Name}_Absolute_Y{i + 1}", typeof(double));
  1005. ResultDataColumns.Add(new DataColumnInfo($"{terminal.Name}_Absolute_Y{i + 1}", dt.Columns.Count - 1));
  1006. }
  1007. }
  1008. }
  1009. else
  1010. {
  1011. dt.Columns.Add(terminal.Name, typeof(string));
  1012. }
  1013. }
  1014. else
  1015. {
  1016. dt.Columns.Add(terminal.Name, typeof(string));
  1017. }
  1018. }
  1019. else if (terminal.ValueType == typeof(int))
  1020. {
  1021. dt.Columns.Add(terminal.Name, typeof(int));
  1022. ResultDataColumns.Add(new DataColumnInfo(terminal.Name, dt.Columns.Count - 1));
  1023. }
  1024. else if (terminal.ValueType == typeof(double))
  1025. {
  1026. dt.Columns.Add(terminal.Name, typeof(double));
  1027. ResultDataColumns.Add(new DataColumnInfo(terminal.Name, dt.Columns.Count - 1));
  1028. }
  1029. else if (terminal.ValueType == typeof(bool))
  1030. {
  1031. dt.Columns.Add(terminal.Name, typeof(bool));
  1032. }
  1033. //else
  1034. // dt.Columns.Add(terminal.Name, typeof(string));
  1035. }
  1036. return dt;
  1037. }
  1038. #endregion
  1039. #region 继承
  1040. public string Title { get; set; } = "视觉静态精度分析仪";
  1041. public event Action<IDialogResult> RequestClose;
  1042. public bool CanCloseDialog()
  1043. {
  1044. return true;
  1045. }
  1046. public void OnDialogClosed()
  1047. {
  1048. //关闭时,停止测试
  1049. _cts?.Cancel();
  1050. }
  1051. public void OnDialogOpened(IDialogParameters parameters)
  1052. {
  1053. SelectProcedure = parameters.GetValue<ProcedureModel>("SelectedProcedure");
  1054. Camera = _cameraService.GetCamera(SelectProcedure.CameraId);
  1055. VisionTool = parameters.GetValue<CogToolBlock>("ToolBlock");
  1056. VisionTool.Run();
  1057. TestResult = CreateResultDataTable(VisionTool.Outputs);
  1058. }
  1059. #endregion
  1060. }
  1061. //辅助类,用于定义DataTable的列,仅用来对于数值列进行统计分析
  1062. public class DataColumnInfo
  1063. {
  1064. public string ColumnName { get; set; }
  1065. //列索引
  1066. public int ColumnIndex { get; set; }
  1067. public DataColumnInfo(string columnName, int columnIndex)
  1068. {
  1069. ColumnName = columnName;
  1070. ColumnIndex = columnIndex;
  1071. }
  1072. }
  1073. }