using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
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 System.Xml;
using Newtonsoft.Json;
namespace Vision.Controls
{
#region 基础接口定义
public interface IDrawable
{
Rectangle Bounds { get; }
void Draw(Graphics g);
event EventHandler StyleChanged;
}
public interface IPlugin
{
string Name { get; }
VisualElement CreateElement();
}
public interface ICommand
{
void Execute();
void Undo();
}
public interface ISerializer
{
string Format { get; }
string Serialize(WorkflowCanvas canvas);
void Deserialize(WorkflowCanvas canvas, string data);
}
#endregion
#region 核心实体类
public class Port
{
public string Name { get; set; }
public PortType Type { get; set; }
public Point Position { get; set; } // 相对于节点的偏移
public Port ConnectedPort { get; set; } // 连接的目标端口
public VisualElement Parent { get; set; } // 所属元素
public Point ScreenPosition
{
get
{
// 确保 Parent 已初始化,且 Bounds 有效
if (Parent == null) return Position;
return new Point(
Parent.Bounds.X + Position.X, // 用 Bounds(逻辑坐标)替代 Location
Parent.Bounds.Y + Position.Y
);
}
}
}
public enum PortType { Input, Output }
public abstract class VisualElement : UserControl, IDrawable
{
public virtual Rectangle Bounds {
get
{
scale = WorkflowCanvas.Instance.Scale;
return new Rectangle(
(int)(LogicLocation.X * scale),
(int)(LogicLocation.Y * scale),
(int)(LogicSize.Width * scale),
(int)(LogicSize.Height * scale)
);
}
}
public abstract void Draw(Graphics g);
public event EventHandler StyleChanged;
public virtual string Title { get; set; } = "DefaultTitle";
protected void OnStyleChanged() => StyleChanged?.Invoke(this, EventArgs.Empty);
public bool IsSelected { get; set; } = false;
public Point LogicLocation { get; set; } = new Point(0, 0);
public Size LogicSize { get; set; } = new Size(100, 50);
public float scale {get; set; }
// 新增:Z轴层级(值越大越靠前)
public int ZIndex { get; set; } = 0;
[Browsable(false)]
public new Point Location
{
get => LogicLocation;
set => LogicLocation = value;
}
[Browsable(false)]
public new Size Size
{
get => LogicSize;
set => LogicSize = value;
}
}
public class Connection : IDrawable
{
public Port StartPort { get; set; }
public Port EndPort { get; set; }
public Point StartPosition { get; set; } // 临时连接起点
public Point EndPosition { get; set; } // 临时连接终点
public Rectangle Bounds
{
get
{
if (StartPort != null && EndPort != null)
{
var minX = Math.Min(StartPort.ScreenPosition.X, EndPort.ScreenPosition.X);
var maxX = Math.Max(StartPort.ScreenPosition.X, EndPort.ScreenPosition.X);
var minY = Math.Min(StartPort.ScreenPosition.Y, EndPort.ScreenPosition.Y);
var maxY = Math.Max(StartPort.ScreenPosition.Y, EndPort.ScreenPosition.Y);
return new Rectangle(minX - 10, minY - 10, maxX - minX + 20, maxY - minY + 20);
}
return new Rectangle(StartPosition.X, StartPosition.Y,
EndPosition.X - StartPosition.X, EndPosition.Y - StartPosition.Y);
}
}
public void Draw(Graphics g)
{
var scale = WorkflowCanvas.Instance.Scale;
g.ScaleTransform(scale, scale); // 应用画布缩放
Point start = StartPort?.ScreenPosition ?? StartPosition;
Point end = EndPort?.ScreenPosition ?? EndPosition;
g.DrawLine(Pens.Black, start, end);
// 绘制箭头(末端)
DrawArrow(g, end, start);
}
private void DrawArrow(Graphics g, Point end, Point start)
{
// 计算箭头方向(从 start 到 end 的向量)
float dx = end.X - start.X;
float dy = end.Y - start.Y;
float angle = (float)Math.Atan2(dy, dx);
int arrowSize = (int)(10 * WorkflowCanvas.Instance.Scale); // 箭头大小适配缩放
// 计算箭头的两个侧点
Point arrowPoint1 = new Point(
(int)(end.X - arrowSize * Math.Cos(angle - Math.PI / 6)),
(int)(end.Y - arrowSize * Math.Sin(angle - Math.PI / 6))
);
Point arrowPoint2 = new Point(
(int)(end.X - arrowSize * Math.Cos(angle + Math.PI / 6)),
(int)(end.Y - arrowSize * Math.Sin(angle + Math.PI / 6))
);
// 绘制箭头(填充三角形)
g.FillPolygon(Brushes.Black, new Point[] { end, arrowPoint1, arrowPoint2 });
}
public event EventHandler StyleChanged;
}
public partial class VisionNode : VisualElement
{
public string Title { get; set; } = "Node";
public List<Port> InputPorts { get; } = new List<Port>();
public List<Port> OutputPorts { get; } = new List<Port>();
public IDictionary<string, object> Parameters { get; set; } = new Dictionary<string, object>();
public bool IsSelected { get; set; }
public override Rectangle Bounds => new Rectangle(Location, Size);
public VisionNode()
{
DoubleBuffered = true;
Size = new Size(100, 50);
BackColor = Color.Transparent;
InputPorts.Add(new Port { Name = "In1", Type = PortType.Input, Position = new Point(Size.Width/2, this.Location.Y), Parent = this });
OutputPorts.Add(new Port { Name = "Out1", Type = PortType.Output, Position = new Point(Size.Width / 2,this.Location.Y+ Size.Height), Parent = this });
}
public override void Draw(Graphics g)
{
// 绘制节点框等其他内容(可再处理缩放)
var pen = StyleManager.Instance.NodeBorderPen;
g.DrawRectangle(pen, Bounds);
// 若有全局缩放,先重置变换(避免文字被缩放影响)
g.ResetTransform();
// 保存原始 Graphics 状态
var state = g.Save();
var font = StyleManager.Instance.NodeFont;
// 用物理坐标绘制文字(假设 _scale 是画布缩放因子)
SizeF textSize = g.MeasureString(Title, font);
var physicalX = Bounds.X + (Bounds.Width - textSize.Width) / 2;
var physicalY = Bounds.Y + (Bounds.Height - textSize.Height) / 2;
g.DrawString(Title, font, Brushes.Black, new PointF(physicalX, physicalY));
g.Restore(state);
}
protected override void OnLocationChanged(EventArgs e)
{
base.OnLocationChanged(e);
OnStyleChanged();
}
}
#endregion
#region 性能优化模块
public class QuadTree
{
private readonly Rectangle _bounds;
private List<IDrawable> _objects = new List<IDrawable>();
private QuadTree[] _children;
private const int MAX_OBJECTS = 10;
private const int MIN_SIZE = 100;
public QuadTree(Rectangle bounds) => _bounds = bounds;
public Rectangle Bounds => _bounds;
public void Insert(IDrawable obj)
{
if (_children != null)
{
int index = GetChildIndex(obj.Bounds);
if (index != -1)
{
_children[index].Insert(obj);
return;
}
}
_objects.Add(obj);
if (_objects.Count > MAX_OBJECTS && _bounds.Width > MIN_SIZE)
{
Subdivide();
var objs = new List<IDrawable>(_objects);
_objects.Clear();
foreach (var o in objs)
{
int index = GetChildIndex(o.Bounds);
if (index != -1) _children[index].Insert(o);
else _objects.Add(o);
}
}
}
private void Subdivide()
{
int x = _bounds.X, y = _bounds.Y;
int w = _bounds.Width / 2, h = _bounds.Height / 2;
_children = new QuadTree[4]
{
new QuadTree(new Rectangle(x, y, w, h)),
new QuadTree(new Rectangle(x + w, y, w, h)),
new QuadTree(new Rectangle(x, y + h, w, h)),
new QuadTree(new Rectangle(x + w, y + h, w, h))
};
}
private int GetChildIndex(Rectangle bounds)
{
if (_children == null) return -1;
int midX = _bounds.X + _bounds.Width / 2;
int midY = _bounds.Y + _bounds.Height / 2;
bool top = bounds.Y < midY;
bool bottom = bounds.Bottom > midY;
bool left = bounds.X < midX;
bool right = bounds.Right > midX;
if (top && left) return 0;
if (top && right) return 1;
if (bottom && left) return 2;
if (bottom && right) return 3;
return -1;
}
public void Clear()
{
_objects.Clear();
if (_children != null)
{
for (int i = 0; i < _children.Length; i++)
{
_children[i].Clear();
_children[i] = null;
}
_children = null;
}
}
public List<IDrawable> Query(Rectangle area)
{
var result = new List<IDrawable>();
if (!_bounds.IntersectsWith(area)) return result;
result.AddRange(_objects.Where(o => o.Bounds.IntersectsWith(area)));
if (_children != null)
{
foreach (var child in _children)
result.AddRange(child.Query(area));
}
return result;
}
public void Query(List<IDrawable> result, Rectangle area)
{
if (!_bounds.IntersectsWith(area)) return;
result.AddRange(_objects.Where(o => o.Bounds.IntersectsWith(area)));
if (_children != null)
{
foreach (var child in _children)
{
child.Query(result, area);
}
}
}
}
// 增强 ForceDirectedLayout 类
public class ForceDirectedLayout
{
private const float Repulsion = 10000f; // 排斥力强度
private const float Attraction = 0.1f; // 吸引力强度
private const float Damping = 0.9f; // 阻尼系数
private const int MaxIterations = 50; // 最大迭代次数
// 存储节点速度
private Dictionary<VisualElement, PointF> _velocities = new Dictionary<VisualElement, PointF>();
public void Layout(IEnumerable<VisualElement> nodes, IEnumerable<Connection> connections)
{
var nodeList = nodes.ToList();
InitializeVelocities(nodeList);
// 迭代计算力和位置
for (int i = 0; i < MaxIterations; i++)
{
ApplyForces(nodeList, connections);
UpdatePositions(nodeList);
}
}
private void InitializeVelocities(List<VisualElement> nodes)
{
_velocities.Clear();
foreach (var node in nodes)
{
_velocities[node] = new PointF(0, 0);
}
}
private void ApplyForces(List<VisualElement> nodes, IEnumerable<Connection> connections)
{
// 计算节点间排斥力
foreach (var node in nodes)
{
foreach (var other in nodes.Where(n => n != node))
{
float dx = node.LogicLocation.X - other.LogicLocation.X;
float dy = node.LogicLocation.Y - other.LogicLocation.Y;
float distance = (float)Math.Sqrt(dx * dx + dy * dy);
if (distance < 1) distance = 1; // 避免除零
float force = Repulsion / (distance * distance);
// 更新速度
_velocities[node] = new PointF(
_velocities[node].X + dx / distance * force,
_velocities[node].Y + dy / distance * force
);
}
}
// 计算连接间吸引力
foreach (var conn in connections)
{
var startNode = conn.StartPort?.Parent;
var endNode = conn.EndPort?.Parent;
if (startNode != null && endNode != null)
{
float dx = endNode.LogicLocation.X - startNode.LogicLocation.X;
float dy = endNode.LogicLocation.Y - startNode.LogicLocation.Y;
float distance = (float)Math.Sqrt(dx * dx + dy * dy);
float force = Attraction * distance;
// 更新速度(吸引力方向相反)
_velocities[startNode] = new PointF(
_velocities[startNode].X + dx * force,
_velocities[startNode].Y + dy * force
);
_velocities[endNode] = new PointF(
_velocities[endNode].X - dx * force,
_velocities[endNode].Y - dy * force
);
}
}
}
private void UpdatePositions(List<VisualElement> nodes)
{
foreach (var node in nodes)
{
// 应用阻尼
_velocities[node] = new PointF(
_velocities[node].X * Damping,
_velocities[node].Y * Damping
);
// 更新位置
node.LogicLocation = new Point(
(int)(node.LogicLocation.X + _velocities[node].X),
(int)(node.LogicLocation.Y + _velocities[node].Y)
);
}
}
}
#endregion
#region 样式与主题管理
public class StyleManager
{
public event EventHandler StyleChanged;
public static StyleManager Instance { get; } = new StyleManager();
public Pen NodeBorderPen { get; private set; } = new Pen(Color.Black, 2);
public Font NodeFont { get; private set; } = new Font("Segoe UI", 16);
public float ZoomThreshold { get; set; } = 0.8f; // LOD阈值
private StyleManager() { }
public void ApplyDarkTheme()
{
NodeBorderPen.Color = Color.White;
NodeFont = new Font("Segoe UI", 10, FontStyle.Bold);
foreach (var element in WorkflowCanvas.Instance.Elements)
OnStyleChanged();
}
public bool ShowPortsWhenZoomed(float scale) => scale > ZoomThreshold;
public virtual void OnStyleChanged()
{
StyleChanged?.Invoke(this, EventArgs.Empty);
}
}
#endregion
#region 命令模式与撤销、重做
public partial class WorkflowCanvas : Panel
{
public List<VisualElement> Elements { get; } = new List<VisualElement>();
public List<Connection> Connections { get; } = new List<Connection>();
private QuadTree _quadTree;
private Rectangle _dirtyRegion = Rectangle.Empty;
private ForceDirectedLayout _layoutEngine = new ForceDirectedLayout();
private Stack<ICommand> _undoStack = new Stack<ICommand>();
private Stack<ICommand> _redoStack = new Stack<ICommand>();
private float _scale = 1.0f;
private float _targetScale = 1.0f;
private VisualElement _draggingElement;
private Point _dragStart;
private Port _draggingPort;
private PointF _zoomCenter;
private PointF _panOffset = PointF.Empty;
private Connection _tempConnection;
private Rectangle _selectionRect;
private bool _isSelecting;
private System.Windows.Forms.Timer _zoomTimer;
public float Scale
{
get => _scale;
private set
{
_scale = Math.Max(0.1f, Math.Min(5.0f, value)); // 限制范围
Invalidate(); // 更新画布
}
}
// 添加公共方法来安全地修改缩放比例
public void SetScale(float newScale)
{
Scale = newScale;
}
public static WorkflowCanvas Instance { get; private set; }
public WorkflowCanvas()
{
Instance = this;
DoubleBuffered = true;
MouseDown += OnMouseDown;
MouseMove += OnMouseMove;
MouseUp += OnMouseUp;
MouseWheel += OnMouseWheel;
_quadTree = new QuadTree(new Rectangle(0, 0, 10000, 10000));
_zoomTimer = new System.Windows.Forms.Timer { Interval = 16 };
_zoomTimer.Tick += (sender, e) => ZoomTick();
}
// 执行自动布局
public void AutoLayout()
{
_layoutEngine.Layout(Elements, Connections);
Invalidate();
}
// 添加布局命令(可选)
public class LayoutCommand : ICommand
{
private readonly WorkflowCanvas _canvas;
private Dictionary<VisualElement, Point> _oldPositions = new Dictionary<VisualElement, Point>();
public LayoutCommand(WorkflowCanvas canvas)
{
_canvas = canvas;
// 保存原始位置用于撤销
foreach (var element in canvas.Elements)
{
_oldPositions[element] = element.LogicLocation;
}
}
public void Execute() => _canvas.AutoLayout();
public void Undo()
{
foreach (var element in _canvas.Elements)
{
if (_oldPositions.TryGetValue(element, out var pos))
{
element.LogicLocation = pos;
}
}
_canvas.Invalidate();
}
}
public void ZoomTo(float newScale, Point? zoomCenter = null, bool animated = true)
{
PointF oldCenter = _zoomCenter==null ? new PointF(Width / 2f, Height / 2f) : _zoomCenter;
PointF oldLogicalCenter = new PointF(oldCenter.X / _scale, oldCenter.Y / _scale);
_targetScale = Math.Max(0.1f, Math.Min(5.0f, newScale));
if (animated && Math.Abs(_scale - _targetScale) > 0.05f)
{
_zoomCenter = oldLogicalCenter;
_zoomTimer.Start();
}
else
{
_scale = _targetScale;
ApplyZoomWithFixedPoint(oldLogicalCenter);
Invalidate();
}
}
private void ZoomTick()
{
// 缓动算法:平滑过渡到目标缩放值
_scale += (_targetScale - _scale) * 0.2f;
// 接近目标时停止动画
if (Math.Abs(_scale - _targetScale) < 0.01f)
{
_scale = _targetScale;
_zoomTimer.Stop();
}
ApplyZoomWithFixedPoint(_zoomCenter);
Invalidate();
}
private void ApplyZoomWithFixedPoint(PointF logicalCenter)
{
// 计算缩放后的偏移,保持逻辑中心点不变
float dx = logicalCenter.X * (_scale - WorkflowCanvas.Instance.Scale);
float dy = logicalCenter.Y * (_scale - WorkflowCanvas.Instance.Scale);
// 调整所有元素位置(需根据实际坐标系调整)
foreach (var element in Elements)
{
element.LogicLocation = new Point(
(int)(element.LogicLocation.X - dx),
(int)(element.LogicLocation.Y - dy)
);
}
}
public void AddElement(VisualElement element)
{
var newBounds = _quadTree.Bounds;
// 确保元素位置在四叉树范围内,否则扩容
if (element.Location.X < 0 || element.Location.Y < 0
|| element.Right > newBounds.Right
|| element.Bottom > newBounds.Bottom)
{
ResizeQuadTreeBounds(element);
}
Elements.Add(element);
Controls.Add(element);
element.Visible = true; // 确保可见
_quadTree.Insert(element);
InvalidateRegion(element.Bounds);
}
public void AddConnection(Connection conn)
{
Connections.Add(conn);
_quadTree.Insert(conn); // 关键:插入四叉树,确保 OnPaint 能查询到
InvalidateRegion(conn.Bounds);
}
private void ResizeQuadTreeBounds(VisualElement element)
{
var newBounds = _quadTree.Bounds;
newBounds.X = Math.Min(newBounds.X, element.Location.X);
newBounds.Y = Math.Min(newBounds.Y, element.Location.Y);
newBounds.Width = Math.Max(newBounds.Width, element.Right - newBounds.X);
newBounds.Height = Math.Max(newBounds.Height, element.Bottom - newBounds.Y);
_quadTree = new QuadTree(newBounds); // 重建四叉树(简易方式,生产需迁移元素)
// 迁移已有元素到新四叉树...
}
public void RemoveElement(VisualElement element)
{
var connectionsToRemove = Connections.Where(c => c.StartPort?.Parent == element || c.EndPort?.Parent == element).ToList();
foreach (var conn in connectionsToRemove) Connections.Remove(conn);
Elements.Remove(element);
Controls.Remove(element);
InvalidateRegion(element.Bounds);
}
public void InvalidateRegion(Rectangle region)
{
_dirtyRegion = Rectangle.Union(_dirtyRegion, region);
Invalidate();
}
#region 交互处理
private void OnMouseDown(object sender, MouseEventArgs e)
{
var scaledPoint = new Point((int)(e.X / _scale), (int)(e.Y / _scale));
_draggingElement = Elements.FirstOrDefault(n => n.Bounds.Contains(scaledPoint));
if (_draggingElement != null)
{
_dragStart = scaledPoint;
_draggingPort = null; // 拖动节点时不处理端口
_tempConnection = null;
}
else
{
_draggingPort = CheckPortClick(_draggingElement, scaledPoint);
if (_draggingPort != null)
{
_tempConnection = new Connection
{
StartPort = _draggingPort,
StartPosition = _draggingPort.ScreenPosition
};
}
else if (e.Button == MouseButtons.Left)
{
_isSelecting = true;
_selectionRect = new Rectangle(scaledPoint, Size.Empty);
}
}
}
private Port CheckPortClick(VisualElement element, Point localPoint)
{
if (element is VisionNode node)
{
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 (_draggingElement != null && _draggingPort == null)
{
int dx = scaledPoint.X - _dragStart.X;
int dy = scaledPoint.Y - _dragStart.Y;
// 更新逻辑坐标(带边界限制)
int maxLogicX = 10000 - _draggingElement.LogicSize.Width;
int maxLogicY = 10000 - _draggingElement.LogicSize.Height;
_draggingElement.Location = new Point
(_draggingElement.Location.X + dx < 0 ? 0 :
_draggingElement.Location.X + dx > maxLogicX ? maxLogicX :
_draggingElement.Location.X + dx,
_draggingElement.Location.Y + dy < 0 ? 0 :
_draggingElement.LogicLocation.Y + dy > maxLogicY ? maxLogicY :
_draggingElement.LogicLocation.Y + dy);
_dragStart = scaledPoint;
// 标记脏区域(旧位置 + 新位置)
var oldBounds = new Rectangle(
_draggingElement.Bounds.X - (int)(dx * _scale),
_draggingElement.Bounds.Y - (int)(dy * _scale),
_draggingElement.Bounds.Width,
_draggingElement.Bounds.Height
);
InvalidateRegion(_draggingElement.Bounds);
}
else if (_tempConnection != null)
{
_tempConnection.EndPosition = scaledPoint;
InvalidateRegion(_tempConnection.Bounds);
}
else if (_isSelecting)
{
_selectionRect.Width = scaledPoint.X - _selectionRect.X;
_selectionRect.Height = scaledPoint.Y - _selectionRect.Y;
InvalidateRegion(_selectionRect);
}
}
private void OnMouseUp(object sender, MouseEventArgs e)
{
var scaledPoint = new Point((int)(e.X / _scale), (int)(e.Y / _scale));
if (_tempConnection != null)
{
var targetElement = Elements.FirstOrDefault(n => n.Bounds.Contains(scaledPoint));
var targetPort = targetElement != null ? CheckPortClick(targetElement, scaledPoint) : null;
if (targetPort != null && _tempConnection.StartPort.Type != targetPort.Type)
{
_tempConnection.EndPort = targetPort;
AddConnection(_tempConnection); // 改用 AddConnection
_tempConnection.StartPort.ConnectedPort = targetPort;
targetPort.ConnectedPort = _tempConnection.StartPort;
InvalidateRegion(_tempConnection.Bounds);
}
_tempConnection = null;
_draggingPort = null;
}
else if (_isSelecting)
{
_isSelecting = false;
foreach (var element in Elements)
element.IsSelected = _selectionRect.IntersectsWith(element.Bounds);
InvalidateRegion(_selectionRect);
}
_draggingElement = null;
}
private void OnMouseWheel(object sender, MouseEventArgs e)
{
PointF mouseLogicPos = new PointF(
(e.Location.X - _panOffset.X) / _scale,
(e.Location.Y - _panOffset.Y) / _scale
);
float oldScale = _scale;
_scale *= e.Delta > 0 ? 1.1f : 0.9f;
_scale = Math.Max(0.1f, Math.Min(5.0f, _scale));
Invalidate();
}
#endregion
#region 绘制逻辑(脏矩形+LOD+四叉树)
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// 防止缩放因子过小导致除零或极小值
var safeScale = Math.Max(0.01f, _scale);
// 使用浮点计算避免精度丢失
var clip = e.ClipRectangle;
var scaledClip = new RectangleF(
clip.X / safeScale,
clip.Y / safeScale,
clip.Width / safeScale,
clip.Height / safeScale
);
// 转换为整数矩形,确保尺寸至少为1x1
var intClip = new Rectangle(
(int)scaledClip.X,
(int)scaledClip.Y,
Math.Max(1, (int)scaledClip.Width),
Math.Max(1, (int)scaledClip.Height)
);
var g = e.Graphics;
g.ScaleTransform(safeScale, safeScale);
// 线程安全访问(使用副本避免并发修改)
List<IDrawable> drawables;
lock (_quadTree)
{
drawables = _quadTree.Query(intClip).ToList();
}
// 绘制节点
foreach (var element in drawables.OfType<VisualElement>().OrderBy(el => el.ZIndex))
{
try
{
element.Draw(g);
}
catch (Exception ex)
{
Debug.WriteLine($"绘制节点时出错: {ex.Message}");
// 可添加降级绘制逻辑
}
}
// 绘制连接
foreach (var drawable in drawables.OfType<Connection>())
{
try
{
drawable.Draw(g);
}
catch (Exception ex)
{
Debug.WriteLine($"绘制连接时出错: {ex.Message}");
}
}
// 绘制临时连接
if (_tempConnection != null)
{
_tempConnection.Draw(g);
}
_dirtyRegion = Rectangle.Empty;
}
public void BringToFront(VisualElement element)
{
var maxZIndex = Elements.Max(el => el.ZIndex);
element.ZIndex = maxZIndex + 1;
Invalidate();
}
// 将元素置于底层
public void SendToBack(VisualElement element)
{
var minZIndex = Elements.Min(el => el.ZIndex);
element.ZIndex = minZIndex - 1;
Invalidate();
}
// 提升一层
public void MoveUp(VisualElement element)
{
var nextZIndex = Elements
.Where(el => el.ZIndex > element.ZIndex)
.Select(el => el.ZIndex)
.DefaultIfEmpty(element.ZIndex + 1)
.Min();
element.ZIndex = nextZIndex;
Invalidate();
}
// 降低一层
public void MoveDown(VisualElement element)
{
var prevZIndex = Elements
.Where(el => el.ZIndex < element.ZIndex)
.Select(el => el.ZIndex)
.DefaultIfEmpty(element.ZIndex - 1)
.Max();
element.ZIndex = prevZIndex;
Invalidate();
}
#endregion
#region 命令模式
public void ExecuteCommand(ICommand command)
{
command.Execute();
_undoStack.Push(command);
_redoStack.Clear();
}
public void Undo()
{
if (_undoStack.Any())
{
var cmd = _undoStack.Pop();
cmd.Undo();
_redoStack.Push(cmd);
Invalidate();
}
}
public void Redo()
{
if (_redoStack.Any())
{
var cmd = _redoStack.Pop();
cmd.Execute();
_undoStack.Push(cmd);
Invalidate();
}
}
public class AddElementCommand : ICommand
{
private readonly WorkflowCanvas _canvas;
private readonly VisualElement _element;
public AddElementCommand(WorkflowCanvas canvas, VisualElement element)
{
_canvas = canvas;
_element = element;
}
public void Execute() => _canvas.AddElement(_element);
public void Undo() => _canvas.RemoveElement(_element);
}
}
#endregion
#endregion
#region 插件与序列化
public class PluginLoader
{
public static IEnumerable<IPlugin> Load(string directory)
{
var result = new List<IPlugin>(); // 临时集合存储结果
foreach (var file in Directory.GetFiles(directory, "*.dll"))
{
try
{
var assembly = Assembly.LoadFrom(file);
foreach (var type in assembly.GetTypes())
{
if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsAbstract)
{
result.Add((IPlugin)Activator.CreateInstance(type)); // 收集结果
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Plugin load failed: {ex}");
}
}
return result; // 统一返回结果
}
}
public class JsonSerializer : ISerializer
{
public string Format => "json";
public string Serialize(WorkflowCanvas canvas)
{
var data = new
{
Elements = canvas.Elements.Select(e => new
{
Type = e.GetType().FullName,
Location = e.Location,
Size = e.Size,
Title = (e as VisionNode)?.Title,
Parameters = (e as VisionNode)?.Parameters
}).ToList(),
Connections = canvas.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 = canvas.Scale
};
return JsonConvert.SerializeObject(data, Newtonsoft.Json.Formatting.Indented);
}
public void Deserialize(WorkflowCanvas canvas, string data)
{
// 反序列化逻辑(需映射类型,此处简化)
var obj = JsonConvert.DeserializeAnonymousType(data, new
{
Elements = new List<ElementDTO>(),
Connections = new List<ConnectionDTO>(),
Scale = 1.0f
});
canvas.Elements.Clear();
canvas.Connections.Clear();
canvas.Controls.Clear();
canvas.SetScale(obj.Scale);
foreach (var dto in obj.Elements)
{
var type = Type.GetType(dto.Type);
if (type == null || !typeof(VisualElement).IsAssignableFrom(type)) continue;
var element = (VisualElement)Activator.CreateInstance(type);
element.Location = dto.Location;
element.Size = dto.Size;
if (element is VisionNode node)
{
node.Title = dto.Title;
node.Parameters = dto.Parameters.ToDictionary(k => k.Key, v => v.Value);
}
canvas.AddElement(element);
}
// 重建连接...
}
private class ElementDTO
{
public string Type { get; set; }
public Point Location { get; set; }
public Size Size { get; set; }
public string Title { get; set; }
public Dictionary<string, object> Parameters { 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
}使用winform完善功能
最新发布