DevExpress DocumentManager 操作类

本文介绍了一个使用DevExpress库实现的文档管理器,该管理器能够创建和管理多个文档,支持文档的增删改查操作,并通过StackGroup进行布局管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

using DevExpress.XtraBars.Docking2010.Views;
    using DevExpress.XtraBars.Docking2010.Views.Widget;


    public partial class UCDocumentManager : UserControl
    {
        #region 私有变量
        /// <summary>
        /// 列集合
        /// </summary>
        List<StackGroup> _stackGroups ;
        /// <summary>
        /// Document集合
        /// </summary>
        List<Document> _documents;

        #endregion

        public UCDocumentManager()
        {
            InitializeComponent();
        }


        #region
        /// <summary>
        /// 创建列
        /// </summary>
        /// <param name="count">列数量</param>
        /// <param name="pixels">列宽度像素点数量,缺省值200</param>
        public void CreateStackGroups(int count, double pixels = 200)
        {
            this.widgetView.StackGroups.Clear();
            _stackGroups = new List<StackGroup>();
            for (int i = 0; i < count; i++)
            {
                StackGroup stackGroup = new StackGroup();
                stackGroup.Length = new Length(pixels);
                _stackGroups.Add(stackGroup);
            }
            this.widgetView.StackGroups.AddRange(_stackGroups);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="documents"></param>
        public void CreateDocuments(IEnumerable<Document> documents)
        {
            if (documents == null) return;
            if (documents.Count() == 0)
            {
                this.widgetView.Documents.Clear();
                return;
            }
            this._documents = documents.ToList<Document>();
            this.widgetView.Documents.AddRange(this._documents);
            int columnCount = _stackGroups.Count;//列的数量
            double dRowCount = this._documents.Count * 1.0 / columnCount;//document需占最大行数,有小数点
            int averageRowCount = (int)dRowCount;//倒数第二层的行数
            int maxColumnCount = (int)((dRowCount - averageRowCount) * columnCount);//最后一行占前几列

            int index = 0;//document集合索引
            this.documentManager.BeginUpdate();
            for (int j = 0; j < columnCount; j++)
            {
                for (int i = 0; i < averageRowCount; i++)
                {
                    _stackGroups[j].Items.Add(this._documents[index++]);
                }
                if (j <= maxColumnCount)
                {
                    _stackGroups[j].Items.Add(this._documents[index++]);
                }
            }
            this.documentManager.EndUpdate();
        }
        /// <summary>
        /// 新增一个document
        /// </summary>
        /// <param name="document"></param>
        public void AddDocument(Document document)
        {
            this.widgetView.Documents.Add(document);
            this._documents.Add(document);
            StackGroup stackGroupp = this.GetStackGroupPositionToAddDocument();
            stackGroupp.Items.Add(document);            
        }
        /// <summary>
        /// 批量新增document,应保证之前document肯定不存在
        /// </summary>
        /// <param name="documents"></param>
        public void AddRangeDocument(IEnumerable<Document> documents)
        {
            this._documents.AddRange(documents);
            this.widgetView.Documents.AddRange(documents);
            foreach (Document document in documents)
            {
                StackGroup stackGroupp = this.GetStackGroupPositionToAddDocument();
                stackGroupp.Items.Add(document);
            }
        }

        public void RemoveDocument(string caption)
        {
            Document document = this._documents.FirstOrDefault<Document>(x => x.Caption.Equals(caption));
            if (document != null)
            {
                this.widgetView.Documents.Remove(document);
                this._stackGroups[document.ColumnIndex].Items.Remove(document);
                this._documents.Remove(document);
            }
        }

        public void RemoveRangeDocument(IEnumerable<string> captions)
        {
            foreach (string caption in captions)
            {
                this.RemoveDocument(caption);
            }
        }
        #endregion

        #region 内部函数
        int count = 0;
        /// <summary>
        /// 匹配每个document
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void widgetView_QueryControl(object sender, QueryControlEventArgs e)
        {
            WidgetControl widget = new WidgetControl();
            widget.CompanyNameText = companyFullNames[e.Document.Caption];
            e.Control = widget;
            (e.Document as Document).MaximizedControl = new MaximizedWidgetControl();
            //if (++count >= this._documents.Count)
            //    Jesus.Utility.DevExpressHelper.DevExpressDeal.CloseWaitForm();
        }
        /// <summary>
        /// 获取从前往后最少行的列
        /// </summary>
        /// <returns></returns>
        StackGroup GetStackGroupPositionToAddDocument()
        {
            int columnIndex = 0;
            int count=this._stackGroups.Count;
            int i = 1;
            for(;i<count;i++)
            {
                if (this._stackGroups[columnIndex].Items.Count > this._stackGroups[i].Items.Count)
                    columnIndex = i;
            }
            return this._stackGroups[columnIndex];
        }
        #endregion

        #region 临时
        Dictionary<string, string> companyFullNames = new Dictionary<string, string>();
        void FillCompaniesInformation()
        {
            companyFullNames.Add("AAPL", "Apple Inc.");
            companyFullNames.Add("YHOO", "Yahoo! Inc.");
            companyFullNames.Add("CSCO", "Cisco Systems Inc.");
            companyFullNames.Add("ADBE", "Adobe Systems Inc.");
            companyFullNames.Add("BAC", "Bank of America Corporation");
            companyFullNames.Add("DELL", "Dell Inc.");
            companyFullNames.Add("NVDA", "NVIDIA Corporation");
            companyFullNames.Add("HPQ", "Hewlett-Packard Company");
        }

        public void CreateDocuments()
        {
            FillCompaniesInformation();
            List<Document> documents = new List<Document>();
            List<string> keys = companyFullNames.Keys.ToList<string>();
            for (int i = 0; i < 8; i++)
            {
                Document document = new Document();
                document.Caption = keys[i];
                document.Properties.AllowClose = DevExpress.Utils.DefaultBoolean.False;
                document.Properties.AllowDock = DevExpress.Utils.DefaultBoolean.False;
                document.Properties.AllowFloat = DevExpress.Utils.DefaultBoolean.False;
                document.Properties.AllowActivate = DevExpress.Utils.DefaultBoolean.False;

                documents.Add(document);
            }
            this.CreateDocuments(documents);
        }

        public void AddRangeDocument(IEnumerable<string> captions)
        {
            List<Document> documents = new List<Document>();
            foreach (string caption in captions)
            {
                Document document = new Document();
                document.Caption = caption;
                document.Properties.AllowClose = DevExpress.Utils.DefaultBoolean.False;
                document.Properties.AllowDock = DevExpress.Utils.DefaultBoolean.False;
                document.Properties.AllowFloat = DevExpress.Utils.DefaultBoolean.False;
                document.Properties.AllowActivate = DevExpress.Utils.DefaultBoolean.False;
                documents.Add(document);
            }
            this.AddRangeDocument(documents);
        }

        public void AddDocument(string caption)
        {
            Document document = new Document();
            document.Caption = caption;
            document.Properties.AllowClose = DevExpress.Utils.DefaultBoolean.False;
            document.Properties.AllowDock = DevExpress.Utils.DefaultBoolean.False;
            document.Properties.AllowFloat = DevExpress.Utils.DefaultBoolean.False;
            document.Properties.AllowActivate = DevExpress.Utils.DefaultBoolean.False;
            this.AddDocument(document);
        }
        #endregion
    }
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完善功能
最新发布
06-23
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值