VppFlowEditorView.xaml.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using Cognex.VisionPro;
  6. using TeamAAS;
  7. using TeamAAS.FlowEditor;
  8. using TeamAAS.FlowEditor.Models;
  9. using TeamAAS.FlowEditor.Plugins;
  10. using TeamAAS.FlowEngine;
  11. using TeamAAS.Vision;
  12. using Plugins.Vpp.Models;
  13. namespace Plugins.Vpp.Views
  14. {
  15. /// <summary>
  16. /// VPP(VisionPro)流程编辑器视图 —— 显示端简化版:
  17. /// 右侧大显示窗**跟随最新运行的图像**(任意节点发布即刷新为该节点默认视图),
  18. /// 画布点选节点可看该节点的最新图像。不再提供图层筛选下拉 ——
  19. /// 输出/显示什么在「编辑输出终端」弹窗里配置(勾选即输出,输出即显示)。
  20. /// 实现 IPluginRunFeedback(窗体"执行"期间同步内嵌画布运行态)、IPluginViewTip(底部提示栏)。
  21. /// </summary>
  22. public partial class VppFlowEditorView : UserControl, IPluginViewSave, IPluginRunFeedback, IPluginViewTip
  23. {
  24. private FlowEditorViewModel _vm;
  25. private VppFlowModel _model;
  26. public VppFlowEditorView()
  27. {
  28. InitializeComponent();
  29. // 视觉显示框注入子流程编辑器右侧面板(整区替换「节点信息」)——三个平台编辑器统一布局
  30. EditorRoot.Children.Remove(VisionPanel);
  31. SubFlowEditor.ReplaceNodeInfoPanel(VisionPanel);
  32. VisionPanel.Visibility = Visibility.Visible;
  33. Loaded += OnLoaded;
  34. Unloaded += OnUnloaded;
  35. }
  36. private void OnLoaded(object sender, RoutedEventArgs e)
  37. {
  38. if (DataContext is VppFlowModel model)
  39. {
  40. _model = model;
  41. model.EnsureSubGraph();
  42. TxtTitle.Text = model.ToolName ?? "VPP流程";
  43. // 工具箱作用域=Vpp → 只显示 IsSubFlowNode 的 Vpp模块 算子,按 VisionPlugin 分组
  44. _vm = new FlowEditorViewModel(model.SubGraph, isSubFlowEditor: true, toolboxScope: FlowToolboxScope.VisionVpp);
  45. SubFlowEditor.DataContext = _vm;
  46. _vm.GraphDataChanged += _vm_GraphDataChanged;
  47. _vm.PropertyChanged += _vm_PropertyChanged;
  48. // 大显示窗(VisionPro 原生显示控件):独立面板随窗体尺寸自适应
  49. var preview = new VppWindow(1);
  50. preview.SetCaption("VPP 显示");
  51. DisplayHost.Content = preview;
  52. // 图像总线挂接:节点发布 → 跟随显示最新
  53. VisionLayerStore.Instance.LayerPublished += OnLayerPublished;
  54. VisionLayerStore.Instance.FlowCleared += OnFlowCleared;
  55. RunStatusText.Text = "等待运行…(节点运行后自动显示其图像)";
  56. // 让子节点的公式树能显示容器「输入」变量(如 &{输入.图像})
  57. PluginLoader.Instance.ParentGroupModel = model;
  58. HookHoverHints(this);
  59. }
  60. }
  61. private void OnUnloaded(object sender, RoutedEventArgs e)
  62. {
  63. PluginLoader.Instance.SetGroupPluginModel = null;
  64. if (ReferenceEquals(PluginLoader.Instance.ParentGroupModel, _model))
  65. PluginLoader.Instance.ParentGroupModel = null;
  66. VisionLayerStore.Instance.LayerPublished -= OnLayerPublished;
  67. VisionLayerStore.Instance.FlowCleared -= OnFlowCleared;
  68. DisplayHost.Content = null;
  69. }
  70. private void _vm_GraphDataChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
  71. {
  72. if (DataContext is VppFlowModel model)
  73. {
  74. if (sender is FlowGraph flowGraph)
  75. model.SubGraph = flowGraph;
  76. }
  77. }
  78. /// <summary>画布点选节点 → 显示该节点最新图像(实时联动)。</summary>
  79. private void _vm_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
  80. {
  81. if (e == null || e.PropertyName != "SelectedNode") return;
  82. var node = _vm?.SelectedNode;
  83. if (node == null || string.IsNullOrEmpty(node.NodeName)) return;
  84. ShowNodeImage(node.NodeName);
  85. }
  86. /// <summary>图像发布回调:直接跟随显示最新发布的图像(无筛选概念)。</summary>
  87. private void OnLayerPublished(LayerPublishArgs args)
  88. {
  89. if (args == null) return;
  90. try
  91. {
  92. System.Windows.Application.Current?.Dispatcher.Invoke(() => ShowArgs(args));
  93. }
  94. catch { }
  95. }
  96. private void OnFlowCleared(string flowId, string nodeName)
  97. {
  98. try
  99. {
  100. System.Windows.Application.Current?.Dispatcher.Invoke(() =>
  101. {
  102. RunStatusText.Text = "等待运行…(节点运行后自动显示其图像)";
  103. });
  104. }
  105. catch { }
  106. }
  107. /// <summary>显示一次发布的默认视图(根记录合成图,退而求其次为第一张图像)。</summary>
  108. private void ShowArgs(LayerPublishArgs args)
  109. {
  110. var preview = DisplayHost.Content as VppWindow;
  111. if (preview == null || args == null || args.Layers.Count == 0) return;
  112. var content = args.Layers.FirstOrDefault(l => l != null && l.Content is ICogRecord)?.Content
  113. ?? args.Layers.FirstOrDefault(l => l != null && l.Kind == LayerKind.Image)?.Content;
  114. switch (content)
  115. {
  116. case ICogRecord rec:
  117. preview.RefreshRecordDisplay(rec);
  118. break;
  119. case ICogImage img:
  120. preview.RefreshImageDisplay(img);
  121. break;
  122. }
  123. RunStatusText.Text = $"{args.NodeName} · #{args.RunStamp}";
  124. }
  125. /// <summary>显示指定节点的最新图像(画布点选联动)。</summary>
  126. private void ShowNodeImage(string nodeName)
  127. {
  128. var preview = DisplayHost.Content as VppWindow;
  129. if (preview == null) return;
  130. var flowId = _model?.SubGraph?.GraphId;
  131. var args = VisionLayerStore.Instance.GetLatest(flowId ?? "", nodeName)
  132. ?? VisionLayerStore.Instance.GetLatestByNode(nodeName);
  133. if (args == null || args.Layers.Count == 0)
  134. {
  135. RunStatusText.Text = $"{nodeName} · 尚未运行";
  136. return;
  137. }
  138. ShowArgs(args);
  139. }
  140. /// <summary>窗口关闭前保存更改回模型</summary>
  141. public void SaveChanges()
  142. {
  143. if (DataContext is VppFlowModel model && _vm != null)
  144. model.SubGraph = _vm.Graph;
  145. }
  146. /// <summary>窗体"执行"开始:内嵌子流程编辑器进入运行态(UI 线程同步,避免跨线程访问控件)</summary>
  147. public void BeginRun()
  148. {
  149. if (_vm == null) return;
  150. System.Windows.Application.Current?.Dispatcher.Invoke(() => { _vm.IsRunning = true; });
  151. }
  152. /// <summary>窗体"执行"结束(含停止/异常):恢复非运行态</summary>
  153. public void EndRun()
  154. {
  155. if (_vm == null) return;
  156. System.Windows.Application.Current?.Dispatcher.Invoke(() => { _vm.IsRunning = false; });
  157. }
  158. #region IPluginViewTip 底部提示栏
  159. private readonly HashSet<System.Windows.DependencyObject> _hoverHooked =
  160. new HashSet<System.Windows.DependencyObject>();
  161. /// <summary>承载本视图的基窗体(PluginViewDialog),未承载时为 null</summary>
  162. private TeamAAS.Dialogs.Dialogs.PluginViewDialog HostDialog =>
  163. System.Windows.Window.GetWindow(this) as TeamAAS.Dialogs.Dialogs.PluginViewDialog;
  164. public void ShowTip(string message) => HostDialog?.ShowTip(message);
  165. public void ShowHoverHint(string message) => HostDialog?.ShowHoverHint(message);
  166. private void HoverEl_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
  167. {
  168. if ((sender as System.Windows.FrameworkElement)?.Tag is string tip && !string.IsNullOrWhiteSpace(tip))
  169. ShowHoverHint(tip);
  170. }
  171. private void HoverEl_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e) => ShowHoverHint(null);
  172. /// <summary>扫描视觉树,给所有 Tag 为非空字符串的按钮挂上悬停提示。</summary>
  173. private void HookHoverHints(System.Windows.DependencyObject root)
  174. {
  175. if (root == null) return;
  176. var count = System.Windows.Media.VisualTreeHelper.GetChildrenCount(root);
  177. for (int i = 0; i < count; i++)
  178. {
  179. var child = System.Windows.Media.VisualTreeHelper.GetChild(root, i);
  180. if (child is System.Windows.Controls.Primitives.ButtonBase btn
  181. && !_hoverHooked.Contains(btn)
  182. && btn.Tag is string tip && !string.IsNullOrWhiteSpace(tip))
  183. {
  184. btn.MouseEnter += HoverEl_MouseEnter;
  185. btn.MouseLeave += HoverEl_MouseLeave;
  186. _hoverHooked.Add(btn);
  187. }
  188. HookHoverHints(child);
  189. }
  190. }
  191. #endregion
  192. private void BtnProperties_Click(object sender, RoutedEventArgs e)
  193. {
  194. if (DataContext is VppFlowModel model)
  195. {
  196. // 编辑容器自身的输入/输出映射:输入用 InputSourceDataProvider(查主流程前置节点),
  197. // 输出用 PluginDataProvider(查子流程节点)
  198. PluginLoader.Instance.SetGroupPluginModel = model.SubGraph;
  199. PluginLoader.Instance.ParentGroupModel = model;
  200. if (DialogHelper.EditProperties(model, $"VPP 流程属性 - {model.NodeName}"))
  201. {
  202. TxtTitle.Text = model.ToolName ?? "VPP流程";
  203. DialogHelper.Info($"已更新 VPP 流程属性: {model.ToolName}");
  204. }
  205. PluginLoader.Instance.SetGroupPluginModel = null;
  206. // 保留 ParentGroupModel = model,供子节点公式树继续显示「输入」变量
  207. }
  208. }
  209. }
  210. }