GithubHelp home page GithubHelp logo

debugst / stnodeeditor Goto Github PK

View Code? Open in Web Editor NEW
531.0 531.0 168.0 11.88 MB

一款基于.Net WinForm的节点编辑器 纯GDI+绘制 使用方式非常简洁 提供了丰富的属性以及事件 可以非常方便的完成节点之间数据的交互及通知 大量的虚函数供开发者重写具有很高的自由性

License: MIT License

C# 100.00%
control csharp dotnet gdiplus nodeeditor winfrom

stnodeeditor's Introduction

Version 3.0

现在: 2022-08-30

3.0版本进入排期开发,开发进度将同步更新。具体说明请查看 V3_CN.md

now: 2022-08-30

The version 3.0 has been start coding,The development progress is updated synchronously. more info refer V3_EN.md

2023-04-20

老铁们,最近问更新情况的人比较多。确实摆烂了一段时间,什么也不想做。不过现在陆陆续续回到状态了。不过在继续更新之前手里还有一个STJson。也正是当前项目需要的。STJson项目也摆烂很久了。。不过这两天应该会完工了,提供了全套的Json解析操作包括JsonPath的支持。。如果有经常使用Json的小伙伴可以关注一下。现在已经进入最后的调试和文档教程编写阶段。。等完工后回到STNodeEditor的开发,并且STNodeEditor的数据保存也将提供Json格式。

STNodeEditor

VS2010 .NET35 NuGet license

STNodeEditor 是一个轻量且功能强大的节点编辑器 纯GDI实现无任何依赖库仅仅100+Kb 使用方式非常简洁 提供了丰富的属性以及事件可以非常方便的完成节点之间数据的交互及通知 大量的虚函数可供开发者重写具有很高的自由性

Environment: VS2010(.NET 3.5)

STNodeEditor STNodeEditor

项目主页 (Project home): DebugST.github.io/STNodeEditor (简体中文, English)

教程文档: DebugST.github.io/STNodeEditor/doc_cn.html

Tutorials and API: DebugST.github.io/STNodeEditor/doc_en.html

Mail: ([email protected])

NuGet: https://www.nuget.org/packages/ST.Library.UI/

PM> Install-Package ST.Library.UI -Version 2.0.0

简介

那是一个冬季 在研究无线电安全的作者接触到了GNURadio 那是作者第一次接触到节点编辑器

-> What? Excuse me... What"s this?.. 这是什么鬼东西?...

那是一个春季 不知道为什么 过完年整个世界都变了 大家被迫窝在家里 无聊至极的作者学起了Blender那是作者第二次接触到节点编辑器

-> Wo...原来这东西可以这么玩...真方便

于是一些想法在作者脑中逐渐诞生 让作者有了想做一个这样的东西的想法

那是一个夏季 不知道为什么 作者又玩起了Davinci那是作者第三次接触到节点编辑器 这一次的接触让作者对节点编辑器的好感倍增 作者瞬间觉得 只要是可以模块化流程化的功能 万物皆可节点化


像流程图一样使用你的功能

你是否有设想过流程图不再是流程图 而是直接可以执行的?

在一些开发过程中我们可能会为整个程序设计一个流程图 上面包含了我们存在的功能模块以及执行流程 然后由开发者逐一实现

但是这样会带来一些问题 程序的执行流程可能会被硬编码到程序中去 如果突然有一天可能需要改变执行顺序或者添加删除一个执行模块 可能需要开发者对代码重新编辑然后编译 而且各个功能模块之间的调用也需要开发者进行编码调度 增加开发成本 等一系列的问题

STNodeEditor 就是为此诞生


STNodeEditor 包含3部分 TreeView PropertyGrid NodeEditor 这三部分组成了一套完整的可使用框架

  • TreeView
    • 可以把执行功能编码到一个节点中 而 TreeView 则负责展示以及检索节点 在 TreeView 中的节点可直接拖拽添加到 NodeEditor
  • PropertyGrid
    • 类似与 WinForm 开发使用的属性窗口 作为一个节点 它也是可以有属性的 而作者在编辑器进行设计的过程中也把一个节点视作一个 Form 让开发者几乎没有什么学习成本直接上手一个节点的开发 *NodeEditor *NodeEditor 是用户组合自己执行流程的地方 使得功能模块执行流程可视化

