using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using Cognex.VisionPro; using TeamAAS; using TeamAAS.FlowEditor; using TeamAAS.FlowEditor.Models; using TeamAAS.FlowEditor.Plugins; using TeamAAS.FlowEngine; using TeamAAS.Vision; using Plugins.Vpp.Models; namespace Plugins.Vpp.Views { /// /// VPP(VisionPro)流程编辑器视图 —— 显示端简化版: /// 右侧大显示窗**跟随最新运行的图像**(任意节点发布即刷新为该节点默认视图), /// 画布点选节点可看该节点的最新图像。不再提供图层筛选下拉 —— /// 输出/显示什么在「编辑输出终端」弹窗里配置(勾选即输出,输出即显示)。 /// 实现 IPluginRunFeedback(窗体"执行"期间同步内嵌画布运行态)、IPluginViewTip(底部提示栏)。 /// public partial class VppFlowEditorView : UserControl, IPluginViewSave, IPluginRunFeedback, IPluginViewTip { private FlowEditorViewModel _vm; private VppFlowModel _model; public VppFlowEditorView() { InitializeComponent(); // 视觉显示框注入子流程编辑器右侧面板(整区替换「节点信息」)——三个平台编辑器统一布局 EditorRoot.Children.Remove(VisionPanel); SubFlowEditor.ReplaceNodeInfoPanel(VisionPanel); VisionPanel.Visibility = Visibility.Visible; Loaded += OnLoaded; Unloaded += OnUnloaded; } private void OnLoaded(object sender, RoutedEventArgs e) { if (DataContext is VppFlowModel model) { _model = model; model.EnsureSubGraph(); TxtTitle.Text = model.ToolName ?? "VPP流程"; // 工具箱作用域=Vpp → 只显示 IsSubFlowNode 的 Vpp模块 算子,按 VisionPlugin 分组 _vm = new FlowEditorViewModel(model.SubGraph, isSubFlowEditor: true, toolboxScope: FlowToolboxScope.VisionVpp); SubFlowEditor.DataContext = _vm; _vm.GraphDataChanged += _vm_GraphDataChanged; _vm.PropertyChanged += _vm_PropertyChanged; // 大显示窗(VisionPro 原生显示控件):独立面板随窗体尺寸自适应 var preview = new VppWindow(1); preview.SetCaption("VPP 显示"); DisplayHost.Content = preview; // 图像总线挂接:节点发布 → 跟随显示最新 VisionLayerStore.Instance.LayerPublished += OnLayerPublished; VisionLayerStore.Instance.FlowCleared += OnFlowCleared; RunStatusText.Text = "等待运行…(节点运行后自动显示其图像)"; // 让子节点的公式树能显示容器「输入」变量(如 &{输入.图像}) PluginLoader.Instance.ParentGroupModel = model; HookHoverHints(this); } } private void OnUnloaded(object sender, RoutedEventArgs e) { PluginLoader.Instance.SetGroupPluginModel = null; if (ReferenceEquals(PluginLoader.Instance.ParentGroupModel, _model)) PluginLoader.Instance.ParentGroupModel = null; VisionLayerStore.Instance.LayerPublished -= OnLayerPublished; VisionLayerStore.Instance.FlowCleared -= OnFlowCleared; DisplayHost.Content = null; } private void _vm_GraphDataChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (DataContext is VppFlowModel model) { if (sender is FlowGraph flowGraph) model.SubGraph = flowGraph; } } /// 画布点选节点 → 显示该节点最新图像(实时联动)。 private void _vm_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e == null || e.PropertyName != "SelectedNode") return; var node = _vm?.SelectedNode; if (node == null || string.IsNullOrEmpty(node.NodeName)) return; ShowNodeImage(node.NodeName); } /// 图像发布回调:直接跟随显示最新发布的图像(无筛选概念)。 private void OnLayerPublished(LayerPublishArgs args) { if (args == null) return; try { System.Windows.Application.Current?.Dispatcher.Invoke(() => ShowArgs(args)); } catch { } } private void OnFlowCleared(string flowId, string nodeName) { try { System.Windows.Application.Current?.Dispatcher.Invoke(() => { RunStatusText.Text = "等待运行…(节点运行后自动显示其图像)"; }); } catch { } } /// 显示一次发布的默认视图(根记录合成图,退而求其次为第一张图像)。 private void ShowArgs(LayerPublishArgs args) { var preview = DisplayHost.Content as VppWindow; if (preview == null || args == null || args.Layers.Count == 0) return; var content = args.Layers.FirstOrDefault(l => l != null && l.Content is ICogRecord)?.Content ?? args.Layers.FirstOrDefault(l => l != null && l.Kind == LayerKind.Image)?.Content; switch (content) { case ICogRecord rec: preview.RefreshRecordDisplay(rec); break; case ICogImage img: preview.RefreshImageDisplay(img); break; } RunStatusText.Text = $"{args.NodeName} · #{args.RunStamp}"; } /// 显示指定节点的最新图像(画布点选联动)。 private void ShowNodeImage(string nodeName) { var preview = DisplayHost.Content as VppWindow; if (preview == null) return; var flowId = _model?.SubGraph?.GraphId; var args = VisionLayerStore.Instance.GetLatest(flowId ?? "", nodeName) ?? VisionLayerStore.Instance.GetLatestByNode(nodeName); if (args == null || args.Layers.Count == 0) { RunStatusText.Text = $"{nodeName} · 尚未运行"; return; } ShowArgs(args); } /// 窗口关闭前保存更改回模型 public void SaveChanges() { if (DataContext is VppFlowModel model && _vm != null) model.SubGraph = _vm.Graph; } /// 窗体"执行"开始:内嵌子流程编辑器进入运行态(UI 线程同步,避免跨线程访问控件) public void BeginRun() { if (_vm == null) return; System.Windows.Application.Current?.Dispatcher.Invoke(() => { _vm.IsRunning = true; }); } /// 窗体"执行"结束(含停止/异常):恢复非运行态 public void EndRun() { if (_vm == null) return; System.Windows.Application.Current?.Dispatcher.Invoke(() => { _vm.IsRunning = false; }); } #region IPluginViewTip 底部提示栏 private readonly HashSet _hoverHooked = new HashSet(); /// 承载本视图的基窗体(PluginViewDialog),未承载时为 null private TeamAAS.Dialogs.Dialogs.PluginViewDialog HostDialog => System.Windows.Window.GetWindow(this) as TeamAAS.Dialogs.Dialogs.PluginViewDialog; public void ShowTip(string message) => HostDialog?.ShowTip(message); public void ShowHoverHint(string message) => HostDialog?.ShowHoverHint(message); private void HoverEl_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e) { if ((sender as System.Windows.FrameworkElement)?.Tag is string tip && !string.IsNullOrWhiteSpace(tip)) ShowHoverHint(tip); } private void HoverEl_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e) => ShowHoverHint(null); /// 扫描视觉树,给所有 Tag 为非空字符串的按钮挂上悬停提示。 private void HookHoverHints(System.Windows.DependencyObject root) { if (root == null) return; var count = System.Windows.Media.VisualTreeHelper.GetChildrenCount(root); for (int i = 0; i < count; i++) { var child = System.Windows.Media.VisualTreeHelper.GetChild(root, i); if (child is System.Windows.Controls.Primitives.ButtonBase btn && !_hoverHooked.Contains(btn) && btn.Tag is string tip && !string.IsNullOrWhiteSpace(tip)) { btn.MouseEnter += HoverEl_MouseEnter; btn.MouseLeave += HoverEl_MouseLeave; _hoverHooked.Add(btn); } HookHoverHints(child); } } #endregion private void BtnProperties_Click(object sender, RoutedEventArgs e) { if (DataContext is VppFlowModel model) { // 编辑容器自身的输入/输出映射:输入用 InputSourceDataProvider(查主流程前置节点), // 输出用 PluginDataProvider(查子流程节点) PluginLoader.Instance.SetGroupPluginModel = model.SubGraph; PluginLoader.Instance.ParentGroupModel = model; if (DialogHelper.EditProperties(model, $"VPP 流程属性 - {model.NodeName}")) { TxtTitle.Text = model.ToolName ?? "VPP流程"; DialogHelper.Info($"已更新 VPP 流程属性: {model.ToolName}"); } PluginLoader.Instance.SetGroupPluginModel = null; // 保留 ParentGroupModel = model,供子节点公式树继续显示「输入」变量 } } } }