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,供子节点公式树继续显示「输入」变量
}
}
}
}