如何使用它?

STNodeEditor的使用非常简单 你几乎可以没有任何学习成本的去使用的 当然最重要的一点就是 你需要知道如何去创建一个节点

你可以像创建一个Form一样去创建一个Node

using ST.Library.UI.NodeEditor;
 
public class MyNode : STNode
{
    public MyNode() { //等同于 [override void Oncreate(){}]
        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());

MyNode.png

可以看到它的使用方式和 Form 确实很像 其实目前还暂时没有提供所见即所得的UI设计器 而且一个 STNode 它同样有它的控件集合且数据类型为 STNodeControl

STNodeControl 作为 STNode 控件的基类 它拥有着和 System.Windows.Forms.Control 许多同名的属性和事件 一切的初衷都只为与 WinForm 靠近

注意:在目前的版本中(2.0) STNodeEditor仅仅提供了STNodeControl基类 并未提供任何一个可用控件 当然在附随的Demo工程中包含了部分示例演示如何自定义一个控件 由于这属于自定义控件的范畴 所以演示并未太多 若需了解关于自定义控件如何开发可参考作者:自定义控件开发 系列文章 当然在后续的版本中 作者将提供部分常用控件 虽说作者想把使用方式往WinForm上靠 单仅仅是把它当作WinForm使用并不是作者的初衷

上面的演示仅仅是为了让大家感到亲切感 毕竟 WinForm 可能是大家熟悉的一个东西 但是如果仅仅是把它当作 WinForm 使用毫无意义 对于一个节点来说 最重要的属性当然是数据的输入和输出

public class MyNode : STNode
{
    protected override void OnCreate() {//等同 [public MyNode(){}]
        base.OnCreate();
        this.Title = "TestNode";
        //可以得到添加的索引位置
        int nIndex = this.InputOptions.Add(new STNodeOption("IN_1", typeof(string), false));
        //可以得到添加的 STNodeOption
        STNodeOption op = this.InputOptions.Add("IN_2", typeof(int), true);
        this.OutputOptions.Add("OUT", typeof(string), false);
    }
    //当所有者发生改变(即:在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.SetOptionDotColor(op, Color.Red); //无需在OnOwnerChanged()中设置
    }
}

MyNode.png

通过上面的案例你可以看到 STNode 有两个重要的属性 InputOptionsOutputOptions 其数据类型为 STNodeOptionSTNodeOption 有两种连接模式 single-connectionmulti-connection

  • single-connection
    • 单连接模式 在单连接模式下一个连接点同时 只能被一个 同数据类型点的连接
  • multi-connection
    • 多连接模式 在多连接模式下一个连接点同时 可以被多个 同数据类型点连接
public class MyNode : STNode {
    protected override void OnCreate() {
        base.OnCreate();
        this.Title = "MyNode";
        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
        //multi-connection
        this.InputOptions.Add("Single", typeof(string), true);
        //single-connection
        this.OutputOptions.Add("Multi", typeof(string), false);
    }
}

MyNode.png


如何进行数据交互?

在上面的案例中仅仅是做了一个可以被连接的选项点 并不包含任何的功能

  • STNodeOption可以通过绑定DataTransfer事件获取到传入该选项的所有数据
  • STNodeOption可以通过TransferData(object obj)向该选项上所有连接的选项进行数据投递

下面通过一个案例进行演示 创建两个节点 一个节点用于每秒输出一次当前系统事件 另一个节点用于接收一个事件并显示

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);
                //STNodeOption.TransferData(object)会自动设置STNodeOption.Data
                //然后自动向所有连接的选项进行数据传递
                m_op_out_time.TransferData(DateTime.Now);
                //如果你需要一些耗时操作STNode同样提供了Begin/Invoke()操作
                //this.BeginInvoke(new MethodInvoker(() => {
                //    m_op_out_time.TransferData(DateTime.Now);
                //}));
            }
        }) { IsBackground = true };
        m_thread.Start();
    }
}

