using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using TeamAAS.FlowEditor.Execution;
using TeamAAS.FlowEditor.Models;
using TeamAAS.FlowEditor.Plugins;
using TeamAAS.FlowEngine.Execution;
using TeamAAS.FlowEngine;
namespace TeamAAS.FlowEditor
{
///
/// 流程编辑器 ViewModel(单流程)
///
public class FlowEditorViewModel : TeamAAS.BindableBase
{
#region 属性
private FlowGraph _graph;
public FlowGraph Graph
{
get => _graph;
set
{
if (SetProperty(ref _graph, value))
HookGraph(_graph);
}
}
public PropertyChangedEventHandler GraphDataChanged { get; set; }
#region 脏标记(决定"切换产品时是否提示保存")
private bool _isDirty;
/// 流程是否被修改过(点击保存或加载后恢复为 false)
public bool IsDirty
{
get => _isDirty;
private set => SetProperty(ref _isDirty, value);
}
/// 标记流程已修改(节点增删/移动/连线/属性编辑等)
public void MarkDirty()
{
IsDirty = true;
}
/// 标记流程已保存/刚加载(清除修改标记)
public void MarkClean()
{
IsDirty = false;
}
private bool _dirtyTrackingHooked;
private void HookGraph(FlowGraph graph)
{
if (graph == null) return;
if (_dirtyTrackingHooked)
{
graph.Nodes.CollectionChanged -= OnNodesCollectionChanged;
graph.Connections.CollectionChanged -= OnConnectionsCollectionChanged;
foreach (var node in graph.Nodes)
node.PropertyChanged -= OnNodePropertyChanged;
}
graph.Nodes.CollectionChanged += OnNodesCollectionChanged;
graph.Connections.CollectionChanged += OnConnectionsCollectionChanged;
foreach (var node in graph.Nodes)
node.PropertyChanged += OnNodePropertyChanged;
_dirtyTrackingHooked = true;
}
private void OnNodesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
foreach (FlowNode node in e.OldItems)
node.PropertyChanged -= OnNodePropertyChanged;
if (e.NewItems != null)
foreach (FlowNode node in e.NewItems)
node.PropertyChanged += OnNodePropertyChanged;
MarkDirty();
}
private void OnConnectionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
MarkDirty();
}
// 只有"用户编辑类"属性才计入脏标记;运行状态(Status/CostTime/结果等)不算
private static readonly HashSet EditProperties = new HashSet
{
nameof(FlowNode.X),
nameof(FlowNode.Y),
nameof(FlowNode.NodeName),
nameof(FlowNode.IsEnabled),
nameof(FlowNode.IsSkipWarning),
nameof(FlowNode.NodeWidth),
nameof(FlowNode.NodeHeight),
};
private void OnNodePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (EditProperties.Contains(e.PropertyName))
MarkDirty();
}
#endregion
private bool _isSubFlowEditor;
///
/// 是否为子流程编辑器
///
public bool IsSubFlowEditor
{
get => _isSubFlowEditor;
set
{
if (SetProperty(ref _isSubFlowEditor, value))
{
RunFlowCommand?.RaiseCanExecuteChanged();
}
}
}
public ObservableCollection ToolboxGroups { get; private set; }
private FlowNode _selectedNode;
public FlowNode SelectedNode
{
get => _selectedNode;
set
{
if (SetProperty(ref _selectedNode, value))
{
// 切换节点时,自动选中最新历史记录(倒序,最新在 index 0)
if (_selectedNode != null && _selectedNode.ExecutionHistory?.Count > 0)
_selectedNode.SelectedHistoryEntry = _selectedNode.ExecutionHistory[0];
}
}
}
private double _zoom = 1.0;
public double Zoom
{
get => _zoom;
set => SetProperty(ref _zoom, value);
}
private bool _isRunning;
public bool IsRunning
{
get => _isRunning;
set => SetProperty(ref _isRunning, value);
}
private bool _isReadOnly;
///
/// 只读监控模式(运行产品运行中):可选中节点查看结果,禁止一切修改与编辑器内运行。
///
public bool IsReadOnly
{
get => _isReadOnly;
private set => SetProperty(ref _isReadOnly, value);
}
/// 由 Shell/产品级状态设置只读模式
public void SetReadOnly(bool value)
{
IsReadOnly = value;
RunFlowCommand?.RaiseCanExecuteChanged();
StopFlowCommand?.RaiseCanExecuteChanged();
}
private bool _isLoading;
/// 导入流程时是否正在加载(画布显示转圈动画)
public bool IsLoading
{
get => _isLoading;
set => SetProperty(ref _isLoading, value);
}
private double _canvasWidth = 3500;
/// 画布宽度
public double CanvasWidth
{
get => _canvasWidth;
set => SetProperty(ref _canvasWidth, value);
}
private double _canvasHeight = 3500;
/// 画布高度
public double CanvasHeight
{
get => _canvasHeight;
set => SetProperty(ref _canvasHeight, value);
}
#endregion
#region 命令
[field: NonSerialized]
public DelegateCommand ClearSelectionCommand { get; private set; }
public DelegateCommand DeleteSelectedCommand { get; private set; }
public DelegateCommand RunFlowCommand { get; private set; }
public DelegateCommand StopFlowCommand { get; private set; }
public DelegateCommand ImportFlowCommand { get; private set; }
public DelegateCommand ExportFlowCommand { get; private set; }
#endregion
#region 节点创建
///
/// 从插件描述符创建节点
///
public static FlowNode CreateNodeFromPlugin(string NodeName,FlowGraph flowGraph, NodePluginInfo info, double x, double y)
{
var desc = PluginLoader.Instance.GetDescriptor(info.PluginId);
var node = new FlowNode
{
NodeName = NodeName,
Category = desc?.NodeShape ?? info.Category,
FlowName = flowGraph.GraphName,
FlowId = flowGraph.GraphId,
PluginId = info.PluginId,
X = x,
Y = y,
IconGeometry = info.IconGeometry
};
// 创建插件实例并获取默认模型
var plugin = PluginLoader.Instance.CreateInstance(info.DisplayName);
if (plugin != null)
{
plugin.GetModel.NodeId = node.NodeId;
plugin.GetModel.ToolName = info.DisplayName;
plugin.GetModel.NodeName = node.NodeName;
plugin.GetModel.PluginId = node.PluginId;
plugin.GetModel.FlowName = flowGraph.GraphName;
plugin.GetModel.FlowId = flowGraph.GraphId;
node.Registry = flowGraph.Registry;
plugin.Registry = flowGraph.Registry; // InitRun 前需就绪,使默认输出注册到正确的注册表
// 新建节点立即执行一次初始化运行:触发 DeclareOutputs 生成默认输出、注册到 ResultRegistry(供绑定树可见)、
// 并填充 LastResults;随后 PluginModel setter 会用 LastResults 刷新节点输出列表。
// 修复:从工具箱新拖入的节点不显示输出(此前仅 InitializeNode 加载/粘贴路径才 InitRun)。
plugin.InitRun();
node.PluginModel = plugin;
}
return node;
}
#endregion
#region 构造函数
public FlowEditorViewModel(FlowGraph graph = null, bool isSubFlowEditor = false, FlowToolboxScope toolboxScope = FlowToolboxScope.Main)
{
Graph = graph ?? new FlowGraph { GraphName = "流程" };
IsSubFlowEditor = isSubFlowEditor;
ToolboxScope = toolboxScope;
InitCommands();
MarkClean();
}
///
/// 工具箱作用域:决定本编辑器工具箱展示哪一批插件、按什么分组。
/// Main=主流程(排除 IsSubFlowNode 子节点,按 PluginCategory 分组);
/// Halcon=Halcon 子流程(只显 IsSubFlowNode 子节点,按 VisionPlugin 分组)。
///
public FlowToolboxScope ToolboxScope { get; private set; } = FlowToolboxScope.Main;
private void InitCommands()
{
// 从 PluginLoader 构建工具箱(按作用域过滤/分组)
ToolboxGroups = new ObservableCollection();
var infos = PluginLoader.Instance.GetAllPluginInfos();
if (ToolboxScope != FlowToolboxScope.Main)
{
// 平台子流程编辑器(Halcon/Vpp/Vm):只取标记为子流程节点的插件,并按平台
// (PluginCategory: Halocn模块/Vpp模块/Vm模块)隔离 —— 各平台只见到自己的算子,
// 平台内按 VisionPlugin 枚举分组
PluginCategory platform;
switch (ToolboxScope)
{
case FlowToolboxScope.VisionVpp: platform = PluginCategory.Vpp模块; break;
case FlowToolboxScope.VisionVm: platform = PluginCategory.Vm模块; break;
default: platform = PluginCategory.Halocn模块; break;
}
var subInfos = infos.Where(i => i.IsSubFlowNode && i.Group == platform).ToList();
foreach (var g in subInfos.GroupBy(i => i.VisionCategory).OrderBy(g => g.Key))
{
ToolboxGroups.Add(new ToolboxGroup
{
GroupName = g.Key.ToString(),
Category = platform,
GroupIcon = VisionCategoryIconMap.GetIcon(g.Key),
Items = g.ToList()
});
}
}
else
{
// 主流程:排除子流程专用节点(保持既有行为——现有插件 IsSubFlowNode 均为 false,不受影响)
var mainInfos = infos.Where(i => !i.IsSubFlowNode).ToList();
foreach (var g in mainInfos.GroupBy(i => i.Group))
{
ToolboxGroups.Add(new ToolboxGroup
{
GroupName = g.Key.ToString(),
Category = g.Key,
GroupIcon = CategoryIconMap.GetIcon(g.Key),
Items = g.ToList()
});
}
}
ClearSelectionCommand = new DelegateCommand(() => SelectedNode = null);
DeleteSelectedCommand = new DelegateCommand(() =>
{
if (SelectedNode != null)
{
Graph.RemoveNode(SelectedNode.NodeId);
SelectedNode = null;
}
});
RunFlowCommand = new DelegateCommand(async () => await RunFlowAsync(), () => !IsRunning && !IsSubFlowEditor && !IsReadOnly);
StopFlowCommand = new DelegateCommand(() => StopFlow(), () => IsRunning);
// 导入/导出统一走命令(工具栏按钮与 Shell 右键菜单共用,避免重复代码)
ImportFlowCommand = new DelegateCommand(async () =>
{
if (IsReadOnly)
{
DialogHelper.Info("运行产品监控中,无法导入流程");
return;
}
var dlg = new Microsoft.Win32.OpenFileDialog
{
Filter = "流程文件|*.aas|所有文件|*.*",
Title = "导入流程"
};
if (dlg.ShowDialog() == true)
{
if (Graph != null && await ImportFlowAsync(Graph.GraphName, dlg.FileName) == true)
DialogHelper.Success("导入成功");
else
DialogHelper.Error("导入失败");
}
});
ExportFlowCommand = new DelegateCommand(() =>
{
if (Graph == null) return;
var dlg = new Microsoft.Win32.SaveFileDialog
{
Filter = "流程文件|*.aas|所有文件|*.*",
Title = "导出流程",
FileName = Graph.GraphName + ".aas"
};
if (dlg.ShowDialog() == true)
{
if (ExportFlow(Graph.GraphName, dlg.FileName) == true)
DialogHelper.Success("导出成功");
else
DialogHelper.Error("导出失败");
}
});
}
#endregion
#region 执行
private CancellationTokenSource _cts;
private FlowExecutor _executor;
public async Task RunFlowAsync()
{
if (IsRunning) return;
_cts = new CancellationTokenSource();
_executor = new FlowExecutor(Graph);
IsRunning = true;
RunFlowCommand.RaiseCanExecuteChanged();
StopFlowCommand.RaiseCanExecuteChanged();
_executor.ExecutionCompleted += (success) =>
{
IsRunning = false;
RunFlowCommand.RaiseCanExecuteChanged();
StopFlowCommand.RaiseCanExecuteChanged();
};
try
{
await _executor.ExecuteAsync(_cts.Token);
}
finally
{
// 兜底:即使 ExecutionCompleted 因 UI 线程繁忙被延迟、或执行器抛异常,
// 也保证运行态复位,避免"执行完仍被锁住、无法再次运行"。
IsRunning = false;
RunFlowCommand?.RaiseCanExecuteChanged();
StopFlowCommand?.RaiseCanExecuteChanged();
}
}
public void StopFlow()
{
_cts?.Cancel();
}
///
/// 仅运行单个节点(不启动后继)
///
public async Task RunSingleNodeAsync(FlowNode node)
{
if (IsRunning || IsReadOnly || node == null) return;
_cts = new CancellationTokenSource();
_executor = new FlowExecutor(Graph);
IsRunning = true;
RunFlowCommand?.RaiseCanExecuteChanged();
StopFlowCommand?.RaiseCanExecuteChanged();
_executor.ExecutionCompleted += (success) =>
{
IsRunning = false;
RunFlowCommand?.RaiseCanExecuteChanged();
StopFlowCommand?.RaiseCanExecuteChanged();
};
try
{
await _executor.ExecuteSingleNodeAsync(node, _cts.Token);
}
finally
{
IsRunning = false;
RunFlowCommand?.RaiseCanExecuteChanged();
StopFlowCommand?.RaiseCanExecuteChanged();
}
}
///
/// 从指定节点开始运行(向下流转)
///
public async Task RunFromNodeAsync(FlowNode node)
{
if (IsRunning || IsReadOnly || node == null) return;
_cts = new CancellationTokenSource();
_executor = new FlowExecutor(Graph);
IsRunning = true;
RunFlowCommand?.RaiseCanExecuteChanged();
StopFlowCommand?.RaiseCanExecuteChanged();
_executor.ExecutionCompleted += (success) =>
{
IsRunning = false;
RunFlowCommand?.RaiseCanExecuteChanged();
StopFlowCommand?.RaiseCanExecuteChanged();
};
try
{
await _executor.ExecuteFromNodeAsync(node, _cts.Token);
}
finally
{
IsRunning = false;
RunFlowCommand?.RaiseCanExecuteChanged();
StopFlowCommand?.RaiseCanExecuteChanged();
}
}
///
/// 右键属性 - 显示节点通用属性(非插件编辑器)
///
public void ShowNodeProperties(FlowNode node)
{
if (node == null) return;
string oldName = node.NodeName;
if (DialogHelper.EditProperties(node, $"节点属性 - {node.NodeName}"))
{
// 重命名后确保唯一性
if (node.NodeName != oldName)
{
string newName = node.NodeName;
int suffix = 1;
while (Graph.Nodes.Any(n => n != node && n.NodeName == newName))
newName = $"{node.NodeName}_{suffix++}";
if (newName != node.NodeName)
{
node.NodeName = newName;
DialogHelper.Info($"名称已存在,自动改为: {newName}");
}
}
}
}
#endregion
#region 方法
public void CreateNode(double x, double y, NodePluginInfo info)
{
string baseName = info.DisplayName;
int suffix = 1;
string candidate = baseName + suffix;
while (Graph.Nodes.Any(n => n.NodeName == candidate))
{
suffix++;
candidate = baseName + suffix;
}
var node = CreateNodeFromPlugin(candidate,Graph, info, x, y);
// 重名加序号
Graph.AddNode(node);
}
///
/// 双击节点 - 打开属性编辑器或插件自定义窗体
///
public void OpenNodeEditor(FlowNode node)
{
if (node == null) return;
// 异常分支节点:无属性可编辑,双击不弹任何窗
if (node.Category == NodeCategory.ExceptionBranch) return;
// 只读监控模式:禁止打开编辑器修改节点(允许选中查看结果)
if (IsReadOnly)
{
DialogHelper.Info("运行产品监控中,节点处于只读状态");
return;
}
var desc = PluginLoader.Instance.GetDescriptor(node.ToolName ?? node.PluginId);
if (desc == null) return;
var plugin = PluginLoader.Instance.CreateInstance(desc.DisplayName);
if (plugin == null) return;
if (node.PluginModel is IFlowNodePlugin savedModel)
plugin.GetModel = savedModel.GetModel;
PluginLoader.Instance.SelectFlow = node;
if (desc.HasCustomView)
{
try
{
var view = System.Activator.CreateInstance(desc.Attribute.ViewType) as System.Windows.FrameworkElement;
if (view != null)
{
view.DataContext = plugin.GetModel;
System.Action