using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using TeamAAS;
using TeamAAS.FlowEditor;
using TeamAAS.FlowEditor.Models;
using TeamAAS.FlowEditor.Plugins;
using TeamAAS.FlowEngine;
using TeamAAS.Vision;
using Plugins.Halcon.Models;
namespace Plugins.Halcon.Views
{
///
/// HALCON 流程编辑器视图(UserControl)- 承载在 PluginViewDialog 中,镜像 GroupEditorView。
/// 通过 DataContext 接收 ,自动初始化子流程编辑器;
/// 与组合模块的唯一区别:子流程编辑器工具箱作用域为 Halcon(只显示 HALCON 节点,按 VisionPlugin 分类)。
/// 实现 IPluginRunFeedback(窗体“执行”期间同步内嵌画布运行态)、IPluginViewTip(底部提示栏 + 悬停提示)。
///
/// 视觉显示(VisionMaster 风格):右侧大显示窗 + 图层筛选 ComboBox。
/// 节点运行后经 HalconEditorPreview.Show(model,…) → VisionLayerStore 发布图层;
/// 本视图订阅图层总线:选中某节点(画布点选或 ComboBox 选择)即切换显示该节点当前运行批次
/// 的图层(底图 + 多彩叠加),旧批次由 VisionLayerStore 统一淘汰释放,不叠加不泄漏。
///
public partial class HalconFlowEditorView : UserControl, IPluginViewSave, IPluginRunFeedback, IPluginViewTip
{
private FlowEditorViewModel _vm;
private HalconFlowModel _model;
/// 图层筛选条目(SDK):NodeName=null=最新运行;LayerName=null=节点全部图层。
private readonly List _layerEntries = new List();
private bool _suppressComboSync;
public HalconFlowEditorView()
{
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 HalconFlowModel model)
{
_model = model;
model.EnsureSubGraph();
TxtTitle.Text = model.ToolName ?? "Halcon流程";
// 关键:工具箱作用域=Halcon → 只显示 IsSubFlowNode 的 HALCON 节点,按 VisionPlugin 分组
_vm = new FlowEditorViewModel(model.SubGraph, isSubFlowEditor: true, toolboxScope: FlowToolboxScope.Vision);
SubFlowEditor.DataContext = _vm;
_vm.GraphDataChanged += _vm_GraphDataChanged;
_vm.PropertyChanged += _vm_PropertyChanged;
// 大显示窗(注入右侧面板;图层总线是唯一显示驱动 —— 不再挂 HalconEditorPreview.Current
// 经典通路,否则每个算子运行后都会直接推图覆盖掉用户的图层筛选结果)
var preview = new HalconWindow(1);
preview.SetCaption("HALCON 显示");
DisplayHost.Content = preview;
// 图层总线挂接:节点发布图层 → 重建筛选项 + 跟随显示
VisionLayerStore.Instance.LayerPublished += OnLayerPublished;
VisionLayerStore.Instance.FlowCleared += OnFlowCleared;
ResetLayerCombo();
// 让子节点的公式树能显示容器「输入」变量(如 &{输入.图像}):
// PluginDataProvider.BuildInputsNode 反射读 ParentGroupModel.ModuleInputs。
// FlowEditorViewModel.OpenNodeEditor 在子流程编辑器内会保留容器 ParentGroupModel(见其 keepParent 逻辑)。
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;
// 移除内嵌预览窗体(图层总线为唯一显示驱动,无经典通路挂接需要清理)
var preview = DisplayHost.Content as HalconWindow;
DisplayHost.Content = null;
preview?.ClearImage();
}
private void _vm_GraphDataChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (DataContext is HalconFlowModel 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;
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 图层筛选与显示
/// 当前选中的条目;越界/未选返回 null。
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;
/// 图层发布回调:重建筛选项并按“值”保留选中;正在看该节点(或最新运行)时实时刷新显示。
/// 发布的流程 Id 与编辑器不一致时(旧流程文件图 Id 漂移)不再直接丢弃 —— 记录日志并按节点名兜底显示。
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}(已按节点名兜底显示)", "Halcon编辑器");
}
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 { }
}
/// 重建筛选条目:[最新运行] + 每节点(全部图层)+ 每图层。按 (节点,图层) 值保留当前选中。
/// 本流程没有发布记录时(图 Id 漂移等)兜底展示全部发布,保证节点条目始终可见。
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();
}
/// 按当前筛选显示:最新运行=最后一次发布整体;节点=底图+全部叠加;
/// 节点.图层=图像层单独显示、区域等叠加层自动附带该节点原图(便于理解)。
private void ApplyLayerSelection()
{
var preview = DisplayHost.Content as HalconWindow;
if (preview == null) return;
var flowId = _model?.SubGraph?.GraphId;
var entry = SelectedEntry();
var store = VisionLayerStore.Instance;
LayerPublishArgs args;
if (entry == null || entry.NodeName == null)
{
// “最新运行”:显示图层库里最近发布的一组(整体替换式显示;图 Id 漂移时兜底全部)
var all = store.GetFlowLayers(flowId ?? "");
if (all.Count == 0) all = store.GetAllLatest();
if (all.Count == 0)
{
preview.ClearImage();
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)
{
preview.ClearImage();
RunStatusText.Text = $"{entry.NodeName} · 尚未运行";
return;
}
}
var layers = LayerComboHelper.ResolveLayers(args, entry?.LayerName, flowId);
if (layers.Count == 0)
{
preview.ClearImage();
RunStatusText.Text = $"{args.NodeName} · 无可显示图层";
return;
}
preview.ShowLayers(layers);
RunStatusText.Text = $"{args.NodeName} · {(entry?.LayerName ?? $"全部 {layers.Count} 图层")} · #{args.RunStamp}";
}
#endregion
/// 窗口关闭前保存更改回模型
public void SaveChanges()
{
if (DataContext is HalconFlowModel 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 HalconFlowModel model)
{
// 编辑容器自身的输入/输出映射:输入用 InputSourceDataProvider(查主流程前置节点),
// 输出用 PluginDataProvider(查子流程节点)——与 GroupEditorView 一致
PluginLoader.Instance.SetGroupPluginModel = model.SubGraph;
PluginLoader.Instance.ParentGroupModel = model;
if (DialogHelper.EditProperties(model, $"HALCON 流程属性 - {model.NodeName}"))
{
TxtTitle.Text = model.ToolName ?? "Halcon流程";
DialogHelper.Info($"已更新 HALCON 流程属性: {model.ToolName}");
}
PluginLoader.Instance.SetGroupPluginModel = null;
// 保留 ParentGroupModel = model,供子节点公式树继续显示「输入」变量
}
}
}
}