当然上面可以直线将时间显示出来 不过这里为了演示数据的传递 所以还需要一个接收节点

public class ShowClockNode : STNode {
    private STNodeOption m_op_time_in;
    protected override void OnCreate() {
        base.OnCreate();
        this.Title = "ShowTime";
        //采用 "single-connection" 模式
        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) {
            //当 STNode.AutoSize=true 并不建议使用STNode.SetOptionText
            //因为当文本发生改变时候会重新计算布局 正确的做法是自定义一个如Lable控件
            //作为时间的显示 当然这里为了演示方式采用此方案
            this.SetOptionText(m_op_time_in, "--");
        } else {
            this.SetOptionText(m_op_time_in, ((DateTime)e.TargetOption.Data).ToString());
        }
    }
}

TimeNode.gif

可以看到当连接被建立时 ShowTime 节点每秒都在刷新 下面是一个更加复杂一点的案例 但是这里并没有给出代码请参考附随工程的 Demo

ImageNode.png

点击 Open Image 按钮可打开并显示一张图片在 ImageShowNode 中并将图片作为输出数据 ImageChanel 则负责接收一张图像并处理输出图像的RGB图像及原图 ImageSize 则负责接收并显示一张图像的尺寸信息

对于上面的节点在开发期间它们并不知道会被什么样的节点连接 也并不知道会被连接到什么节点上 开发者仅仅是完成了自己的功能处理接收到的数据并将结果打包给 STNodeOption 无需关系最终会被谁把结果拿走并处理 使得节点之间与节点之间的耦合关系大大降低 唯一将它们联系在一起的是一个 Image 数据类型 最终执行的逻辑交给用户自己拖拽节点组合他们自己想要的流程 使得功能的执行流程变得可视化 这也是作者的初衷

关于更多的教程和文档请参考:https://debugst.github.io/STNodeEditor/doc_cn.html 在下载的调用库压缩包里面同样包含离线版文档


关于下个版本

其实目前这个版本还有很多需要完善的代码 如上面提到的提供一些基础控件 而且目前提供的东西还很原始 一些应用场景目前需要开发者自己写代码完成

First.png

上图为作者的最初构思以及第一个 Demo 演示版本 在上图中可以看到有 启动 按钮 某些应用场景下可能需要用户点击执行按钮以后才开始执行用户所部署的逻辑 而之前上面的案例数据交互都是更具用户的布线实时的 当然在目前的版本中想实现也是可以的 只是需要开发者自己写部分代码 由于这部分的代码作者暂时还没有构思好很多细节处理 所以还有下一个版本的话很多功能都将出现

上图的构想是 开发者无需关系架构执行逻辑什么的 而开发者只需要关系功能点本省只需要开发出包含 STNodeDLL 文件 而程序启动 TreeView 会自动加载目录下的 DLL 文件并装载 STNodeTreeView 中 然后让用户拖拽执行 对于上一段话中作者提到的需要通过 启动 按钮执行如何在当前版本的实现 作者这里给出一些思路

//首先定义一个基类 包含Start和Stop方法
public abstract class BaseNode : STNode
{
    public abstract void Start();
    public abstract void Stop();
}
//===================================================================
//然后再基于基类在定义3个类型
//InputNode 将作为开始节点 作为数据执行的入口节点 类似与Main函数一样
public abstract class InputNode : BaseNode { }
//OutputNode 将作为最终数据的处理节点 如文件保存 数据库保存等
public abstract class OutputNode : BaseNode { }
//更具自己需求定义一些其他执行功能的节点
public abstract class ExecNode : BaseNode { }
//===================================================================
//创建一个 TestInputNode 提供一个字符串输入 并作为开始节点
public class TestInputNode : InputNode
{
    //使用"STNodeProperty"特性则此属性会在"STNodePropertyGrid"中显示
    [STNodeProperty("希望显示的属性名字", "属性秒速")]
    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;//结束后解锁选项
    }
}
//===================================================================
//创建一个 TextFileOutputNode 用于文本文件保存收到的字符串
public class TextFileOutputNode : OutputNode
{
    [STNodeProperty("属性显示名称", "属性描述")]
    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;
    }
}

