STNodeEditor is a lightweight and powerful node editor, and is very simple to use. Provides a wealth of properties and events, which can easily complete the data interaction and notification between nodes. A large number of virtual functions are available for developers to use. It is very free.
Introduction
It was a winter. The author who was studying radio security used GNURadio. That was the first time the author used the node editor.
-> What? Excuse me... What's this?.. What the hell is this?...
It was a spring season, and the author didn't know why the whole world had changed after the Chinese New Year. Everyone was forced to stay at home. The extremely boring author learned Blender. That was the second time the author used the node editor.
-> Wo...It turns out that this one is really convenient to use.
So some ideas gradually emerged in the author's mind, making the author want to make one.
It was a summer. The author didn’t know why the author started to learn Davinci again. That was the third time the author used the node editor. The use of this time has doubled the author's favor with the node editor. The author instantly felt that as long as it is a program that can be modularized and streamlined, everything can be nodeized.
Make Your Functions as Flowchart
Many times when we are doing development, we will use flowcharts, and code to complete the functions and execution processes on the flowcharts.
But this will cause a problem, our function execution process will be hard-coded into the program, and the execution process is not visible to the user. And when we need to change the execution process, we have to re-modify the code.
STNodeEditor
is to solve such problems.
This project home page: https://debugst.github.io/STNodeEditor/index_en.html
You can see that the above picture contains a NodeEditor
, TreeView
and a PropertyGrid
. They are combined into a complete framework.
TreeView
:
- You can code your function into the node, and then add the node to the
TreeView
. The nodes in the TreeView
can be directly dragged and added to the NodeEditor
.
PropertyGrid
:
- Maybe your node may need some properties, and
PropertyGrid
can provide access operations to modify node properties just like the WinForm designer.
NodeEditor
:
- The Node Editor combines the functions of the nodes through wires. Let your function execution process become visualized.
How to Use It?
STNodeEditor
is very simple to use, you hardly need any learning costs. First, you need to know how to create a node.
You can create a node very simply like use WinForm:
public class MyNode : STNode
{
public MyNode() {
this.Title = "MyNode";
this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
this.AutoSize = false;
this.Size = new Size(100, 100);
var ctrl = new STNodeControl();
ctrl.Text = "Button";
ctrl.Location = new Point(10, 10);
this.Controls.Add(ctrl);
ctrl.MouseClick += new MouseEventHandler(ctrl_MouseClick);
}
void ctrl_MouseClick(object sender, MouseEventArgs e) {
MessageBox.Show("MouseClick");
}
}
stNodeEditor.Nodes.Add(new MyNode());
You can see that it is almost the same as developing a WinForm program. The only difference is that STNode
currently does not provide a WYSIWYG UI designer. Of course, the control type required by STNode
is STNodeControl
.
As the base class of STNode
control, STNodeControl
has many properties and events with the same name as System.Windows.Forms.Control
, allowing developers to develop a node like a WinForm program.
Note: In this version (2.0), no available control is provided. Only the STNodeControl
base class needs to be extended by the developer. If available later, the author will improve it.
The above example is just to make everyone feel familiar, because it is very similar to WinForm.
The most important function for a node is data input and output. For a node, there are two important properties, InputOptions
and OutOptions
and the data type is STNodeOption
.
public class MyNode : STNode
{
protected override void OnCreate() {
base.OnCreate();
this.Title = "TestNode";
int nIndex = this.InputOptions.Add(new STNodeOption("IN_1", typeof(string), false));
STNodeOption op = this.InputOptions.Add("IN_2", typeof(int), true);
this.OutputOptions.Add("OUT", typeof(string), false);
}
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);
}
}
Now you can see that the node has a point that can be used to connect. But currently, the connection point does not have any function.
From the above case, we can see that STNodeOption
is the connection option of STNode
. The connection option can be multi-connection and single-connection mode.
public class MyNode : STNode {
protected override void OnCreate() {
base.OnCreate();
this.Title = "MyNode";
this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
this.InputOptions.Add("Single", typeof(string), true);
this.OutputOptions.Add("Multi", typeof(string), false);
}
}
- In multi-connection mode:
- An option can be connected by multiple options of the same data type (rectangle)
- In single-connection mode:
- An option can only be connected by one option of the same data type (circle)
How to Interact With Data?
STNodeOption
can get all the data input of this option by binding to the DataTransfer
event.
STNodeOption.TransferData(object)
function can transfer data to all connections on this option.
Let's make two nodes to show it:
Create a node whose function is to output the current system time every second.
public class ClockNode : STNode
{
private Thread m_thread;
private STNodeOption m_op_out_time;
protected override void OnCreate() {
base.OnCreate();
this.Title = "ClockNode";
m_op_out_time = this.OutputOptions.Add("Time", typeof(DateTime), false);
}
protected override void OnOwnerChanged() {
base.OnOwnerChanged();
if (this.Owner == null) {
if (m_thread != null) m_thread.Abort();
return;
}
this.Owner.SetTypeColor(typeof(DateTime), Color.DarkCyan);
m_thread = new Thread(() => {
while (true) {
Thread.Sleep(1000);
m_op_out_time.TransferData(DateTime.Now);
}
}) { IsBackground = true };
m_thread.Start();
}
}
Of course, we can directly display the time of the above node, but in order to demonstrate the data transfer, we also need a node that accepts the data.
public class ShowClockNode : STNode {
private STNodeOption m_op_time_in;
protected override void OnCreate() {
base.OnCreate();
this.Title = "ShowTime";
m_op_time_in = this.InputOptions.Add("--", typeof(DateTime), true);
m_op_time_in.DataTransfer += new STNodeOptionEventHandler(op_DataTransfer);
}
void op_DataTransfer(object sender, STNodeOptionEventArgs e) {
if (e.Status != ConnectionStatus.Connected || e.TargetOption.Data == null) {
this.SetOptionText(m_op_time_in, "--");
} else {
this.SetOptionText(m_op_time_in, ((DateTime)e.TargetOption.Data).ToString());
}
}
}
You can see that ShowClockNode
is refreshing every second.
The above is a relatively complicated example, and the code is not given here.
Click the Open Image button, you can select an image and display it in the node. After the ImageSizeNode
is connected, the size of the image will be display.
For ImageShowNode
, it just provides the data source and displays it. For the ImageSizeNode
and ImageChannel
nodes, they don't know what node will be connected. They just complete their functions and package the results to the output option, waiting to be connected by the next node.
The execution logic is completely connected by the user to connect their functions together. During the development, there is no interaction between nodes and nodes. The only thing that ties them together is an Image
data type, so that nodes and nodes. There is no coupling relationship between them. High class poly low coupling.
For more tutorials, please refer to this link.
About the Future
The current framework is not perfect, it only provides some very basic functions, and there are still many functions to be realized. The author will continue to update in subsequent versions. Such as provides some common controls available to nodes and a set of executable frameworks, so that developers only need to provide DLL files containing STNode
. Like this picture:
This is the author's original idea and the first demo. Developers only need to provide the DLL file containing STNode
. The program will be automatically loaded into the TreeView
, and the user only needs to drag the Node
to the NodeEditor
to combine the logic and execution flow.
You can see the Start button on it, yes. In some application scenarios, the developer hopes that the user clicks the Start button after processing the logic to start the execution process.
It can also be done in the current version, but you need to implement it yourself. First, you need to define a rule, for example, each node must contain Start
and Stop
functions. Or the node that only needs to provide data input must include Start
and Stop
functions.
Such as:
public abstract class BaseNode : STNode
{
public abstract void Start();
public abstract void Stop();
}
public abstract class InputNode : BaseNode { }
public abstract class OutputNode : BaseNode { }
public abstract class ExecNode : BaseNode { }
public class TestInputNode : InputNode
{
[STNodeProperty("NameInPropertyGrid", "Description")]
public string TestText { get; set; }
private STNodeOption m_op_out;
protected override void OnCreate() {
base.OnCreate();
this.Title = "StringInput";
m_op_out = this.OutputOptions.Add("OutputString", typeof(string), false);
}
public override void Start() {
m_op_out.TransferData(this.TestText);
this.LockOption = true;
}
public override void Stop() {
this.LockOption = false;
}
}
public class TextFileOutputNode : OutputNode
{
[STNodeProperty("FileName", "Description")]
public string FileName { get; set; }
private StreamWriter m_writer;
protected override void OnCreate() {
base.OnCreate();
this.InputOptions.Add("Text", typeof(string), false)
.DataTransfer += new STNodeOptionEventHandler(op_DataTransfer);
}
void op_DataTransfer(object sender, STNodeOptionEventArgs e) {
if (e.Status != ConnectionStatus.Connected) return;
if (e.TargetOption.Data == null) return;
if (m_writer == null) return;
lock (m_writer) m_writer.WriteLine(e.TargetOption.Data.ToString());
}
public override void Start() {
m_writer = new StreamWriter(this.FileName, false, Encoding.UTF8);
this.LockOption = true;
}
public override void Stop() {
this.LockOption = false;
if (m_writer == null) return;
m_writer.Close();
m_writer = null;
}
}
And when the Start button was clicked:
public void OnClickStart() {
List<InputNode> lst_input = new List<InputNode>();
List<OutputNode> lst_output = new List<OutputNode>();
List<BaseNode> lst_other = new List<BaseNode>();
foreach (var v in stNodeEditor.Nodes) {
if ((v is BaseNode)) continue;
if (v is InputNode) {
lst_input.Add((InputNode)v);
} else if (v is OutputNode) {
lst_output.Add((OutputNode)v);
} else {
lst_other.Add((BaseNode)v);
}
}
if (lst_output.Count == 0)
throw new Exception("Can not found [OutputNode] please add it.");
if (lst_input.Count == 0)
throw new Exception("Can not found [InputNode] please add it.");
foreach (var v in lst_other) v.Start();
foreach (var v in lst_output) v.Start();
foreach (var v in lst_input) v.Start();
stNodePropertyGrid1.ReadOnlyModel = true;
}
If you want only one InputNode
to be added:
stNodeEditor.NodeAdded += new STNodeEditorEventHandler(stNodeEditor_NodeAdded);
void stNodeEditor_NodeAdded(object sender, STNodeEditorEventArgs e) {
int nCounter = 0;
foreach (var v in stNodeEditor.Nodes) {
if (v is InputNode) nCounter++;
}
if (nCounter > 1) {
System.Windows.Forms.MessageBox.Show("Only one InputNode can be added");
stNodeEditor.Nodes.Remove(e.Node);
}
}
But I think no one has such a demand, right?
Of course, the above code does not have any exception handling, just to show you how to implement the logic. In fact, a lot of code needs to be supplemented and improved in order to make a universal framework, so the author intends to complete it in subsequent versions. The above code is just to show you how to do it by yourself if you have similar requirements as above.
Points of Interest
When there are many applications (modules), they need to call each other to transfer data to complete a set of processes.
It is easy to develop a single-function application (module), but it is tedious to develop a whole set of applications that call each other with many functions.
Developers using this framework only need to define the data type, and then develop a single function node, as for the execution process, hand it over to the framework and the user connection.
For more information, please refer to https://debugst.github.io/STNodeEditor/index_en.html.
If you think STNodeEditor
is useful, you can recommend it to your friends, and mark it.
Thanks for reading!
History
- 19th May, 2021: Initial version