节点,一般都为树形Tree结构,如TreeNode,XmlNode。
树形结构有其关键属性Parent【父节点】,Children【子节点】
LinkedListNode为链表线性结构,有其关键属性Next【下一个】,Previous【上一个】,
可以用其进行工作流workFlow设计
右键 项目 STNodeDemo,管理NuGet程序包
输入关键字STNodeEditor
安装完成后
我们可以查看工具箱
开源的 .NET 轻量级且功能强大的节点编辑器STNodeEditor
STNodeEditor 是一个轻量且功能强大的节点编辑器 使用方式非常简洁 提供了丰富的属性以及事件可以非常方便的完成节点之间数据的交互及通知 大量的虚函数可供开发者重写具有很高的自由性。
当有很多应用程序(模块) 它们之间需要相互调用传递数据来完成一整套流程的工作 开发单一功能的应用程序(模块)相对比较容易 而实现一整套很多功能相互调用的应用程序相对比较繁琐 此套框架开发者只需要定义好传递的数据类型 然后分别实现单一节点功能 至于执行流程交给框架和用户布线即可。
项目地址
https://github.com/DebugST/STNodeEditor
STNodeEditor是基于WinForm的一套框架 使用GDI+开发 不包含任何额外依赖 整个调用库仅100+kb
项目主页:https://debugst.github.io/STNodeEditor/
教程文档:https://debugst.github.io/STNodeEditor/doc_cn.html
NuGet:https://www.nuget.org/packages/ST.Library.UI/
GitHub:https://github.com/DebugST/STNodeEditor
由上图可见 STNodeEditor 包含3部分 TreeView PropertyGrid NodeEditor 这三部分组成了一套完整的可使用框架
TreeView
开发这可以把执行功能编码到一个节点中 而TreeView则负责展示以及检索节点 在TreeView中的节点可直接拖拽添加到NodeEditor中
PropertyGrid
类似与WinForm开发使用的属性窗口 作为一个节点 它也是可以有属性的 而作者在编辑器进行设计的过程中也把一个节点视作一个Form让开发者几乎没有什么学习成本直接上手一个节点的开发
NodeEditor
NodeEditor是用户组合自己执行流程的地方 使得功能模块执行流程可视化
因ST.Library.UI.NodeEditor.STNode 是一个抽象类,因此我们需要新建一个子类SnakeNode
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using ST.Library.UI.NodeEditor;
namespace STNodeDemo
{
public class SnakeNode : ST.Library.UI.NodeEditor.STNode
{
public SnakeNode(string title)
{
this.Title = title;
this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
}
/// <summary>
/// 输出节点集合
/// </summary>
public List<STNodeOption> OutputOptionCollection = new List<STNodeOption>();
STNodeOption outputTest;
protected override void OnCreate()
{
base.OnCreate();
this.Title = "SnakeNode";
//输入项集合
int index = this.InputOptions.Add(new STNodeOption("Input1", typeof(string), false));
STNodeOption nodeIn2 = this.InputOptions.Add("Input2", typeof(int), false);
STNodeOption nodeIn3 = this.InputOptions.Add("Input3", typeof(float), false);
//输出项集合
STNodeOption nodeOut = this.OutputOptions.Add("Output1", typeof(string), false);
outputTest = this.OutputOptions.Add("OutputTime", typeof(DateTime), false);
OutputOptionCollection.Add(nodeOut);
OutputOptionCollection.Add(outputTest);
//STNodeOption[] ss = this.GetOutputOptions();
}
//当所有者发生改变(即:在NodeEditor中被添加或移除)
//应当像容器提交自己拥有数据类型的连接点 所期望显示的颜色
//颜色主要用于区分不同的数据类型
protected override void OnOwnerChanged()
{
base.OnOwnerChanged();
if (this.Owner == null) return;
this.Owner.SetTypeColor(typeof(string), Color.Yellow);
//当前容器中已有的颜色会被替换
this.Owner.SetTypeColor(typeof(int), Color.DodgerBlue, true);
this.Owner.SetTypeColor(typeof(float), Color.Pink, true);
this.Owner.SetTypeColor(typeof(DateTime), Color.Green, true);
//下面的代码将忽略容器中已有的颜色
//this.SetOptionDotColor(op, Color.Red); //无需在OnOwnerChanged()中设置
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 50; i++)
{
System.Threading.Thread.Sleep(1000);
//STNodeOption.TransferData(object)会自动设置STNodeOption.Data
//然后自动向所有连接的选项进行数据传递
outputTest.TransferData(DateTime.Now);
}
});
}
}
}
新建测试接收节点类ViperNode
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using ST.Library.UI.NodeEditor;
namespace STNodeDemo
{
public class ViperNode : ST.Library.UI.NodeEditor.STNode
{
public ViperNode(string title)
{
this.Title = title;
this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
}
/// <summary>
/// 输入节点集合
/// </summary>
public List<STNodeOption> InputOptionCollection = new List<STNodeOption>();
STNodeOption inputTest;
protected override void OnCreate()
{
base.OnCreate();
this.Title = "ViperNode";
//输入项集合
inputTest = this.InputOptions.Add("ViperTime", typeof(DateTime), false);
inputTest.DataTransfer += new STNodeOptionEventHandler(InputTest_DataTransfer);
InputOptionCollection.Add(inputTest);
}
private void InputTest_DataTransfer(object sender, STNodeOptionEventArgs e)
{
//当连接的建立与断开都会触发此事件 所以需要判断连接状态
if (e.Status != ConnectionStatus.Connected || e.TargetOption.Data == null)
{
//当 STNode.AutoSize=true 并不建议使用STNode.SetOptionText
//因为当文本发生改变时候会重新计算布局 正确的做法是自定义一个如Lable控件
//作为时间的显示 当然这里为了演示方式采用此方案
this.SetOptionText(inputTest, "--");
}
else
{
this.SetOptionText(inputTest, Convert.ToDateTime(e.TargetOption.Data).ToString("yyyy-MM-dd HH:mm:ss.fff"));
}
}
}
}
新建测试窗体FormSTNode,设计如下
FormSTNode.Designer.cs设计器代码如下:
namespace STNodeDemo
{
partial class FormSTNode
{
/// <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.stNodeEditor1 = new ST.Library.UI.NodeEditor.STNodeEditor();
this.btnConnectLine = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// stNodeEditor1
//
this.stNodeEditor1.AllowDrop = true;
this.stNodeEditor1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(34)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
this.stNodeEditor1.Curvature = 0.3F;
this.stNodeEditor1.Location = new System.Drawing.Point(12, 67);
this.stNodeEditor1.LocationBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.stNodeEditor1.MarkBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.stNodeEditor1.MarkForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.stNodeEditor1.MinimumSize = new System.Drawing.Size(100, 100);
this.stNodeEditor1.Name = "stNodeEditor1";
this.stNodeEditor1.Size = new System.Drawing.Size(1160, 475);
this.stNodeEditor1.TabIndex = 0;
this.stNodeEditor1.Text = "stNodeEditor1";
//
// btnConnectLine
//
this.btnConnectLine.Font = new System.Drawing.Font("宋体", 16F);
this.btnConnectLine.Location = new System.Drawing.Point(52, 12);
this.btnConnectLine.Name = "btnConnectLine";
this.btnConnectLine.Size = new System.Drawing.Size(169, 33);
this.btnConnectLine.TabIndex = 1;
this.btnConnectLine.Text = "连线-显示时间";
this.btnConnectLine.UseVisualStyleBackColor = true;
this.btnConnectLine.Click += new System.EventHandler(this.btnConnectLine_Click);
//
// FormSTNode
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1200, 554);
this.Controls.Add(this.btnConnectLine);
this.Controls.Add(this.stNodeEditor1);
this.Name = "FormSTNode";
this.Text = "STNodeEditor开源框架,输入输出";
this.Load += new System.EventHandler(this.FormSTNode_Load);
this.ResumeLayout(false);
}
#endregion
private ST.Library.UI.NodeEditor.STNodeEditor stNodeEditor1;
private System.Windows.Forms.Button btnConnectLine;
}
}
窗体FormSTNode.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;
using ST.Library.UI.NodeEditor;
namespace STNodeDemo
{
public partial class FormSTNode : Form
{
public FormSTNode()
{
InitializeComponent();
//参考地址 https://blog.csdn.net/crystal_lz/article/details/117131080
/*single-connection
单连接模式 在单连接模式下一个连接点同时 只能被一个 同数据类型点的连接
multi-connection
多连接模式 在多连接模式下一个连接点同时 可以被多个 同数据类型点连接
数据交互:
STNodeOption可以通过绑定DataTransfer事件获取到传入该选项的所有数据
STNodeOption可以通过TransferData(object obj)向该选项上所有连接的选项进行数据投递
*/
}
private void FormSTNode_Load(object sender, EventArgs e)
{
SnakeNode stNode = new SnakeNode("STNode的标题");
stNode.Location = new Point(10, 10);
stNodeEditor1.Nodes.Add(stNode);
ViperNode destNode = new ViperNode("显示时间");
destNode.Location = new Point(400, 10);
stNodeEditor1.Nodes.Add(destNode);
}
/// <summary>
/// 获取指定的输出节点选项
/// </summary>
/// <param name="node"></param>
/// <param name="strText"></param>
/// <returns></returns>
private STNodeOption GetOutNodeOption(SnakeNode node, string strText)
{
STNodeOption outNodeOption = node.OutputOptionCollection.FirstOrDefault(option => option.Text == strText);
return outNodeOption;
}
/// <summary>
/// 获取指定的输入节点选项
/// </summary>
/// <param name="node"></param>
/// <param name="strText"></param>
/// <returns></returns>
private STNodeOption GetInNodeOption(ViperNode node, string strText)
{
STNodeOption inNodeOption = node.InputOptionCollection.FirstOrDefault(option => option.Text == strText);
return inNodeOption;
}
private void btnConnectLine_Click(object sender, EventArgs e)
{
STNodeOption outNodeOption = GetOutNodeOption((SnakeNode)stNodeEditor1.Nodes[0], "OutputTime");
STNodeOption inNodeOption = GetInNodeOption((ViperNode)stNodeEditor1.Nodes[1], "ViperTime");
ConnectionStatus connectionStatus = outNodeOption.ConnectOption(inNodeOption);
MessageBox.Show($"输出节点 连接 输入节点 的状态结果【{connectionStatus}】", "提示");
}
}
}