上面的代码演示了一个 输入输出 类型的节点 至于其他需求自行举一反三 当用户点下 启动 按钮时候

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("没有找到 [OutputNode] 类型的节点 请添加.");
    if (lst_input.Count == 0)
        throw new Exception("没有找到 [InputNode] 类型的节点 请添加.");
    foreach (var v in lst_other) v.Start();
    foreach (var v in lst_output) v.Start();
    //最起码 InputNode 类型的节点至少得又一个吧 不然怎么开始.
    //而且 InputNode 类型的节点应当是最后启动
    foreach (var v in lst_input) v.Start();
    stNodePropertyGrid1.ReadOnlyModel = true;//不要忘记设置属性窗口只读
}

如果你希望只能有一个 InputNode 类型的节点被添加

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("只能有一个 InputNode 被添加");
        stNodeEditor.Nodes.Remove(e.Node);
    }
}

当然 这个需求估计很少有吧

当然这里就并没有给出上述代码片段的执行效果了 因为上面仅仅是提供思路 让读者可以举一反三 而且上面的代码均没有任何的异常处理 要真正做好其实还有很多细节需要处理很多代码需要写 所以暂定目前版本不提供这样的功能

关于作者

stnodeeditor's People

Contributors

debugst avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stnodeeditor's Issues

关于NodeProperty面板和Node控件同步更新的问题

你好,我遇到了一个问题
在NodeProperty里更改了节点的某一个属性,但是在节点那边好像不能同步更新
我在节点里加了一个enum,并打上了STNodeProperty标记,还在节点上搞了个控件,可以在节点中显示并且更改这个enum,总的来说我既可以在NodeProperty里面更改这个enum,也可以通过节点上的控件更改这个enum。
但是现在就出现了一个问题,我在NodeProperty里更改了这个enum的值,但是节点上的控件不能同步更新(显示的还是更改前的enum),我必须把鼠标移上节点它才能更新。并且我通过节点上的控件更改了enum的值,但是NodeProperty也不能同步更新,我必须把鼠标移上NodeProperty面板它才会更新。
我很想做成节点和NodeProperty同步更新的效果,但是我尝试了很久都没有效果...请问我该怎么做?

这是enum

    public enum ImageBlendMode
    {
        /// <summary>
        /// 变暗
        /// </summary>
        Darken = 1,
        /// <summary>
        /// 正片叠底
        /// </summary>
        Multiply = 0,
        /// <summary>
        /// 颜色加深
        /// </summary>
        ColorBurn = 2,
        /// <summary>
        /// 线性加深
        /// </summary>
        LinearBurn = 3,
        /// <summary>
        /// 变亮
        /// </summary>
        Lighten = 4,
        /// <summary>
        /// 滤色模式
        /// </summary>
        Screen = 5,
        /// <summary>
        /// 颜色减淡
        /// </summary>
        ColorDodge = 6,
        /// <summary>
        /// 线性减淡
        /// </summary>
        LinearDodge = 7,
        /// <summary>
        /// 叠加模式
        /// </summary>
        Overlay = 8,
    }

