using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Newtonsoft.Json; // 需通过NuGet安装:Install-Package Newtonsoft.Json
namespace Vision.Controls
{
#region 基础类型定义
public enum PortType { Input, Output }
public enum NodeStatus { Idle, Executing, Success, Error, Paused }
public class Port
{
public string Name { get; set; }
public PortType Type { get; set; }
public Point Position { get; set; } // 相对于节点的偏移
public Port ConnectedPort { get; set; } // 连接的目标端口
public VisionNode Parent { get; set; } // 所属节点
}
public class Connection
{
public Port StartPort { get; set; }
public Port EndPort { get; set; }
public Point StartPosition { get; set; } // 临时连接起点(绘制用)
public Point EndPosition { get; set; } // 临时连接终点(绘制用)
}
public class ExecutionEventArgs : EventArgs
{
public object Output { get; set; }
public Exception Error { get; set; }
}
public interface IExecutable
{
string UniqueId { get; }
NodeStatus Status { get; }
IDictionary<string, object> Parameters { get; set; }
object Execute(IDictionary<string, object> inputs);
Task<object> ExecuteAsync(IDictionary<string, object> inputs, CancellationToken ct);
event EventHandler<ExecutionEventArgs> ExecutionCompleted;
}
#endregion
#region 节点控件(VisionNode)
public partial class VisionNode : UserControl, IExecutable
{
public string Title { get; set; } = "Node";
public List<Port> InputPorts { get; } = new List<Port>();
public List<Port> OutputPorts { get; } = new List<Port>();
public string UniqueId { get; } = Guid.NewGuid().ToString();
public NodeStatus Status { get; private set; } = NodeStatus.Idle;
public IDictionary<string, object> Parameters { get; set; } = new Dictionary<string, object>();
public bool IsSelected { get; set; }
public event EventHandler<ExecutionEventArgs> ExecutionCompleted;
public VisionNode()
{
DoubleBuffered = true;
Size = new Size(200, 100);
// 初始化端口(左侧输入,右侧输出)
InputPorts.Add(new Port { Name = "In1", Type = PortType.Input, Position = new Point(10, 30), Parent = this });
InputPorts.Add(new Port { Name = "In2", Type = PortType.Input, Position = new Point(10, 60), Parent = this });
OutputPorts.Add(new Port { Name = "Out1", Type = PortType.Output, Position = new Point(180, 30), Parent = this });
OutputPorts.Add(new Port { Name = "Out2", Type = PortType.Output, Position = new Point(180, 60), Parent = this });
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// 绘制节点边框(状态色)
using (var pen = new Pen(GetStatusColor(), 2))
{
e.Graphics.DrawRectangle(pen, 0, 0, Width - 1, Height - 1);
}
// 绘制标题
e.Graphics.DrawString(Title, Font, Brushes.Black, new PointF(10, 10));
// 绘制端口(圆形)
foreach (var port in InputPorts.Concat(OutputPorts))
{
var portCenter = new Point(port.Position.X + 5, port.Position.Y + 5);
e.Graphics.FillEllipse(Brushes.Gray, portCenter.X - 5, portCenter.Y - 5, 10, 10);
}
// 绘制选中框
if (IsSelected)
{
using (var pen = new Pen(Color.Blue, 2) { DashStyle = DashStyle.Dash })
{
e.Graphics.DrawRectangle(pen, 0, 0, Width - 1, Height - 1);
}
}
}
private Color GetStatusColor()
{
return Status switch
{
NodeStatus.Executing => Color.Orange,
NodeStatus.Success => Color.Green,
NodeStatus.Error => Color.Red,
NodeStatus.Paused => Color.Yellow,
_ => Color.Gray
};
}
public virtual object Execute(IDictionary<string, object> inputs)
{
Status = NodeStatus.Executing;
try
{
Thread.Sleep(1000); // 模拟业务逻辑
var output = new { Timestamp = DateTime.Now };
Status = NodeStatus.Success;
ExecutionCompleted?.Invoke(this, new ExecutionEventArgs { Output = output });
return output;
}
catch (Exception ex)
{
Status = NodeStatus.Error;
ExecutionCompleted?.Invoke(this, new ExecutionEventArgs { Error = ex });
return null;
}
finally
{
Invalidate();
}
}
public virtual async Task<object> ExecuteAsync(IDictionary<string, object> inputs, CancellationToken ct)
{
Status = NodeStatus.Executing;
try
{
await Task.Delay(1500, ct); // 模拟异步操作
var output = new { Result = "Async Success" };
Status = NodeStatus.Success;
ExecutionCompleted?.Invoke(this, new ExecutionEventArgs { Output = output });
return output;
}
catch (OperationCanceledException)
{
Status = NodeStatus.Paused;
ExecutionCompleted?.Invoke(this, new ExecutionEventArgs { Error = new OperationCanceledException() });
return null;
}
catch (Exception ex)
{
Status = NodeStatus.Error;
ExecutionCompleted?.Invoke(this, new ExecutionEventArgs { Error = ex });
return null;
}
finally
{
Invalidate();
}
}
}
#endregion
#region 流程画布(WorkflowCanvas)
public partial class WorkflowCanvas : Panel
{
public List<VisionNode> Nodes { get; } = new List<VisionNode>();
public List<Connection> Connections { get; } = new List<Connection>();
// 交互状态
private VisionNode _draggingNode;
private Point _dragStart;
private Port _draggingPort;
private Connection _tempConnection;
private Rectangle _selectionRect;
private bool _isSelecting;
private float _scale = 1.0f;
// 命令历史
private Stack<ICommand> _undoStack = new Stack<ICommand>();
private Stack<ICommand> _redoStack = new Stack<ICommand>();
public WorkflowCanvas()
{
DoubleBuffered = true;
MouseDown += OnMouseDown;
MouseMove += OnMouseMove;
MouseUp += OnMouseUp;
MouseWheel += OnMouseWheel;
}
public void AddNode(VisionNode node)
{
Nodes.Add(node);
Controls.Add(node);
Invalidate();
}
public void RemoveNode(VisionNode node)
{
var connectionsToRemove = Connections.Where(c => c.StartPort.Parent == node || c.EndPort.Parent == node).ToList();
foreach (var conn in connectionsToRemove) Connections.Remove(conn);
Nodes.Remove(node);
Controls.Remove(node);
Invalidate();
}
#region 鼠标交互处理
private void OnMouseDown(object sender, MouseEventArgs e)
{
var scaledPoint = new Point((int)(e.X / _scale), (int)(e.Y / _scale));
_draggingNode = Nodes.FirstOrDefault(n => n.Bounds.Contains(scaledPoint));
if (_draggingNode != null)
{
_dragStart = scaledPoint;
_draggingPort = CheckPortClick(_draggingNode, scaledPoint);
if (_draggingPort != null)
{
_tempConnection = new Connection
{
StartPort = _draggingPort,
StartPosition = new Point(
_draggingNode.Location.X + _draggingPort.Position.X + 5,
_draggingNode.Location.Y + _draggingPort.Position.Y + 5)
};
}
}
else if (e.Button == MouseButtons.Left)
{
_isSelecting = true;
_selectionRect = new Rectangle(scaledPoint, Size.Empty);
}
}
private Port CheckPortClick(VisionNode node, Point localPoint)
{
foreach (var port in node.InputPorts.Concat(node.OutputPorts))
{
var portCenter = new Point(port.Position.X + 5, port.Position.Y + 5);
if (Math.Sqrt(Math.Pow(localPoint.X - portCenter.X, 2) + Math.Pow(localPoint.Y - portCenter.Y, 2)) <= 5)
return port;
}
return null;
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
var scaledPoint = new Point((int)(e.X / _scale), (int)(e.Y / _scale));
if (_draggingNode != null && _draggingPort == null)
{
int dx = scaledPoint.X - _dragStart.X;
int dy = scaledPoint.Y - _dragStart.Y;
_draggingNode.Location = new Point(_draggingNode.Location.X + dx, _draggingNode.Location.Y + dy);
_dragStart = scaledPoint;
Invalidate();
}
else if (_tempConnection != null)
{
_tempConnection.EndPosition = new Point((int)(e.X / _scale), (int)(e.Y / _scale));
Invalidate();
}
else if (_isSelecting)
{
_selectionRect.Width = scaledPoint.X - _selectionRect.X;
_selectionRect.Height = scaledPoint.Y - _selectionRect.Y;
Invalidate();
}
}
private void OnMouseUp(object sender, MouseEventArgs e)
{
var scaledPoint = new Point((int)(e.X / _scale), (int)(e.Y / _scale));
if (_tempConnection != null)
{
var targetNode = Nodes.FirstOrDefault(n => n.Bounds.Contains(scaledPoint));
var targetPort = targetNode != null ? CheckPortClick(targetNode, scaledPoint) : null;
if (targetPort != null && _tempConnection.StartPort.Type != targetPort.Type)
{
_tempConnection.EndPort = targetPort;
Connections.Add(_tempConnection);
_tempConnection.StartPort.ConnectedPort = targetPort;
targetPort.ConnectedPort = _tempConnection.StartPort;
}
_tempConnection = null;
_draggingPort = null;
}
else if (_isSelecting)
{
_isSelecting = false;
foreach (var node in Nodes)
node.IsSelected = _selectionRect.IntersectsWith(node.Bounds);
}
_draggingNode = null;
Invalidate();
}
private void OnMouseWheel(object sender, MouseEventArgs e)
{
if (ModifierKeys == Keys.Control)
{
_scale = Math.Max(0.1f, Math.Min(5.0f, _scale * (e.Delta > 0 ? 1.1f : 0.9f)));
Invalidate();
}
}
#endregion
#region 绘制逻辑
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.ScaleTransform(_scale, _scale);
// 绘制节点
foreach (var node in Nodes)
e.Graphics.DrawImageUnscaled(node.BackgroundImage, node.Location);
// 绘制连线(贝塞尔曲线)
foreach (var conn in Connections)
{
Point start = conn.StartPort != null
? new Point(conn.StartPort.Parent.Location.X + conn.StartPort.Position.X + 5,
conn.StartPort.Parent.Location.Y + conn.StartPort.Position.Y + 5)
: conn.StartPosition;
Point end = conn.EndPort != null
? new Point(conn.EndPort.Parent.Location.X + conn.EndPort.Position.X + 5,
conn.EndPort.Parent.Location.Y + conn.EndPort.Position.Y + 5)
: conn.EndPosition;
Point ctrl1 = new Point((start.X + end.X) / 2, start.Y - 50);
Point ctrl2 = new Point((start.X + end.X) / 2, end.Y + 50);
e.Graphics.DrawBezier(Pens.Black, start, ctrl1, ctrl2, end);
}
// 绘制临时连接
if (_tempConnection != null)
{
using (var pen = new Pen(Color.Red, 1) { DashStyle = DashStyle.Dash })
{
e.Graphics.DrawLine(pen, _tempConnection.StartPosition, _tempConnection.EndPosition);
}
}
// 绘制框选区域
if (_isSelecting)
{
using (var pen = new Pen(Color.Blue, 1) { DashStyle = DashStyle.Dash })
{
e.Graphics.DrawRectangle(pen, _selectionRect);
}
}
e.Graphics.ResetTransform();
}
#endregion
#region 序列化与反序列化
public string Serialize()
{
var data = new
{
Nodes = Nodes.Select(n => new
{
Title = n.Title,
Location = n.Location,
InputPorts = n.InputPorts.Select(p => new { p.Name, p.Type, p.Position }),
OutputPorts = n.OutputPorts.Select(p => new { p.Name, p.Type, p.Position }),
Parameters = n.Parameters,
IsSelected = n.IsSelected
}).ToList(),
Connections = Connections.Select(c => new
{
StartPort = c.StartPort != null ? new { c.StartPort.Parent.Title, c.StartPort.Name, c.StartPort.Type } : null,
EndPort = c.EndPort != null ? new { c.EndPort.Parent.Title, c.EndPort.Name, c.EndPort.Type } : null,
StartPosition = c.StartPosition,
EndPosition = c.EndPosition
}).ToList(),
Scale = _scale
};
return JsonConvert.SerializeObject(data, Formatting.Indented);
}
public void Deserialize(string json)
{
var data = JsonConvert.DeserializeAnonymousType(json, new
{
Nodes = new List<NodeDTO>(),
Connections = new List<ConnectionDTO>(),
Scale = 1.0f
});
Nodes.Clear();
Connections.Clear();
Controls.Clear();
_scale = data.Scale;
var nodeMap = new Dictionary<string, VisionNode>();
foreach (var nodeDTO in data.Nodes)
{
var node = new VisionNode { Title = nodeDTO.Title, Location = nodeDTO.Location, IsSelected = nodeDTO.IsSelected };
node.Parameters = nodeDTO.Parameters.ToDictionary(k => k.Key, v => v.Value);
node.InputPorts.Clear();
node.InputPorts.AddRange(nodeDTO.InputPorts.Select(p => new Port { Name = p.Name, Type = p.Type, Position = p.Position, Parent = node }));
node.OutputPorts.Clear();
node.OutputPorts.AddRange(nodeDTO.OutputPorts.Select(p => new Port { Name = p.Name, Type = p.Type, Position = p.Position, Parent = node }));
AddNode(node);
nodeMap[node.Title] = node;
}
foreach (var connDTO in data.Connections)
{
if (connDTO.StartPort == null || connDTO.EndPort == null) continue;
if (!nodeMap.TryGetValue(connDTO.StartPort.Title, out var startNode) ||
!nodeMap.TryGetValue(connDTO.EndPort.Title, out var endNode)) continue;
var startPort = startNode.InputPorts.Concat(startNode.OutputPorts)
.First(p => p.Name == connDTO.StartPort.Name && p.Type == connDTO.StartPort.Type);
var endPort = endNode.InputPorts.Concat(endNode.OutputPorts)
.First(p => p.Name == connDTO.EndPort.Name && p.Type == connDTO.EndPort.Type);
var connection = new Connection
{
StartPort = startPort,
EndPort = endPort,
StartPosition = connDTO.StartPosition,
EndPosition = connDTO.EndPosition
};
Connections.Add(connection);
startPort.ConnectedPort = endPort;
endPort.ConnectedPort = startPort;
}
Invalidate();
}
private class NodeDTO
{
public string Title { get; set; }
public Point Location { get; set; }
public List<PortDTO> InputPorts { get; set; }
public List<PortDTO> OutputPorts { get; set; }
public Dictionary<string, object> Parameters { get; set; }
public bool IsSelected { get; set; }
}
private class PortDTO
{
public string Name { get; set; }
public PortType Type { get; set; }
public Point Position { get; set; }
}
private class ConnectionDTO
{
public PortRef StartPort { get; set; }
public PortRef EndPort { get; set; }
public Point StartPosition { get; set; }
public Point EndPosition { get; set; }
}
private class PortRef
{
public string Title { get; set; }
public string Name { get; set; }
public PortType Type { get; set; }
}
#endregion
#region 命令模式(撤销/重做)
public interface ICommand
{
void Execute();
void Undo();
}
public void ExecuteCommand(ICommand command)
{
command.Execute();
_undoStack.Push(command);
_redoStack.Clear();
}
public void Undo()
{
if (_undoStack.Count > 0)
{
var cmd = _undoStack.Pop();
cmd.Undo();
_redoStack.Push(cmd);
Invalidate();
}
}
public void Redo()
{
if (_redoStack.Count > 0)
{
var cmd = _redoStack.Pop();
cmd.Execute();
_undoStack.Push(cmd);
Invalidate();
}
}
public class AddNodeCommand : ICommand
{
private WorkflowCanvas _canvas;
private VisionNode _node;
public AddNodeCommand(WorkflowCanvas canvas, VisionNode node)
{
_canvas = canvas;
_node = node;
}
public void Execute() => _canvas.AddNode(_node);
public void Undo() => _canvas.RemoveNode(_node);
}
#endregion
}
#endregion
#region 执行引擎(WorkflowRunner)
public class WorkflowRunner
{
private WorkflowCanvas _canvas;
private Dictionary<string, object> _dataCache = new Dictionary<string, object>();
private CancellationTokenSource _cts;
public WorkflowRunner(WorkflowCanvas canvas) => _canvas = canvas;
public async Task RunAsync()
{
_cts = new CancellationTokenSource();
var nodes = _canvas.Nodes.OfType<IExecutable>().ToList();
var dependencyGraph = BuildDependencyGraph(nodes);
var sortedNodes = TopologicalSort(dependencyGraph);
var tasks = sortedNodes.Select(node => Task.Run(async () =>
{
var inputs = CollectInputs(node);
var output = await node.ExecuteAsync(inputs, _cts.Token);
_dataCache[node.UniqueId] = output;
}, _cts.Token));
await Task.WhenAll(tasks);
}
private Dictionary<IExecutable, List<IExecutable>> BuildDependencyGraph(List<IExecutable> nodes)
{
var graph = new Dictionary<IExecutable, List<IExecutable>>();
foreach (var node in nodes)
{
graph[node] = new List<IExecutable>();
var visionNode = node as VisionNode;
foreach (var port in visionNode.InputPorts)
{
if (port.ConnectedPort != null && port.ConnectedPort.Parent is IExecutable dependency)
graph[dependency].Add(node);
}
}
return graph;
}
private List<IExecutable> TopologicalSort(Dictionary<IExecutable, List<IExecutable>> graph)
{
var inDegree = graph.ToDictionary(k => k.Key, v => v.Value.Count);
var queue = new Queue<IExecutable>(graph.Where(kv => kv.Value.Count == 0).Select(kv => kv.Key));
var result = new List<IExecutable>();
while (queue.Count > 0)
{
var node = queue.Dequeue();
result.Add(node);
foreach (var neighbor in graph[node])
if (--inDegree[neighbor] == 0) queue.Enqueue(neighbor);
}
return result;
}
private IDictionary<string, object> CollectInputs(IExecutable node)
{
var inputs = new Dictionary<string, object>();
var visionNode = node as VisionNode;
foreach (var port in visionNode.InputPorts)
{
if (port.ConnectedPort != null &&
port.ConnectedPort.Parent is IExecutable source &&
_dataCache.TryGetValue(source.UniqueId, out var data))
{
inputs[port.Name] = data;
}
}
return inputs;
}
public void Cancel() => _cts?.Cancel();
}
#endregion
#region 示例窗体(MainForm)
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
public partial class MainForm : Form
{
private WorkflowCanvas _canvas;
private WorkflowRunner _runner;
public MainForm()
{
InitializeComponent();
_canvas = new WorkflowCanvas { Dock = DockStyle.Fill };
Controls.Add(_canvas);
// 示例节点
var node1 = new VisionNode { Title = "Source", Location = new Point(100, 100) };
var node2 = new VisionNode { Title = "Processor", Location = new Point(300, 200) };
_canvas.AddNode(node1);
_canvas.AddNode(node2);
// 按钮区
var buttonPanel = new Panel { Dock = DockStyle.Top, Height = 40 };
Controls.Add(buttonPanel);
var btnRun = new Button { Text = "Run Workflow", Location = new Point(10, 10) };
btnRun.Click += async (s, e) =>
{
_runner = new WorkflowRunner(_canvas);
await _runner.RunAsync();
};
buttonPanel.Controls.Add(btnRun);
var btnSave = new Button { Text = "Save", Location = new Point(120, 10) };
btnSave.Click += (s, e) => File.WriteAllText("workflow.json", _canvas.Serialize());
buttonPanel.Controls.Add(btnSave);
var btnLoad = new Button { Text = "Load", Location = new Point(190, 10) };
btnLoad.Click += (s, e) => _canvas.Deserialize(File.ReadAllText("workflow.json"));
buttonPanel.Controls.Add(btnLoad);
var btnUndo = new Button { Text = "Undo", Location = new Point(260, 10) };
btnUndo.Click += (s, e) => _canvas.Undo();
buttonPanel.Controls.Add(btnUndo);
var btnRedo = new Button { Text = "Redo", Location = new Point(330, 10) };
btnRedo.Click += (s, e) => _canvas.Redo();
buttonPanel.Controls.Add(btnRedo);
}
private void InitializeComponent()
{
this.SuspendLayout();
this.ClientSize = new Size(800, 600);
this.Text = "Workflow Editor";
this.ResumeLayout(false);
}
}
#endregion
}
优化