
前言
软件开发领域,流程设计与可视化是提升系统可维护性、增强用户体验的重要手段。无论是工作流管理、业务逻辑编排还是算法流程展示,一个灵活、易用的流程节点编辑框架都能极大地提高开发效率与系统灵活性。
本文将推荐一款基于 WPF 的开源流程节点编辑框架,通过对其核心设计与实现逻辑的解析,带领大家从零开始手写一个具备基础功能的 WPF 流程图编辑器,为实际项目中的可视化流程开发提供有价值的参考。
项目介绍
一款基于WPF的图形化流程节点编辑工具,为大家提供一个直观、高效的流程设计与编辑环境。通过拖拽节点、连接线等操作,可以轻松开发复杂的流程图,实现业务逻辑的可视化表达。
项目功能
1、节点创建与编辑
支持多种类型的节点创建,包括但不限于开始节点、结束节点、处理节点、决策节点等。
通过简单的点击或拖拽操作,在画布上添加、删除或修改节点属性,如节点名称、颜色、形状等。
2、连线管理
节点间通过连线表示流程走向,提供灵活的连线创建与编辑功能。
轻松地在节点间绘制直线、曲线或折线,调整连线的起点与终点,甚至设置连线的条件表达式,实现条件分支。
3、布局调整与边界扩展
满足不同场景下的展示需求,支持画布的缩放、平移以及节点的自动布局功能。
根据需要调整画布大小,通过手势或按钮控制画布的显示范围。同时,框架还提供了边界扩展功能,当节点靠近画布边缘时,自动扩展画布大小,确保所有节点都能完整显示。
4、框选与拖动
支持框选多个节点进行批量操作,如移动、删除等。可以通过鼠标拖拽选择框,选中多个节点后进行统一操作。同时,框架还提供节点的拖动功能,用户可以拖动单个或多个节点到指定位置。
5、数据绑定与交互
支持与后端数据的绑定,将流程图中的节点属性与数据库字段或API接口关联,实现数据的动态展示与交互。
另外,框架还提供了事件处理机制,用户可以自定义节点点击、连线双击等事件的处理逻辑,增强流程图的交互性。
项目特点
1、界面友好
WPF的丰富UI控件与动画效果,提供直观、美观的操作界面。无论是节点的拖拽、连线的绘制还是属性的编辑,都能带来流畅的操作体验。
2、扩展性强
设计遵循模块化原则,各个功能模块相对独立,便于开发者根据需求进行二次开发或功能扩展。同时,框架提供丰富的API接口,方便与其他系统进行集成。
3、交互丰富
支持多种交互方式,如框选、拖动、右键菜单等,可以根据需要选择合适的交互方式,提高操作效率。
项目技术
1、MVVM设计模式
为了实现界面与逻辑的分离,提高代码的可维护性与可测试性,框架采用MVVM(Model-View-ViewModel)设计模式。通过数据绑定与命令机制,实现了视图与模型之间的松耦合。
2、自定义控件开发
针对流程节点编辑的特殊需求,开发一系列自定义控件,如节点控件、连线控件等。控件通过继承WPF的基础控件类,实现了特定的功能与行为。
4、事件处理与委托
通过事件处理与委托机制,实现用户交互的响应与处理。无论是节点的点击、连线的双击还是画布的缩放,都能通过事件处理函数实现相应的逻辑。
项目代码
框选拖动
/// <summary>
/// 添加连线更新方法
/// </summary>
/// <param name="selectedControls">选中的控件</param>
/// <param name="Xoffset">连线位置X轴的偏移量</param>
/// <param name="Yoffset">连线位置Y轴的偏移量</param>
private void UpdateConnectionsForNode(List<Control> selectedControls, double Xoffset = 0, double Yoffset = 0)
{
// 当前不存在连线的话直接结束
if (connections.Count == 0) return;
foreach (XNode node in selectedControls)
{
List<Connection> refConns = connections.FindAll(conn => IsChildOf(conn.FromPort, node) || IsChildOf(conn.ToPort, node));
refConns.ForEach(conn =>
{
Point p1 = PointAdd(GetPortCenter(conn.FromPort), new Point(Xoffset, Yoffset));
Point p2 = PointAdd(GetPortCenter(conn.ToPort), new Point(Xoffset, Yoffset));
var geometry = new PathGeometry();
var figure = new PathFigure { StartPoint = p1 };
var segment = CreateSegment("polyline", p1, p2);
figure.Segments.Add(segment);
geometry.Figures.Add(figure);
conn.Path.Data = geometry;
});
}
}
// 点位相加
private Point PointAdd(Point p1, Point p2)
{
return new Point(p1.X + p2.X, p1.Y + p2.Y);
}
private bool IsChildOf(FrameworkElement child, FrameworkElement parent)
{
var current = child;
while (current != null)
{
if (current == parent)
return true;
current = VisualTreeHelper.GetParent(current) as FrameworkElement;
}
return false;
}
线条拖动 | 线条类型切换
private void OutputPort_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is FrameworkElement outputPort)
{
fromPort = outputPort;
startPoint = GetPortCenter(outputPort);
currentPath = new System.Windows.Shapes.Path
{
Stroke = Brushes.MediumPurple,
StrokeThickness = lineThickness,
Data = new PathGeometry()
};
currentPath.MouseLeftButtonDown += Path_MouseLeftButtonDown;
currentPath.MouseEnter += Path_MouseEnter;
currentPath.MouseLeave += Path_MouseLeave;
MainCanvas.Children.Add(currentPath);
isConnecting = true;
e.Handled = true;
}
}
private Point GetPortCenter(FrameworkElement port)
{
var point = new Point(port.Width / 2, port.Height / 2);
// 将当前点相对于port的坐标转换为当前点相对于Canvas的坐标位置,Canvas会先获取point左上角的位置,然后再偏移point.X,point.Y
var position = port.TranslatePoint(point, MainCanvas);
return position;
}
private PathSegment CreateSegment(string type, Point startPoint, Point endPoint)
{
if (string.IsNullOrEmpty(type))
throw new Exception("type 类型不能为空");
PathSegment segment;
if (type == "polyline")
{
if (startPoint.X <= endPoint.X - 40) // 两边距离大于40
{
double centerX = (startPoint.X + endPoint.X) / 2;
var polyline = new PolyLineSegment
{
Points = new PointCollection()
{
new Point(centerX,startPoint.Y),
new Point(centerX,endPoint.Y),
new Point(endPoint.X,endPoint.Y) // 终点
}
};
segment = polyline;
}
else
{
double centerY = (startPoint.Y + endPoint.Y) / 2;
var polyline = new PolyLineSegment
{
Points = new PointCollection()
{
new Point(startPoint.X + 20,startPoint.Y),
new Point(startPoint.X + 20,centerY),
new Point(endPoint.X - 20,centerY),
new Point(endPoint.X - 20,endPoint.Y),
new Point(endPoint.X,endPoint.Y) // 终点
}
};
segment = polyline;
}
}
else
{
var bezier = new BezierSegment
{
Point1 = new Point(startPoint.X + 50, startPoint.Y),
Point2 = new Point(endPoint.X - 50, endPoint.Y),
Point3 = endPoint
};
segment = bezier;
}
return segment;
}
项目效果
框架极大地简化复杂流程的设计与实现过程,为项目的快速迭代与交付提供有力支持。

项目源码
源码结构清晰,注释详细,为大家提供良好的学习与参考价值。
代码中包含节点的创建、连线的绘制、布局的调整、框选与拖动的实现等核心功能,是学习WPF开发与流程节点编辑的好示例。
Gitee:https://gitee.com/Zero_0002/process-node-editing-framework
总结
流程节点编辑框架是一款功能强大、易于使用的图形化流程设计工具。通过其丰富的功能特性、友好的操作界面以及强大的技术,框架在软件开发领域展现出了广泛的用途。
关键词
WPF、流程节点编辑、连线管理、框选拖动、边界扩展、数据绑定、MVVM模式、自定义控件、项目源码、交互丰富流程图、节点编辑、可视化、C#、开源项目、JSON、MVVM、拖拽、连线
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号[DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