这是我写的对应控件

    public class STMultiSelectBox : STNodeControl
    {
        private bool m_b_enter;
        private bool m_b_l;
        private bool m_b_r;
        private bool m_b_down;
        private int index = 0;
        private int switchButtonWidth = 16;

        private Dictionary<int, string> options = new Dictionary<int, string>();
        public Dictionary<int, string> Options
        {
            get { return options; }
            set { options = value; }
        }
        public int SwitchButtonWidth
        {
            get { return switchButtonWidth; }
            set { switchButtonWidth = value; }
        }
        public int Index
        {
            get { return index; }
            set { index = value; }
        }

        protected override void OnMouseEnter(EventArgs e)
        {
            base.OnMouseEnter(e);
            m_b_enter = true;
            this.Invalidate();
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);
            m_b_l = false; m_b_r = false;
            m_b_enter = false;
            this.Invalidate();
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            m_b_down = true;
            this.Invalidate();
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (m_b_r)
            {
                if (index < options.Count - 1) index++;
                else index = 0;
            }
            else if (m_b_l)
            {
                if (index > 0) index--;
                else index = options.Count - 1;
            }
            base.OnMouseUp(e);
            m_b_down = false;
            this.Invalidate();
        }
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            m_b_l = false; m_b_r = false;
            if (e.Location.X <= switchButtonWidth) m_b_l = true;
            if (e.Location.X >= Width - switchButtonWidth) m_b_r = true;
            this.Invalidate();
        }

        protected override void OnPaint(DrawingTools dt)
        {
            index = Mathf.Clamp(index, 0, options.Count - 1);
            //base.OnPaint(dt);
            Graphics g = dt.Graphics;
            SolidBrush brush = dt.SolidBrush;
            brush.Color = this.BackColor;
            g.FillRectangle(brush, 0, 0, this.Width, this.Height);//绘制背景黑色

            //鼠标移上背景变量
            brush.Color = NodeControlsColor.ColorDark;
            if (m_b_enter) g.FillRectangle(brush, 0, 0, this.Width, this.Height);//绘制背景黑色
            //绘制鼠标移上两边按钮
            brush.Color = m_b_down ? NodeControlsColor.ActivitiveColorLight : NodeControlsColor.ActivitiveColorDark;//不按下偏白的灰色,按下亮蓝色
            if (m_b_l) g.FillRectangle(brush, 0, 0, switchButtonWidth, this.Height);
            if (m_b_r) g.FillRectangle(brush, Width - switchButtonWidth, 0, switchButtonWidth, this.Height);
            //绘制三角形
            Point centerL = new Point(switchButtonWidth / 2, Height / 2);
            Point centerR = new Point(((Width - switchButtonWidth) + Width) / 2, Height / 2);
            //左边
            Point pointTopL = new Point(centerL.X - 3, centerL.Y);
            Point pointBot1L = new Point(centerL.X + 3, centerL.Y + 6);
            Point pointBot2L = new Point(centerL.X + 3, centerL.Y - 6);
            brush.Color = m_b_enter ? NodeControlsColor.ActivitiveColorWhite : NodeControlsColor.ActivitiveColorDark;
            g.FillPolygon(brush, new Point[] { pointTopL, pointBot1L, pointBot2L });
            //右边
            Point pointTopR = new Point(centerR.X + 3, centerR.Y);
            Point pointBot1R = new Point(centerR.X - 3, centerR.Y + 6);
            Point pointBot2R = new Point(centerR.X - 3, centerR.Y - 6);
            brush.Color = m_b_enter ? NodeControlsColor.ActivitiveColorWhite : NodeControlsColor.ActivitiveColorDark;
            g.FillPolygon(brush, new Point[] { pointTopR, pointBot1R, pointBot2R });

            g.DrawString($"{Text}:{options[index]}", new Font("courier new", 7f), !m_b_enter ? Brushes.LightGray : Brushes.White, new Rectangle(0, 2, Width, Height), base.m_sf);//绘制颜色
        }
    }

