| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- using System.Collections.Generic;
- using System.Linq;
- using System.Windows;
- using System.Windows.Controls;
- using TeamAAS;
- using TeamAAS.FlowEditor;
- using TeamAAS.FlowEditor.Models;
- using TeamAAS.FlowEditor.Plugins;
- using TeamAAS.FlowEngine;
- using TeamAAS.Vision;
- using Plugins.Vm.Models;
- namespace Plugins.Vm.Views
- {
- /// <summary>
- /// VM(VisionMaster)流程编辑器视图(UserControl)- 承载在 PluginViewDialog 中,
- /// 与 HalconFlowEditorView 完全同构:左侧子流程画布(工具箱作用域=Vm,只显 Vm模块 算子),
- /// 右侧大显示窗 + 节点图像筛选 ComboBox(VisionMaster 风格)。
- /// 实现 IPluginRunFeedback(窗体"执行"期间同步内嵌画布运行态)、IPluginViewTip(底部提示栏)。
- ///
- /// 图像来源:订阅 VisionLayerStore 图层总线(VM 算子后续发布图层后自动跟随显示;
- /// VM 图像结果键目前经 VmWindow.RefreshImage 按平台对象类型解释,不支持的类型安全跳过)。
- /// </summary>
- public partial class VmFlowEditorView : UserControl, IPluginViewSave, IPluginRunFeedback, IPluginViewTip
- {
- private FlowEditorViewModel _vm;
- private VmFlowModel _model;
- /// <summary>图层筛选条目(SDK):NodeName=null=最新运行;LayerName=null=节点全部图层。</summary>
- private readonly List<LayerComboEntry> _layerEntries = new List<LayerComboEntry>();
- private bool _suppressComboSync;
- public VmFlowEditorView()
- {
- 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 VmFlowModel model)
- {
- _model = model;
- model.EnsureSubGraph();
- TxtTitle.Text = model.ToolName ?? "VM流程";
- // 关键:工具箱作用域=Vm → 只显示 IsSubFlowNode 的 Vm模块 算子,按 VisionPlugin 分组
- _vm = new FlowEditorViewModel(model.SubGraph, isSubFlowEditor: true, toolboxScope: FlowToolboxScope.VisionVm);
- SubFlowEditor.DataContext = _vm;
- _vm.GraphDataChanged += _vm_GraphDataChanged;
- _vm.PropertyChanged += _vm_PropertyChanged;
- // 大显示窗(VM 原生渲染控件):独立面板随窗体尺寸自适应
- var preview = new VmWindow();
- preview.SetCaption("VM 显示");
- DisplayHost.Content = preview;
- // 图像总线挂接:节点发布图像 → 重建筛选项 + 跟随显示
- VisionLayerStore.Instance.LayerPublished += OnLayerPublished;
- VisionLayerStore.Instance.FlowCleared += OnFlowCleared;
- ResetLayerCombo();
- // 让子节点的公式树能显示容器「输入」变量(如 &{输入.图像})
- 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 VmFlowModel model)
- {
- if (sender is FlowGraph flowGraph)
- model.SubGraph = flowGraph;
- }
- }
- /// <summary>画布点选节点 → 图像筛选自动切到该节点(点节点看该节点图)。</summary>
- 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;
- var index = _layerEntries.FindIndex(x => x.NodeName == node.NodeName && x.LayerName == null);
- if (index < 0)
- index = 0;
- if (LayerCombo.SelectedIndex != index)
- {
- _suppressComboSync = true;
- LayerCombo.SelectedIndex = index;
- _suppressComboSync = false;
- }
- ApplyLayerSelection();
- }
- #region 图层筛选与显示
- /// <summary>当前选中的条目;越界/未选返回 null。</summary>
- private LayerComboEntry SelectedEntry()
- {
- var i = LayerCombo.SelectedIndex;
- if (i < 0 || i >= _layerEntries.Count) return null;
- return _layerEntries[i];
- }
- private void ResetLayerCombo()
- {
- _layerEntries.Clear();
- _layerEntries.Add(new LayerComboEntry { Display = "最新运行" });
- _suppressComboSync = true;
- LayerCombo.ItemsSource = _layerEntries.Select(e => e.Display).ToList();
- LayerCombo.SelectedIndex = 0;
- _suppressComboSync = false;
- RunStatusText.Text = "等待运行…(节点运行后自动显示其图像)";
- }
- private static System.DateTime _lastMismatchLog = System.DateTime.MinValue;
- /// <summary>图像发布回调:重建筛选项并按"值"保留选中;正在看该节点(或最新运行)时实时刷新显示。
- /// 发布的流程 Id 与编辑器不一致时(旧流程文件图 Id 漂移)不再直接丢弃 —— 记录日志并按节点名兜底显示。</summary>
- private void OnLayerPublished(LayerPublishArgs args)
- {
- if (args == null) return;
- var flowId = _model?.SubGraph?.GraphId;
- var matched = string.Equals(args.FlowId, flowId, System.StringComparison.OrdinalIgnoreCase);
- if (!matched && (System.DateTime.Now - _lastMismatchLog).TotalSeconds > 5)
- {
- _lastMismatchLog = System.DateTime.Now;
- TeamAAS.AppLogger.Warning($"图像发布流程Id与编辑器不一致: 发布={args.FlowId} 节点={args.NodeName} 编辑器期望={flowId}(已按节点名兜底显示)", "Vm编辑器");
- }
- try
- {
- System.Windows.Application.Current?.Dispatcher.Invoke(() =>
- {
- RebuildLayerEntries();
- var current = SelectedEntry();
- bool follow = current == null || current.NodeName == null ||
- string.Equals(current.NodeName, args.NodeName, System.StringComparison.OrdinalIgnoreCase);
- if (follow || !matched) ApplyLayerSelection();
- });
- }
- catch { }
- }
- private void OnFlowCleared(string flowId, string nodeName)
- {
- try
- {
- System.Windows.Application.Current?.Dispatcher.Invoke(() =>
- {
- RebuildLayerEntries();
- ApplyLayerSelection();
- });
- }
- catch { }
- }
- /// <summary>重建筛选条目:[最新运行] + 每节点(全部图层)+ 每图层。按 (节点,图层) 值保留当前选中。
- /// 本流程没有发布记录时(图 Id 漂移等)兜底展示全部发布,保证节点条目始终可见。</summary>
- private void RebuildLayerEntries()
- {
- var flowId = _model?.SubGraph?.GraphId;
- var own = VisionLayerStore.Instance.GetFlowLayers(flowId ?? "");
- var source = own.Count > 0 ? own : VisionLayerStore.Instance.GetAllLatest();
- var current = SelectedEntry();
- var entries = LayerComboHelper.BuildEntries(flowId ?? "", source);
- _suppressComboSync = true;
- _layerEntries.Clear();
- foreach (var e in entries)
- _layerEntries.Add(e);
- LayerCombo.ItemsSource = _layerEntries.Select(e => e.Display).ToList();
- var index = current == null
- ? 0
- : _layerEntries.FindIndex(x => x.NodeName == current.NodeName && x.LayerName == current.LayerName);
- LayerCombo.SelectedIndex = index >= 0 ? index : 0;
- _suppressComboSync = false;
- }
- private void LayerCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- if (_suppressComboSync) return;
- ApplyLayerSelection();
- }
- /// <summary>按当前筛选显示:最新运行=最后一次发布整体;节点=底图+全部叠加;
- /// 节点.图层=图像层单独显示、其他叠加层自动附带该节点原图。
- /// VmWindow.RefreshImage 按平台对象类型(IVmModule/IImageData)解释,其余类型安全跳过。</summary>
- private void ApplyLayerSelection()
- {
- var preview = DisplayHost.Content as VmWindow;
- if (preview == null) return;
- var flowId = _model?.SubGraph?.GraphId;
- var entry = SelectedEntry();
- var store = VisionLayerStore.Instance;
- LayerPublishArgs args;
- if (entry == null || entry.NodeName == null)
- {
- var all = store.GetFlowLayers(flowId ?? "");
- if (all.Count == 0) all = store.GetAllLatest();
- if (all.Count == 0)
- {
- RunStatusText.Text = "等待运行…(节点运行后自动显示其图像)";
- return;
- }
- args = all[all.Count - 1];
- }
- else
- {
- // 图 Id 漂移兜底:按流程查不到时按节点名查(跨流程最近一次发布)
- args = store.GetLatest(flowId ?? "", entry.NodeName)
- ?? store.GetLatestByNode(entry.NodeName);
- if (args == null || args.Layers.Count == 0)
- {
- RunStatusText.Text = $"{entry.NodeName} · 尚未运行";
- return;
- }
- }
- var layers = LayerComboHelper.ResolveLayers(args, entry?.LayerName, flowId);
- var content = layers.FirstOrDefault(l => l.Kind == LayerKind.Image)?.Content;
- if (content != null)
- preview.RefreshImage(content);
- RunStatusText.Text = $"{args.NodeName} · {(entry?.LayerName ?? $"全部 {layers.Count} 图层")} · #{args.RunStamp}";
- }
- #endregion
- /// <summary>窗口关闭前保存更改回模型</summary>
- public void SaveChanges()
- {
- if (DataContext is VmFlowModel model && _vm != null)
- model.SubGraph = _vm.Graph;
- }
- /// <summary>窗体"执行"开始:内嵌子流程编辑器进入运行态(UI 线程同步,避免跨线程访问控件)</summary>
- public void BeginRun()
- {
- if (_vm == null) return;
- System.Windows.Application.Current?.Dispatcher.Invoke(() => { _vm.IsRunning = true; });
- }
- /// <summary>窗体"执行"结束(含停止/异常):恢复非运行态</summary>
- public void EndRun()
- {
- if (_vm == null) return;
- System.Windows.Application.Current?.Dispatcher.Invoke(() => { _vm.IsRunning = false; });
- }
- #region IPluginViewTip 底部提示栏
- private readonly HashSet<System.Windows.DependencyObject> _hoverHooked =
- new HashSet<System.Windows.DependencyObject>();
- /// <summary>承载本视图的基窗体(PluginViewDialog),未承载时为 null</summary>
- 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);
- /// <summary>扫描视觉树,给所有 Tag 为非空字符串的按钮挂上悬停提示。</summary>
- 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 VmFlowModel model)
- {
- // 编辑容器自身的输入/输出映射:输入用 InputSourceDataProvider(查主流程前置节点),
- // 输出用 PluginDataProvider(查子流程节点)
- PluginLoader.Instance.SetGroupPluginModel = model.SubGraph;
- PluginLoader.Instance.ParentGroupModel = model;
- if (DialogHelper.EditProperties(model, $"VM 流程属性 - {model.NodeName}"))
- {
- TxtTitle.Text = model.ToolName ?? "VM流程";
- DialogHelper.Info($"已更新 VM 流程属性: {model.ToolName}");
- }
- PluginLoader.Instance.SetGroupPluginModel = null;
- // 保留 ParentGroupModel = model,供子节点公式树继续显示「输入」变量
- }
- }
- }
- }
|