当前位置:首页 > 技术 > 正文内容

基于 GDI+ 的流程图开发:矩形关联与连线逻辑实现

访客 技术 2026年5月30日 1

1. 基础连接逻辑:关联两个矩形中心

在流程图应用中,最基础的连线需求是连接两个已有图形。我们首先实现一种简单场景:计算两个矩形的几何中心点,并在它们之间绘制一条直线。

实现原理在于:实时获取两个矩形的坐标与尺寸,计算出中心点 $P(x, y)$,公式为 $x = X + Width/2, y = Y + Height/2$。当矩形位置发生变动时,重新计算这两个点并触发重绘。

以下是核心逻辑的初步实现示例:

public class NodeElement
{
    public string NodeId { get; set; }
    public Rectangle Bounds { get; set; }
}

// 定义连接点
private Point _startCenter = Point.Empty;
private Point _endCenter = Point.Empty;

// 计算中心位置
private void UpdateConnectionPoints()
{
    if (_nodes.Count >= 2)
    {
        var n1 = _nodes[0].Bounds;
        _startCenter = new Point(n1.X + n1.Width / 2, n1.Y + n1.Height / 2);

        var n2 = _nodes[1].Bounds;
        _endCenter = new Point(n2.X + n2.Width / 2, n2.Y + n2.Height / 2);
    }
}

// 绘图方法
private void RenderCanvas(Graphics g)
{
    g.SmoothingMode = SmoothingMode.AntiAlias;
    foreach (var node in _nodes)
    {
        g.FillRectangle(Brushes.SkyBlue, node.Bounds);
    }
    
    if (!_startCenter.IsEmpty && !_endCenter.IsEmpty)
    {
        using (Pen p = new Pen(Color.Black, 2))
        {
            g.DrawLine(p, _startCenter, _endCenter);
        }
    }
}

2. 进阶功能:实现任意图形间的动态连线

在实际应用中,用户需要手动选择起始节点和终点节点。这需要引入"交互状态机"的概念,通过鼠标点击事件来捕获节点 ID,并建立关联关系。

我们需要定义一个连接对象 EdgeLink,存储起始节点和结束节点的标识符。在鼠标按下事件(MouseDown)中,根据当前是否处于"连线模式"来决定是拖动图形还是选择连线目标。

public class EdgeLink
{
    public string LinkId { get; set; }
    public string FromNodeId { get; set; }
    public string ToNodeId { get; set; }
    public Color LinkColor { get; set; } = Color.Black;
}

private List<EdgeLink> _edges = new List<EdgeLink>();
private NodeElement _linkingSource = null;
private bool _isLinkingMode = false;

private void Canvas_MouseDown(object sender, MouseEventArgs e)
{
    var targetNode = _nodes.FindLast(n => n.Bounds.Contains(e.Location));
    
    if (_isLinkingMode && targetNode != null)
    {
        if (_linkingSource == null)
        {
            _linkingSource = targetNode;
            // 提示用户选择第二个节点
        }
        else if (_linkingSource.NodeId != targetNode.NodeId)
        {
            // 创建新的连接关系
            _edges.Add(new EdgeLink {
                LinkId = Guid.NewGuid().ToString(),
                FromNodeId = _linkingSource.NodeId,
                ToNodeId = targetNode.NodeId
            });
            ResetLinkingState();
            Invalidate(); // 刷新画布
        }
    }
}

3. 功能优化:颜色区分、去重与操作中止

为了提升用户体验,我们需要完善以下细节:

  • 颜色区分:通过索引或属性为不同的连线分配颜色,便于视觉识别。
  • 连接去重:在建立连接前检查 _edges 集合,避免在相同两个节点间重复绘线。
  • 操作中止:允许用户通过右键或特定按钮退出连线模式,清除当前选中的起始节点。

优化后的绘图与校验逻辑如下:

private void CreateConnection(NodeElement source, NodeElement target)
{
    // 去重检查:判断是否已存在相同方向或反向的连接
    bool exists = _edges.Any(link => 
        (link.FromNodeId == source.NodeId && link.ToNodeId == target.NodeId) ||
        (link.FromNodeId == target.NodeId && link.ToNodeId == source.NodeId));

    if (!exists)
    {
        var newLink = new EdgeLink {
            LinkId = "L" + (_edges.Count + 1),
            FromNodeId = source.NodeId,
            ToNodeId = target.NodeId,
            LinkColor = GetDynamicColor(_edges.Count)
        };
        _edges.Add(newLink);
    }
}

private Color GetDynamicColor(int index)
{
    Color[] palette = { Color.Red, Color.ForestGreen, Color.RoyalBlue, Color.Orange };
    return palette[index % palette.Length];
}

// 综合绘制方法
private void OnDraw(Graphics g)
{
    // 先画线,后画节点,保证节点遮盖线头
    foreach (var link in _edges)
    {
        Point p1 = GetNodeCenter(link.FromNodeId);
        Point p2 = GetNodeCenter(link.ToNodeId);
        using (Pen customPen = new Pen(link.LinkColor, 2))
        {
            g.DrawLine(customPen, p1, p2);
        }
    }

    foreach (var node in _nodes)
    {
        // 绘制矩形及文字标识
        g.FillRectangle(Brushes.White, node.Bounds);
        g.DrawRectangle(Pens.DimGray, node.Bounds);
        g.DrawString(node.NodeId, this.Font, Brushes.Black, node.Bounds);
    }
}

通过上述逻辑,我们构建了一个具备基础交互能力的连线系统。用户可以动态创建关联,且在拖动节点时,由于中心点是根据节点坐标实时计算的,连线会自动跟随移动,从而实现了动态布局的初步效果。

标签: C#GDI+WinForms

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。