然后这是节点

    public class ColorBlendImageNode : STNode
    {
        private STNodeOption m_op_in_imageBase;
        private STNodeOption m_op_in_imageBlend;
        private STNodeOption m_op_in_fac;
        private STNodeOption m_op_out;

        private STImageBox imageBox;
        private STMultiSelectBox selectBox;

        [STNodeProperty("模式", "")]
        public ImageBlendMode mode
        {
            get
            {
                return (ImageBlendMode)selectBox.Index;
            }
            set
            {
                selectBox.Index = (int)value;
                OutImage();
            }
        }
        protected override void OnCreate()
        {
            base.OnCreate();
            this.Title = "颜色混合";
            this.Tag = "解析/转换";
            this.ItemHeight = 30;
            this.TitleHeight = 30;
            this.TitleColor = Color.FromArgb(200, Color.GreenYellow);
            this.AutoSize = false;
            this.Size = new Size(190 + 10 * 2, 10 + ItemHeight * 3 + 190 + TitleHeight + 35);

            m_op_in_imageBase = this.InputOptions.Add("基图像", typeof(Image), true);
            m_op_in_imageBlend = this.InputOptions.Add("混合图像", typeof(Image), true);
            m_op_in_fac = this.InputOptions.Add("系数", typeof(float), true);

            m_op_out = this.OutputOptions.Add("输出", typeof(Image), false);

            m_op_in_imageBase.DataTransfer += new STNodeOptionEventHandler(m_op_in_DataTransfer);
            m_op_in_imageBlend.DataTransfer += new STNodeOptionEventHandler(m_op_in_DataTransfer);
            m_op_in_fac.DataTransfer += new STNodeOptionEventHandler(m_op_in_DataTransfer);

            selectBox = new STMultiSelectBox();
            selectBox.Location = new Point(10, 5 + ItemHeight * 3);
            selectBox.Size = new Size(190, 30);
            selectBox.Text = "混合模式";
            selectBox.Options.Add(0, "正片叠底");
            selectBox.Options.Add(1, "变暗");
            selectBox.Options.Add(2, "颜色加深");
            selectBox.Options.Add(3, "线性加深");
            selectBox.Options.Add(4, "变亮");
            selectBox.Options.Add(5, "滤色");
            selectBox.Options.Add(6, "颜色减淡");
            selectBox.Options.Add(7, "线性减淡");
            selectBox.Options.Add(8, "叠加");

            selectBox.MouseUp += new MouseEventHandler(selectBox_MouseUp);

            imageBox = new STImageBox();
            imageBox.Location = new Point(10, 5 + ItemHeight * 3 + 35);
            imageBox.Size = new Size(190, 190);

            this.Controls.Add(imageBox);
            this.Controls.Add(selectBox);

            m_op_in_fac.Data = 1f;
        }
        //无论AutoSize为何值 都可以对选项连接点位置进行修改

        protected override Point OnSetOptionDotLocation(STNodeOption op, Point pt, int nIndex)
        {
            //if (op == m_op_out_x) return new Point(pt.X, pt.Y + ItemHeight);
            //if (op == m_op_out_y) return new Point(pt.X, pt.Y + ItemHeight);
            return base.OnSetOptionDotLocation(op, pt, nIndex);
        }
        protected override Rectangle OnSetOptionTextRectangle(STNodeOption op, Rectangle rect, int nIndex)
        {
            //if (op == m_op_out_x) return new Rectangle(rect.X, rect.Y + ItemHeight, rect.Width, rect.Height);
            //if (op == m_op_out_y) return new Rectangle(rect.X, rect.Y + ItemHeight, rect.Width, rect.Height);
            return base.OnSetOptionTextRectangle(op, rect, nIndex);
        }

        void OutImage()
        {
            if (m_op_in_imageBase.Data != null && m_op_in_imageBlend.Data != null)
            {
                Bitmap bitmapBase = new Bitmap((Image)m_op_in_imageBase.Data);
                Bitmap bitmapBlend = new Bitmap((Image)m_op_in_imageBlend.Data);
                float fac = (float)m_op_in_fac.Data;

                Bitmap bitmap = ImageTool.ColorBlending(bitmapBase, bitmapBlend, fac, mode);
                m_op_out.TransferData(bitmap);
                imageBox.Image = bitmap;
                //File.Delete(Application.StartupPath + "\\Cache\\CacheBitmap.png");
            }
        }

        void selectBox_MouseUp(object sender, MouseEventArgs e)
        {
            OutImage();
        }
        void m_op_in_DataTransfer(object sender, STNodeOptionEventArgs e)
        {
            try
            {
                if (e.Status != ConnectionStatus.Connected || e.TargetOption.Data == null)
                {
                    if (sender == m_op_in_imageBase) m_op_in_imageBase.Data = null;
                    if (sender == m_op_in_imageBlend) m_op_in_imageBlend.Data = null;
                    if (sender == m_op_in_fac) m_op_in_fac.Data = 1f;
                    m_op_out.TransferData(null);
                }
                else
                {
                    if (sender == m_op_in_imageBase) m_op_in_imageBase.Data = e.TargetOption.Data;
                    if (sender == m_op_in_imageBlend) m_op_in_imageBlend.Data = e.TargetOption.Data;
                    if (sender == m_op_in_fac) m_op_in_fac.Data = e.TargetOption.Data;
                }
                OutImage();
            }
            catch (Exception ex)
            {
                UsefulMethods.ShowError(ex);
            }
        }
    }

