接上篇:
使用Winform的GDI进行流程图绘制,实现环节流转。
比如流程图如下:
A---->B---->C
|
F<----E<----D
新建windows应用程序WorkFlowDemo,将默认的Form1重命名为FormWorkFlow,引入开源ORM框架SqlSugar以及log4net,以及操作mysql的库MySql.Data.dll,然后添加对System.Configuration的引用。
一、默认的应用程序配置文件App.Config源程序如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
<connectionStrings>
<add name="mysql" providerName="MySql.Data.MySqlClient" connectionString="Server=127.0.0.1;Database=workflow_demo;Uid=root;Pwd=root;" />
</connectionStrings>
</configuration>
二、增加操作mysql的类文件SugarDao,
SugarDao.cs源程序如下:
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using SqlSugar;
namespace WorkFlowDemo
{
/// <summary>
/// SqlSugar
/// </summary>
public class SugarDao
{
//禁止实例化
private SugarDao()
{
}
public static string ConnectionString(int index = 1)
{
string reval = System.Configuration.ConfigurationManager.ConnectionStrings["mysql"].ConnectionString;
return reval;
}
public static SqlSugarClient GetInstance(SqlSugar.DbType dbType= SqlSugar.DbType.MySql, int index = 1)
{
return new SqlSugarClient(new ConnectionConfig()
{
DbType = dbType,
ConnectionString = ConnectionString(index),
InitKeyType = InitKeyType.Attribute,
IsAutoCloseConnection = true,
AopEvents = new AopEvents
{
OnLogExecuting = (sql, p) =>
{
Console.WriteLine(sql);
Console.WriteLine(string.Join(",", p?.Select(it => it.ParameterName + ":" + it.Value)));
}
}
});
}
/// <summary>
/// 检查数据库连接
/// </summary>
public static bool CheckConnect()
{
SqlSugarClient db = GetInstance();
string connectionString = db.CurrentConnectionConfig.ConnectionString;//连接字符串
try
{
db.Open();
return true;
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show($"连接mysql数据库失败,【{ex.Message}】.\n连接字符串【{connectionString}】", "出错");
return false;
}
finally
{
db.Close();
}
}
}
}
三、增加操作类RawSql,
RawSql.cs源程序如下
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WorkFlowDemo
{
/// <summary>
/// 原生的sql增删改查
/// </summary>
public class RawSql
{
/// <summary>
/// 执行原生的Sql与sql参数的insert、update、delete等操作,返回受影响的行数
/// </summary>
/// <param name="sql"></param>
/// <param name="dict"></param>
/// <param name="dbType">数据库类型,一般是mysql</param>
/// <param name="index">额外的连接数据库,2为新的数据库连接字符串</param>
/// <returns></returns>
public static int ExecuteCommand(string sql, Dictionary<string, object> dict, SqlSugar.DbType dbType = SqlSugar.DbType.MySql, int index = 1)
{
List<SugarParameter> parameters = DictToList(dict);
using (var db = SugarDao.GetInstance(dbType, index))
{
return db.Ado.ExecuteCommand(sql, parameters);
}
}
/// <summary>
/// 执行原生的Sql与sql参数的select查询等操作,返回首行首列的数据
/// </summary>
/// <param name="sql"></param>
/// <param name="dict"></param>
/// <param name="dbType">数据库类型,一般是mysql</param>
/// <param name="index">额外的连接数据库,2为新的数据库连接字符串</param>
/// <returns></returns>
public static object GetScalar(string sql, Dictionary<string, object> dict, SqlSugar.DbType dbType = SqlSugar.DbType.MySql, int index = 1)
{
List<SugarParameter> parameters = DictToList(dict);
using (var db = SugarDao.GetInstance(dbType, index))
{
return db.Ado.GetScalar(sql, parameters);
}
}
/// <summary>
/// 执行原生的Sql与sql参数的select查询等操作,返回一个数据表
/// </summary>
/// <param name="sql"></param>
/// <param name="dict"></param>
/// <param name="dbType">数据库类型,一般是mysql</param>
/// <param name="index">额外的连接数据库,2为新的数据库连接字符串</param>
/// <returns></returns>
public static DataTable GetDataTable(string sql, Dictionary<string, object> dict, SqlSugar.DbType dbType = SqlSugar.DbType.MySql, int index = 1)
{
List<SugarParameter> parameters = DictToList(dict);
using (var db = SugarDao.GetInstance(dbType, index))
{
return db.Ado.GetDataTable(sql, parameters);
}
}
/// <summary>
/// 字典转参数列表
/// </summary>
/// <param name="dict"></param>
/// <returns></returns>
private static List<SugarParameter> DictToList(Dictionary<string, object> dict)
{
List<SugarParameter> parameters = new List<SugarParameter>();
for (int i = 0; dict != null && i < dict.Count; i++)
{
KeyValuePair<string, object> keyValuePair = dict.ElementAt(i);
parameters.Add(new SugarParameter(keyValuePair.Key, keyValuePair.Value));
}
return parameters;
}
}
}
四、新建枚举类ArrowDirection
ArrowDirection.cs源程序如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WorkFlowDemo
{
/// <summary>
/// 箭头方向:分水平方向Horizontal 和垂直方向Vertical
/// </summary>
[Flags]
public enum ArrowDirection
{
/// <summary>
/// 空,不绘制箭头
/// </summary>
None = 0,
/// <summary>
/// 垂直方向:向上
/// </summary>
Up = 1,
/// <summary>
/// 垂直方向:向下
/// </summary>
Down = 2,
/// <summary>
/// 水平方向:向左
/// </summary>
Left = 4,
/// <summary>
/// 水平方向:向右
/// </summary>
Right = 8
}
}
五、窗体FormWorkFlow.Designer.cs设计器源程序如下:
namespace WorkFlowDemo
{
partial class FormWorkFlow
{
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
/// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows 窗体设计器生成的代码
/// <summary>
/// 设计器支持所需的方法 - 不要修改
/// 使用代码编辑器修改此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.SuspendLayout();
//
// FormWorkFlow
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(884, 695);
this.Name = "FormWorkFlow";
this.Text = "工作流节点(环节)流转图";
this.Load += new System.EventHandler(this.FormWorkFlow_Load);
this.ResumeLayout(false);
}
#endregion
}
}
六、核心流程图绘制FormWorkFlow.cs程序如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WorkFlowDemo
{
public partial class FormWorkFlow : Form
{
public FormWorkFlow()
{
InitializeComponent();
}
private void FormWorkFlow_Load(object sender, EventArgs e)
{
//DataTable dt = RawSql.GetDataTable("select * from process_router where PreviousOperationName='' or PreviousOperationName is null", null);
//if (dt == null || dt.Rows.Count == 0)
//{
// MessageBox.Show("没有配置工艺路线【process_router】的起始工序,请检查工艺路线配置", "出错");
// return;
//}
//string nodeText = dt.Rows[0]["OperationName"].ToString();
//Panel panel = new Panel();
//panel.Location = new Point(10, 10);
//panel.Name = "panel1";
//panel.Size = new Size(160, 60);
//this.Controls.Add(panel);
//AddWorkFlowNode(panel, Color.Red, nodeText, ArrowDirection.Right);
string nodeText;
if (!PaintStartNode(out nodeText))
{
return;
}
PaintNextNode(nodeText);
}
/// <summary>
/// 绘制起始环节,返回是否查找到起始环节
/// </summary>
/// <param name="nodeText"></param>
/// <returns></returns>
private bool PaintStartNode(out string nodeText)
{
nodeText = string.Empty;
DataTable dt = RawSql.GetDataTable("select * from process_router where PreviousOperationName='' or PreviousOperationName is null", null);
if (dt == null || dt.Rows.Count == 0)
{
MessageBox.Show("没有配置工艺路线【process_router】的起始工序,请检查工艺路线配置", "出错");
return false;
}
nodeText = dt.Rows[0]["OperationName"].ToString();
Panel panel = new Panel();
panel.Location = new Point(10, 10);
panel.Name = "panel1";
panel.Size = new Size(160, 60);
this.Controls.Add(panel);
AddWorkFlowNode(panel, Color.Green, nodeText, ArrowDirection.Right, 0);
//PaintNodeAndArrowPanel(0, Color.Red, nodeText, false);
return true;
}
/// <summary>
/// 连续绘制工作流的当前环节与下一环节,以及最后一个终结环节
/// </summary>
/// <param name="operationName"></param>
private void PaintNextNode(string operationName)
{
int index = 1;
do
{
//获取当前节点(环节)的下一环节
DataTable dt = RawSql.GetDataTable("select * from process_router where PreviousOperationName=@OperationName", new Dictionary<string, object>() { { "OperationName", operationName } });
if (dt != null && dt.Rows.Count > 0)
{
operationName = dt.Rows[0]["OperationName"].ToString();
//查找是否是终结环节
dt = RawSql.GetDataTable("select * from process_router where PreviousOperationName=@OperationName", new Dictionary<string, object>() { { "OperationName", operationName } });
if (dt == null || dt.Rows.Count == 0)
{
//当前环节 没有下一个环节 ,则认为是工序完成环节,不显示向右 或向下箭头
PaintNodeAndArrowPanel(index, Color.Red, operationName, true);
break;
}
PaintNodeAndArrowPanel(index, Color.Blue, operationName, false);
}
index++;
} while (true);
}
/// <summary>
/// 绘制环节节点以及流转箭头
/// </summary>
/// <param name="index"></param>
/// <param name="color"></param>
/// <param name="operationName"></param>
/// <param name="isCompletedNode">是否是完成环节(最后一道工序)</param>
private void PaintNodeAndArrowPanel(int index, Color color, string operationName, bool isCompletedNode)
{
Panel panel = new Panel();
panel.Name = $"panel{index + 1}";
int rowNumber = index / 3 * 2;
switch (index % 6)
{
case 1:
panel.Location = new Point(10 + 160 * 2, 10 + 60 * rowNumber);
panel.Size = new Size(160, 60);
AddWorkFlowNode(panel, color, operationName, isCompletedNode ? ArrowDirection.None : ArrowDirection.Right, index);
break;
case 2:
panel.Location = new Point(10 + 160 * 4, 10 + 60 * rowNumber);
panel.Size = new Size(160, 60);
AddWorkFlowNode(panel, color, operationName, isCompletedNode ? ArrowDirection.None : ArrowDirection.Down, index);
break;
case 3:
panel.Location = new Point(10 + 160 * 2, 10 + 60 * rowNumber);
panel.Size = new Size(160 * 2, 60);
AddWorkFlowNode(panel, color, operationName, isCompletedNode ? ArrowDirection.None : ArrowDirection.Left, index);
break;
case 4:
panel.Location = new Point(10 + 160 * 0, 10 + 60 * rowNumber);
panel.Size = new Size(160 * 2, 60);
AddWorkFlowNode(panel, color, operationName, isCompletedNode ? ArrowDirection.None : ArrowDirection.Left, index);
break;
case 5:
panel.Location = new Point(10 + 160 * 0, 10 + 60 * rowNumber);
panel.Size = new Size(160, 60);
AddWorkFlowNode(panel, color, operationName, isCompletedNode ? ArrowDirection.None : ArrowDirection.Down, index);
break;
case 0:
panel.Location = new Point(10 + 160 * 0, 10 + 60 * rowNumber);
panel.Size = new Size(160, 60);
AddWorkFlowNode(panel, color, operationName, isCompletedNode ? ArrowDirection.None : ArrowDirection.Right, index);
break;
}
this.Controls.Add(panel);
}
private void AddWorkFlowNode(Panel panel, Color color, string text, ArrowDirection arrowDirection, int index)
{
panel.BackgroundImage = null;//清除背景
//面板宽度为160,高度为60
Bitmap bitmap = new Bitmap(panel.Width * 2, panel.Height);
if (arrowDirection == ArrowDirection.Up || arrowDirection == ArrowDirection.Down)
{
bitmap = new Bitmap(panel.Width, panel.Height * 2);
}
Graphics graphics = Graphics.FromImage(bitmap);
Rectangle rect = new Rectangle(0, 0, panel.Width, panel.Height);
if (index % 6 == 3 || index % 6 == 4)
{
//需要向左的环节,矩形绘制需要更新
rect = new Rectangle(panel.Width / 2, 0, panel.Width / 2, panel.Height);
}
//Color color = Color.Blue;
//string text = "Busbar焊接";
graphics.FillRectangle(new SolidBrush(color), rect);
graphics.DrawRectangle(new Pen(color), rect);
AddTextAlignCenter(graphics, text, new Font("华文楷体", 16), rect);
Pen pen = new Pen(Color.OrangeRed, 3);//画笔:线条颜色、粗细
//DashStyle.Dot 代表虚线
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;//实线
pen.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;//设置线尾的样式为箭头
switch (arrowDirection)
{
case ArrowDirection.None:
if (index % 6 == 3 || index % 6 == 4)
{
panel.Location = new Point(panel.Location.X + 160, panel.Location.Y);
}
break;
case ArrowDirection.Up:
break;
case ArrowDirection.Down:
graphics.DrawLine(pen, new Point(panel.Width / 2, panel.Height), new Point(panel.Width / 2, panel.Height * 2));
//将面板高度放大到两倍,下边部分用于显示线条
panel.Height = panel.Height * 2;
break;
case ArrowDirection.Left:
graphics.DrawLine(pen, new Point(panel.Width / 2, panel.Height / 2), new Point(0, panel.Height / 2));
//将面板宽度放大到两倍,右边部分用于显示线条
//panel.Width = panel.Width * 2;
panel.Location = new Point(panel.Location.X + 160, panel.Location.Y);
break;
case ArrowDirection.Right:
graphics.DrawLine(pen, new Point(panel.Width, panel.Height / 2), new Point(panel.Width * 2, panel.Height / 2));
//将面板宽度放大到两倍,右边部分用于显示线条
panel.Width = panel.Width * 2;
break;
}
graphics.Dispose();
pen.Dispose();
panel.BackgroundImage = bitmap;
}
/// <summary>
/// 将显示的文字放在矩形的中间
/// </summary>
/// <param name="graphics"></param>
/// <param name="text"></param>
/// <param name="font"></param>
/// <param name="rect"></param>
private void AddTextAlignCenter(Graphics graphics, string text, Font font, Rectangle rect)
{
SizeF sizeF = graphics.MeasureString(text, font);
float destX = rect.X + (rect.Width - sizeF.Width) / 2;
float destY = rect.Y + (rect.Height - sizeF.Height) / 2;
graphics.DrawString(text, font, Brushes.Black, destX, destY);
}
}
}