Why CanConnect called two times inside ConnectOption () ?

Hi,

I'm studying at STNodeEditor.
Thank you for providing a wonderful project!!

I'm not very good at English and I use online translators a lot, so I'm sorry if my sentences sound weird.

I trying to change it so that objects can be connected,If the object can be cast.
CanConnect() intend to return "ConnectionStatus.connected" if cast is possible inside functuin.

There is something I don't know, why is CanConnect called two times inside the ConnectOption () function?

For example, I can cast from int to float, but I can't cast from float to int.
There is a problem when checked in both directions.

I want to change the function call once. What do you think?

Thanks,

Getting connected option from option

Hi,
Im probably understanding something poorly but Im trying to get option connected to my option and it acts like this:
opt.ConnectionCount returns 1 but opt.GetConnectedOption() returns list of 0 elements

Any idea why this could happen or what Im doing/understanding incorrectly?

STNode 定义只读属性保存后,再读取时会报异常

假如定义如下属性
[STNodeProperty("Name", "唯一标识")]
public string Name { get { return this.Guid.ToString(); } }
则在保存后,再打开会出现异常。
我定义了如下特性
public class STNoSaveAttribute: Attribute
{
}
并修改 STNode的OnSaveNode事件中
foreach (var p in t.GetProperties()) {
var attrs = p.GetCustomAttributes(true);
// 增加检测
if (attrs.Where(s => s is STNoSaveAttribute).Count() > 0) continue;
……
}

这样就没有错误了
只是一种方案,也许有更好的。

怎么获取节点的连接关系

你好,这个应该就是保存的stn文件的数据结构
我是想用这个描述PLC设备的工作流程,具体设想是这样的
1.编辑一些工作任务的节点,比如电机转动,继电器开关之类的,这个教程已经讲了,目前也知道怎么去编辑自己的节点
2.在edit界面实现任务的流程,就是先干啥后干啥这种,这个也没问题,直接拉线就行
3.把这个流程保存为json文件,需要知道节点前后级连接关系,或者知道stn文件的解析方式也行,这个不知道怎么做
4.把这个文件发给设备端,设备端解析出步骤,执行任务

请问怎么获取节点的前后级连接关系?

希望可以添加动态节点的支持

在很多场景里,节点一般都不是固定的,如果可以支持动态节点就更好了。
我自己也在尝试改STNodeTreeView相关的代码,但是因为工作时间比较紧张,不知道能不能做成泛用性好些的代码,只是一个小小的建议。感谢你的工作 : )

一些建议

一开始看到这个东西的时候 就觉得老哥很牛b,也在脑海里一直想这个东西的用处。
也在想项目里是否什么东西可以用上,后来想这确实不就是一个蓝图的功能么,目前在调研使用的时候发现几个问题:

  1. 视图里的右键功能没有,实际上可以把左侧的功能加入到右键,而不是每次都需要拖进去。
    比如,a 组件 右键 显示out可以连线的组件。

  2. 缺少整合蓝图的功能,意思就是如果后期数量多了,顶部也没有小缩略图,也看不到整体的样子的,线看起来乱七八糟的,无法一键让他按固定的样子排列好。

  3. 因为用到的是类似流程的蓝图,我本来是想利用这个蓝图功能生成对应的代码文件,也就是每个node 为一段代码,目前还没想到如何实现

最后还是感谢老哥开源出这么牛b的东西。

ReadOnlyModel 建议可以添加做为STNode属性

ReadOnlyModel 建议可以作为 STNode 属性,开启后只针对该节点, 外部的改成readOnly 作为全局统一的。

现在我想针对局部的节点是没有办法的,只能改造了

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.