Compare commits

...

30 Commits
V2.0 ... main

Author SHA1 Message Date
DebugST
a296731d3e
Merge pull request #32 from xincheng213618/main
Update net3.5->net4.8  && Add NET8.0 WPFdemo
2025-05-15 14:05:49 +08:00
xincheng
4c7334863c update 2025-05-13 12:09:26 +08:00
xincheng
83d86ed714 更新 库到net 4.8 2025-05-13 12:08:49 +08:00
xincheng
52c5065886 Add WPF Demo 2025-05-13 12:06:31 +08:00
DebugST
90acc47d87
Merge pull request #31 from surebrec/main
fix misspell (causing NPE)
2025-03-04 15:37:31 +08:00
kostas
acdfcb33f6 fix misspell (causing NPE) 2025-03-04 08:44:42 +03:00
DebugST
b22e328b17
Update README.md 2023-04-20 10:18:22 +08:00
DebugST
77b8f77a12 update progress 2022-10-17 14:56:00 +08:00
DebugST
b0e99fb148 update 2022-09-22 22:37:21 +08:00
DebugST
359c9a12bd update 2022-09-22 22:34:41 +08:00
DebugST
6b75cea71f update 2022-09-22 01:17:13 +08:00
DebugST
f55e6e129b update 2022-09-22 01:14:07 +08:00
DebugST
532d806f32 update 2022-09-22 01:08:10 +08:00
DebugST
5fc933ef55 auto dpi 2022-09-13 08:02:35 +08:00
DebugST
01e3216b56 start 3.0 2022-08-30 15:53:16 +08:00
DebugST
eed3ca2644 start 3.0 2022-08-30 15:50:37 +08:00
DebugST
e92206a462 start 3.0 2022-08-30 15:47:04 +08:00
DebugST
3ced81000a
Update README.md 2021-09-09 03:10:37 +08:00
DebugST
a2a39a7606
Update index.html 2021-05-22 22:18:50 +08:00
DebugST
502ade98d4
Update index_en.html 2021-05-22 22:18:18 +08:00
DebugST
f9e08a2837
Update index_en.html 2021-05-22 22:15:10 +08:00
DebugST
fde50edea3
Update index.html 2021-05-22 22:13:37 +08:00
DebugST
7dcf13b8d0 Modify some error messages. 2021-05-21 11:31:18 +08:00
DebugST
725f49b8ac
Update README.md 2021-05-18 22:54:36 +08:00
DebugST
0501aa2d2b
Update README.md 2021-05-18 22:15:05 +08:00
DebugST
1f2fa9637c Merge branch 'main' of https://github.com/DebugST/DotNet_WinForm_NodeEditor 2021-05-06 13:48:53 +08:00
DebugST
27c45d1368 modified the document page css 2021-05-06 13:48:20 +08:00
DebugST
2957bc40c9
Update README.md 2021-04-30 15:48:35 +08:00
DebugST
38a1341e1b
modified some error key world
modified some error key world
2021-04-30 15:45:44 +08:00
DebugST
ccf3fa5a56
update format
update some format
2021-04-30 15:34:31 +08:00
38 changed files with 2879 additions and 103 deletions

368
.gitignore vendored
View File

@ -1,4 +1,364 @@
.DS_Store
/Bin
bin/
obj/
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
.idea/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

398
README.md
View File

@ -1,75 +1,399 @@
# Version 3.0
现在: 2022-08-30
`3.0`版本进入排期开发,开发进度将同步更新。具体说明请查看 [V3_CN.md](./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](./V3_EN.md)
2023-04-20
老铁们,最近问更新情况的人比较多。确实摆烂了一段时间,什么也不想做。不过现在陆陆续续回到状态了。不过在继续更新之前手里还有一个[STJson](https://github.com/DebugST/STJson)。也正是当前项目需要的。`STJson`项目也摆烂很久了。。不过这两天应该会完工了,提供了全套的`Json`解析操作包括`JsonPath`的支持。。如果有经常使用`Json`的小伙伴可以关注一下。现在已经进入最后的调试和文档教程编写阶段。。等完工后回到`STNodeEditor`的开发,并且`STNodeEditor`的数据保存也将提供`Json`格式。
# STNodeEditor
[![VS2010](https://img.shields.io/badge/Visual%20Studio-2010-blueviolet)](https://visualstudio.microsoft.com/zh-hans/vs/) [![.NET35](https://img.shields.io/badge/DotNet-3.5-blue)](https://www.microsoft.com/zh-cn/download/details.aspx?id=25150) [![NuGet](https://img.shields.io/badge/NuGet-5.9-blue)](https://www.nuget.org/packages/ST.Library.UI/) [![license](https://img.shields.io/badge/License-MIT-green)](https://github.com/DebugST/STNodeEditor/blob/main/LICENSE)
STNodeEditor 是一个轻量且功能强大的节点编辑器 使用方式非常简洁 提供了丰富的属性以及事件可以非常方便的完成节点之间数据的交互及通知 大量的重载函数供开发者使用具有很高的自由性
STNodeEditor 是一个轻量且功能强大的节点编辑器 `GDI`实现无任何依赖库仅仅`100+Kb` 使用方式非常简洁 提供了丰富的属性以及事件可以非常方便的完成节点之间数据的交互及通知 大量的虚函数可供开发者重写具有很高的自由性
![STNodeEditor](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/page_top.png)
Environment: VS2010(.NET 3.5)
项目主页 (Project home): [DebugST.github.io/DotNet_WinForm_NodeEditor](https://DebugST.github.io/DotNet_WinForm_NodeEditor) (简体中文, English)
![STNodeEditor](https://debugst.github.io/STNodeEditor/images/page_top.png)
![STNodeEditor](https://debugst.github.io/STNodeEditor/images/node_scan.png)
教程文档: [DebugST.github.io/DotNet_WinForm_NodeEditor/doc_cn.html](https://DebugST.github.io/DotNet_WinForm_NodeEditor/doc_cn.html)
项目主页 (Project home): [DebugST.github.io/STNodeEditor](https://DebugST.github.io/STNodeEditor) (简体中文, English)
Tutorials and API: [DebugST.github.io/DotNet_WinForm_NodeEditor/doc_en.html](https://DebugST.github.io/DotNet_WinForm_NodeEditor/doc_en.html)
教程文档: [DebugST.github.io/STNodeEditor/doc_cn.html](https://DebugST.github.io/STNodeEditor/doc_cn.html)
Tutorials and API: [DebugST.github.io/STNodeEditor/doc_en.html](https://DebugST.github.io/STNodeEditor/doc_en.html)
Mail: (2212233137@qq.com)
# STNodeEditor
NuGet: [https://www.nuget.org/packages/ST.Library.UI/](https://www.nuget.org/packages/ST.Library.UI/)
![STNodeEditor](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/stnodeeditor.gif)
```
PM> Install-Package ST.Library.UI -Version 2.0.0
```
---
# 简介
`STNodeEditor`拥有非常强大的功能 支持画布的移动和缩放 可以对节点位置以及连线进行锁定 连线时候会自动检测数据类型是否兼容 以及连线是否重复或者构成环形线路等问题
那是一个冬季 在研究无线电安全的作者接触到了[GNURadio](https://www.gnuradio.org/) 那是作者第一次接触到节点编辑器
* 拖动标题移动节点
* 右击标题弹出菜单 (需要设置`ContextMenuStrip`)
* 拖动连接点进行连线
* 右击连线断开连接
* 中键拖动移动画布 (若笔记本触摸板支持 可二指拖动)
* CTRL+鼠标滚轮 缩放画布
-> What? Excuse me... What"s this?.. 这是什么鬼东西?...
__注:节点Body区域进行的操作编辑器不会响应 因为在节点客户区内部的操作将被转换为节点的事件__
那是一个春季 不知道为什么 过完年整个世界都变了 大家被迫窝在家里 无聊至极的作者学起了[Blender](https://www.blender.org/)那是作者第二次接触到节点编辑器
__因为作者将一个节点视为一个`Form` 而编辑器容器则为`Desktop` 开发者可以像开发`WinForm`程序一样去开发一个节点__
-> Wo...原来这东西可以这么玩...真方便
# STNodeHub
于是一些想法在作者脑中逐渐诞生 让作者有了想做一个这样的东西的想法
![STNodeHub](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/stnodehub.gif)
那是一个夏季 不知道为什么 作者又玩起了[Davinci](http://www.blackmagicdesign.com/cn/products/davinciresolve/)那是作者第三次接触到节点编辑器 这一次的接触让作者对节点编辑器的好感倍增 作者瞬间觉得 只要是可以模块化流程化的功能 万物皆可节点化
`STNodeHub`是一个内置的节点 其主要作用分线 可以将一个输出分散到多个输入或多个输出集中到一个输入点上以避免重复布线 也可在节点布线复杂时用于绕线
---
HUB的输入输出默认为`object`类型 当一个连接被连入时候将会自动更换数据类型并增加新行
# 像流程图一样使用你的功能
__注:仅`STNodeHub`可以修改连接点的数据类型 因为相应字段被`internal`标记 而作为第三方扩展的STNode中是无法修改已添加连接点的数据类型的__
你是否有设想过流程图不再是流程图 而是直接可以执行的?
## STNodeTreeView
在一些开发过程中我们可能会为整个程序设计一个流程图 上面包含了我们存在的功能模块以及执行流程 然后由开发者逐一实现
![STNodeTreeView](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/stnodetreeview.gif)
但是这样会带来一些问题 程序的执行流程可能会被硬编码到程序中去 如果突然有一天可能需要改变执行顺序或者添加删除一个执行模块 可能需要开发者对代码重新编辑然后编译 而且各个功能模块之间的调用也需要开发者进行编码调度 增加开发成本 等一系列的问题
`STNodeTreeView`可与`STNodeEditor`结合使用`STNodeTreeView`中的节点可直接拖拽进`STNodeEditor`中 并且提供预览和检索功能
`STNodeEditor` 就是为此诞生
`STNodeTreeView`的使用简单 无需像`System.Windows.Forms.TreeView`需要自行去构造树
---
通过使用`STNodeAttribute`标记继承的`STNode`可直接设置需要在`STNodeTreeView`中显示的路径 以及希望在`STNodePropertyGrid`中显示的信息
`STNodeEditor` 包含3部分 `TreeView` `PropertyGrid` `NodeEditor` 这三部分组成了一套完整的可使用框架
__注:若希望节点能够在`STNodeTreeView`中显示 必须使用`STNodeAttribute`标记`STNode`子类__
* TreeView
* 可以把执行功能编码到一个节点中 而 `TreeView` 则负责展示以及检索节点 在 `TreeView` 中的节点可直接拖拽添加到 `NodeEditor`
* PropertyGrid
* 类似与 `WinForm` 开发使用的属性窗口 作为一个节点 它也是可以有属性的 而作者在编辑器进行设计的过程中也把一个节点视作一个 `Form` 让开发者几乎没有什么学习成本直接上手一个节点的开发
*NodeEditor
*`NodeEditor` 是用户组合自己执行流程的地方 使得功能模块执行流程可视化
---
# 如何使用它?
# STNodePropertyGrid
STNodeEditor的使用非常简单 你几乎可以没有任何学习成本的去使用的 当然最重要的一点就是 你需要知道如何去创建一个节点
![STNodePropertyGrid](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/stnodepropertygrid.gif)
你可以像创建一个Form一样去创建一个Node
`STNode`中的属性被`STNodePropertyAttribute`标记则会在`STNodePropertyGrid`中显示 默认情况下支持`int,float,double,bool,string,enum`以及上述数据类型的`Array` 若希望显示的属性数据类型不被支持 可以对`DescriptorType`进行扩展重写 详细请参考DEMO
``` cs
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());
```
<img alt="MyNode.png" src="https://img-blog.csdnimg.cn/img_convert/7c7dd3f7b17c18781c54dc210555bf56.png" width="273">
可以看到在`STNodePropertyGrid`的面板中可以显示节点的一些信息 作者认为提供给大家的是一套框架 大家可以基于这套框架打造一套自己的框架
可以看到它的使用方式和 `Form` 确实很像 其实目前还暂时没有提供所见即所得的UI设计器 而且一个 `STNode` 它同样有它的控件集合且数据类型为 `STNodeControl`
__而为框架编写节点的`Coder`应该有权利选择是否留下个人信息__
`STNodeControl` 作为 `STNode` 控件的基类 它拥有着和 `System.Windows.Forms.Control` 许多同名的属性和事件 一切的初衷都只为与 `WinForm` 靠近
# STNodeEditorPannel
![STNodeEditorPannel](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/stnodeeditorpannel.gif)
**注意:在目前的版本中(2.0) STNodeEditor仅仅提供了STNodeControl基类 并未提供任何一个可用控件 当然在附随的Demo工程中包含了部分示例演示如何自定义一个控件 由于这属于自定义控件的范畴 所以演示并未太多 若需了解关于自定义控件如何开发可参考作者:[自定义控件开发](http://st233.com/blog.php?group=1) 系列文章 当然在后续的版本中 作者将提供部分常用控件 虽说作者想把使用方式往WinForm上靠 单仅仅是把它当作WinForm使用并不是作者的初衷**
`STNodeEditorPannel``STNodeEditor` `STNodeTreeView` `STNodePropertyGrid`的一套组合控件
上面的演示仅仅是为了让大家感到亲切感 毕竟 `WinForm` 可能是大家熟悉的一个东西 但是如果仅仅是把它当作 `WinForm` 使用毫无意义 对于一个节点来说 最重要的属性当然是数据的输入和输出
可以通过拖动手柄控制布局
``` cs
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()中设置
}
}
```
## 关于作者
<img alt="MyNode.png" src="https://img-blog.csdnimg.cn/img_convert/a7bb83f3bc1c39143d71a42f26668e4f.png" width="208">
通过上面的案例你可以看到 `STNode` 有两个重要的属性 `InputOptions``OutputOptions` 其数据类型为 `STNodeOption``STNodeOption` 有两种连接模式 `single-connection``multi-connection`
* single-connection
* 单连接模式 在单连接模式下一个连接点同时 只能被一个 同数据类型点的连接
* multi-connection
* 多连接模式 在多连接模式下一个连接点同时 可以被多个 同数据类型点连接
``` cs
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);
}
}
```
<img alt="MyNode.png" src="https://img-blog.csdnimg.cn/img_convert/da719b3dc6c7d61423df83c5876917ae.png" width="208">
---
# 如何进行数据交互?
在上面的案例中仅仅是做了一个可以被连接的选项点 并不包含任何的功能
* STNodeOption可以通过绑定DataTransfer事件获取到传入该选项的所有数据
* STNodeOption可以通过TransferData(object obj)向该选项上所有连接的选项进行数据投递
下面通过一个案例进行演示 创建两个节点 一个节点用于每秒输出一次当前系统事件 另一个节点用于接收一个事件并显示
``` cs
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();
}
}
```
当然上面可以直线将时间显示出来 不过这里为了演示数据的传递 所以还需要一个接收节点
``` cs
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());
}
}
}
```
<img alt="TimeNode.gif" src="https://img-blog.csdnimg.cn/img_convert/91c426fda7d1e8dd7d85307168868116.gif" width="208">
可以看到当连接被建立时 `ShowTime` 节点每秒都在刷新 下面是一个更加复杂一点的案例 但是这里并没有给出代码请参考附随工程的 `Demo`
<img alt="ImageNode.png" src="https://img-blog.csdnimg.cn/img_convert/2ffaa58c22f91f1219143256dfa49799.png" width="418">
点击 `Open Image` 按钮可打开并显示一张图片在 `ImageShowNode` 中并将图片作为输出数据 `ImageChanel` 则负责接收一张图像并处理输出图像的RGB图像及原图 `ImageSize` 则负责接收并显示一张图像的尺寸信息
对于上面的节点在开发期间它们并不知道会被什么样的节点连接 也并不知道会被连接到什么节点上 开发者仅仅是完成了自己的功能处理接收到的数据并将结果打包给 `STNodeOption` 无需关系最终会被谁把结果拿走并处理 使得节点之间与节点之间的耦合关系大大降低 唯一将它们联系在一起的是一个 `Image` 数据类型 最终执行的逻辑交给用户自己拖拽节点组合他们自己想要的流程 使得功能的执行流程变得可视化 这也是作者的初衷
**关于更多的教程和文档请参考:[https://debugst.github.io/STNodeEditor/doc_cn.html](https://debugst.github.io/STNodeEditor/doc_cn.html) 在下载的调用库压缩包里面同样包含离线版文档**
---
# 关于下个版本
其实目前这个版本还有很多需要完善的代码 如上面提到的提供一些基础控件 而且目前提供的东西还很原始 一些应用场景目前需要开发者自己写代码完成
<img alt="First.png" src="https://img-blog.csdnimg.cn/img_convert/6785f2f9d7244bc6e6756c2f786776b4.png" width="1030">
上图为作者的最初构思以及第一个 `Demo` 演示版本 在上图中可以看到有 `启动` 按钮 某些应用场景下可能需要用户点击执行按钮以后才开始执行用户所部署的逻辑 而之前上面的案例数据交互都是更具用户的布线实时的 当然在目前的版本中想实现也是可以的 只是需要开发者自己写部分代码 由于这部分的代码作者暂时还没有构思好很多细节处理 所以还有下一个版本的话很多功能都将出现
上图的构想是 开发者无需关系架构执行逻辑什么的 而开发者只需要关系功能点本省只需要开发出包含 `STNode``DLL` 文件 而程序启动 `TreeView` 会自动加载目录下的 `DLL` 文件并装载 `STNode``TreeView` 中 然后让用户拖拽执行 对于上一段话中作者提到的需要通过 启动 按钮执行如何在当前版本的实现 作者这里给出一些思路
``` cs
//首先定义一个基类 包含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;
}
}
```
上面的代码演示了一个 `输入``输出` 类型的节点 至于其他需求自行举一反三 当用户点下 `启动` 按钮时候
``` cs
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 类型的节点被添加
``` cs
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);
}
}
```
当然 这个需求估计很少有吧
当然这里就并没有给出上述代码片段的执行效果了 因为上面仅仅是提供思路 让读者可以举一反三 而且上面的代码均没有任何的异常处理 要真正做好其实还有很多细节需要处理很多代码需要写 所以暂定目前版本不提供这样的功能
# 关于作者
* Github: [DebugST](https://github.com/DebugST/)
* Blog: [Crystal_lz](http://st233.com)
* Mail: (2212233137@qq.com)

View File

@ -974,7 +974,7 @@ namespace ST.Library.UI.NodeEditor
protected internal virtual void OnMouseWheel(MouseEventArgs e) {
Point pt = e.Location;
pt.Y -= this._TitleHeight;
if (m_ctrl_hover != null && m_ctrl_active.Enabled && m_ctrl_hover.Visable) {
if (m_ctrl_hover != null && m_ctrl_hover.Enabled && m_ctrl_hover.Visable) {
m_ctrl_hover.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_hover.Left, pt.Y - m_ctrl_hover.Top, e.Delta));
return;
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@ -10,8 +10,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ST.Library.UI</RootNamespace>
<AssemblyName>ST.Library.UI</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -22,6 +23,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -32,6 +34,7 @@
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\ST.Library.UI.XML</DocumentationFile>
<DebugSymbols>true</DebugSymbols>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

81
V3_CN.md Normal file
View File

@ -0,0 +1,81 @@
由于`3.0`版本可能部分代码会被重构,所以决定开一个新的项目。但是项目目前并未上传`GitHub`。将在完成时候上传。
虽然用户量不多,但是感谢那些一直在使用`STNodeEditor`的用户,在他们那里作者得到了一些反馈。将做如下调整:
|内容|状态|完成时间|备注|
|:---|:---|:---|:---|
|增加高DPI支持 |✅ |2022-09-12|创建了 `STGraphics`|
|添加json格式序列化文件 |✅ |2022-09-30|添加新项目 [STJson](https://github.com/DebugST/STJson)|
|添加`STNodeEditorCanvas` |☑️ |开始|-|
|添加缩略图 |☑️ |-|-|
|节点选项悬浮提示信息 |☑️ |-|-|
|修复已知bug |☑️ |-|-|
添加控件支持:
|内容|状态|完成时间|备注|
|:---|:---|:---|:---|
|Panel |☑️ |-|-|
|lable |☑️ |-|-|
|button |☑️ |-|-|
|textbox |☑️ |-|-|
|listview |☑️ |-|-|
|chekcbox |☑️ |-|-|
|radiobutton |☑️ |-|-|
|combobox |☑️ |-|-|
|groupbox |☑️ |-|-|
|picturebox |☑️ |-|-|
|progressbar |☑️ |-|-|
|trackbar |☑️ |-|-|
|NumericUpDown |☑️ |-|-|
控件将尽可能保持`WinForm`的使用习惯。
添加`STNodeEditorCanvas.cs`
替代原本的`STNodeEditor.cs`
```cs
var canvas = new STNodeEditorCanvas("layer_name");
canvas.Nodes.add(new STNode_1());
canvas.Nodes.add(new STNode_2());
STNodeEditor_1.Canvas = canvas;
// STNodeEditor.Layers.Add(canvas) 此方式先待定
/*
这样做的目的是想实现类似TabControl的效果用户可能有多个画布需要加载
所以他不得不添加多个STNodeEditor做切换。所以用STNodeEditorCanvas代替原本的STNodeEditor
而STNodeEditor仅仅作为一个画布容器
*/
```
添加`STNodeSpy.cs`
虽然`STNodeEditor`可以通过选项点颜色来区分数据类型,但是不排除会出现同一个颜色不同数据类型的情况,尤其是节点并非一个人开发的情况。
`STNodeSpy`作为一个内置节点提供就像`STNodeHub`一样,它的使用方式将会和`SPY++`类似。
添加`STNodeGroup.cs`
目前的`STNodeEditor`并没有分组的功能,作者准备尝试使用`Blender`一样的分组方式,毕竟作者确实很喜欢`Blender`。将分组作为一个节点使用。
```cs
var group = new STNodeGroup("group_name");
group.Nodes.Add(new STNode_1());
group.Nodes.Add(new STNode_2());
var layer = new STNodeEditorLayer("layer_name");
layder.Nodes.Add(group);
/*
group 具有可编辑属性它将以普通节点的形式存在于layer中。但是它可以被展开
当group被展开时它将变成一个独立的layer可以进行节点的添加和删除。
可以参考Blender的group可以理解为将整个画布的最开始的输入和最终的输出作为一个节点的输入和输出。
*/
```
以上仅供参考,最终效果请以实物为准。😏😏😏😏😏(毕竟作者是个死咸鱼,不排除摆烂、虚假宣传的可能)。
如上面的某一个功能已经完成则会在旁边标记一个时间 比如:
(2023-12-21)`增加高DPI支持`
如果你有什么想法可以联系咸鱼作者2212233137@qq.com

81
V3_EN.md Normal file
View File

@ -0,0 +1,81 @@
Since the `3.0` version may be partially refactored, it was decided to start a new project. But the project is not currently uploaded to `GitHub`. Will upload when finished.
Although there are not many users, thanks to those who have been using `STNodeEditor`, where the author got some feedback. The following adjustments will be made:
|Items|Status|Complete time|Note|
|:---|:---|:---|:---|
|Add high DPI support |✅ |2022-09-12|Create `STGraphics`|
|Add json format serialization file |✅ |2022-09-30|Add new project [STJson](https://github.com/DebugST/STJson)|
|Add `STNodeEditorCanvas` |☑️ |start|-|
|Add mini-map |☑️ |-|-|
|Node option hover hint text |☑️ |-|-|
|Add mini-map |☑️ |-|-|
Add Controls
|Items|Status|Complete time|Note|
|:---|:---|:---|:---|
|Panel |☑️ |-|-|
|lable |☑️ |-|-|
|button |☑️ |-|-|
|textbox |☑️ |-|-|
|listview |☑️ |-|-|
|chekcbox |☑️ |-|-|
|radiobutton |☑️ |-|-|
|combobox |☑️ |-|-|
|groupbox |☑️ |-|-|
|picturebox |☑️ |-|-|
|progressbar |☑️ |-|-|
|trackbar |☑️ |-|-|
|NumericUpDown |☑️ |-|-|
Controls will maintain the `WinForm` usage habits as much as possible.
Add `STNodeEditorCanvas.cs`
To replace `STNodeEditor.cs`
```cs
var canvas = new STNodeEditorCanvas("layer_name");
canvas.Nodes.add(new STNode_1());
canvas.Nodes.add(new STNode_2());
STNodeEditor_1.Canvas = canvas;
// STNodeEditor.Layers.Add(canvas) This method is pending.
/*
The purpose of this is to achieve a TabControl-like effect, where the user may have multiple canvases to load.
So he had to add multiple STNodeEditors for switching. So use STNodeEditorCanvas to replace the original STNodeEditor.
And STNodeEditor only acts as a canvas container.
*/
```
Add `STNodeSpy.cs`
Although `STNodeEditor` can distinguish data types by option point color, it does not rule out that there will be different data types with the same color, especially if the node is not developed by one person.
`STNodeSpy` is provided as a built-in node just like `STNodeHub`, it will be used in a similar way to `SPY++`.
Add `STNodeGroup.cs`
The current `STNodeEditor` does not have the function of grouping. The author is going to try to use the same grouping method as `Blender`. After all, the author really likes `Blender`. Use the group as a node.
```cs
var group = new STNodeGroup("group_name");
group.Nodes.Add(new STNode_1());
group.Nodes.Add(new STNode_2());
var layer = new STNodeEditorLayer("layer_name");
layder.Nodes.Add(group);
/*
The group has editable properties and it will exist in the layer as a normal node. But it can be expanded.
When the group is expanded, it will become an independent layer, and nodes can be added and removed.
You can refer to Blender's group, which can be understood as taking the initial input and final output of the entire canvas as the input and output of a node.
*/
```
The above is for reference only, please refer to the actual product for the final effect. 😏😏😏😏😏 (after all, the author is a dead salted fish, and the possibility of strikes and false propaganda cannot be ruled out).
If one of the above functions has been completed, a time will be marked next to it. For example:
(2023-12-21)`Add high DPI support`
If you have any ideas you can contact the author: 2212233137@qq.com

View File

@ -1,55 +1,54 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace WinNodeEditorDemo.Properties
{
namespace WinNodeEditorDemo.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// 一个强类型的资源类,用于查找本地化的字符串等。
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
// 此类是由 StronglyTypedResourceBuilder
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// 返回此类使用的缓存的 ResourceManager 实例。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if ((resourceMan == null)) {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WinNodeEditorDemo.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// 重写当前线程的 CurrentUICulture 属性,对
/// 使用此强类型资源类的所有资源查找执行重写。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {

View File

@ -1,24 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace WinNodeEditorDemo.Properties
{
namespace WinNodeEditorDemo.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
@ -10,8 +10,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>WinNodeEditorDemo</RootNamespace>
<AssemblyName>WinNodeEditorDemo</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -22,6 +23,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
@ -31,6 +33,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
@ -82,7 +85,9 @@
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="app.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/></startup></configuration>

View File

@ -1,10 +1,14 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.35906.104
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ST.Library.UI", "ST.Library.UI\ST.Library.UI.csproj", "{EFFCC270-4999-4077-A543-56CCCCE92147}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinNodeEditorDemo", "WinNodeEditorDemo\WinNodeEditorDemo.csproj", "{4E1829B5-2160-48F5-ABD6-11914A6A25DD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfNodeEdittorDemo", "WpfNodeEdittorDemo\WpfNodeEdittorDemo.csproj", "{B355CF99-7494-472A-8E0E-1543236FFC88}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -35,8 +39,23 @@ Global
{4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Release|Mixed Platforms.Build.0 = Release|x86
{4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Release|x86.ActiveCfg = Release|x86
{4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Release|x86.Build.0 = Release|x86
{B355CF99-7494-472A-8E0E-1543236FFC88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B355CF99-7494-472A-8E0E-1543236FFC88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B355CF99-7494-472A-8E0E-1543236FFC88}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{B355CF99-7494-472A-8E0E-1543236FFC88}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{B355CF99-7494-472A-8E0E-1543236FFC88}.Debug|x86.ActiveCfg = Debug|Any CPU
{B355CF99-7494-472A-8E0E-1543236FFC88}.Debug|x86.Build.0 = Debug|Any CPU
{B355CF99-7494-472A-8E0E-1543236FFC88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B355CF99-7494-472A-8E0E-1543236FFC88}.Release|Any CPU.Build.0 = Release|Any CPU
{B355CF99-7494-472A-8E0E-1543236FFC88}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{B355CF99-7494-472A-8E0E-1543236FFC88}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{B355CF99-7494-472A-8E0E-1543236FFC88}.Release|x86.ActiveCfg = Release|Any CPU
{B355CF99-7494-472A-8E0E-1543236FFC88}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CE29E4E7-E2FB-4387-9743-04332BC01645}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,9 @@
<Application x:Class="WpfNodeEdittorDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfNodeEdittorDemo"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@ -0,0 +1,14 @@
using System.Configuration;
using System.Data;
using System.Windows;
namespace WpfNodeEdittorDemo
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI.NodeEditor;
using System.Drawing;
using System.Windows.Forms;
namespace WinNodeEditorDemo
{
[STNode("/", "Crystal_lz", "2212233137@qq.com", "www.st233.com", "关于此节点的描述信息\r\n此类为\r\nSTNodeAttribute\r\nSTNodePropertyAttribute\r\n效果演示类")]
public class AttrTestNode : STNode
{
//因为属性编辑器默认并不支持Color类型数据 所以这里重写一个描述器并指定
[STNodeProperty("颜色", "颜色信息", DescriptorType = typeof(DescriptorForColor))]
public Color Color { get; set; }
[STNodeProperty("整型数组", "整型数组测试")]
public int[] IntArr { get; set; }
[STNodeProperty("布尔", "布尔类型测试")]
public bool Bool { get; set; }
[STNodeProperty("字符串", "字符串类型测试")]
public string String { get; set; }
[STNodeProperty("整型", "整型测试")]
public int Int { get; set; }
[STNodeProperty("浮点数", "浮点数类型测试")]
public float Float { get; set; }
[STNodeProperty("枚举值", "枚举类型测试 -> FormBorderStyle")]
public FormBorderStyle STYLE { get; set; }
public AttrTestNode() {
this.String = "string";
IntArr = new int[] { 10, 20 };
base.InputOptions.Add("string", typeof(string), false);
base.OutputOptions.Add("string", typeof(string), false);
this.Title = "AttrTestNode";
this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
}
/// <summary>
/// 此方法为魔术方法(Magic function)
/// 若存在 static void ShowHelpInfo(string) 且此类被STNodeAttribute标记
/// 则此方法将作为属性编辑器上 查看帮助 功能
/// </summary>
/// <param name="strFileName">此类所在的模块所在的文件路径</param>
public static void ShowHelpInfo(string strFileName) {
MessageBox.Show("this is -> ShowHelpInfo(string);\r\n" + strFileName);
}
protected override void OnOwnerChanged() {
base.OnOwnerChanged();
if (this.Owner == null) return;
this.Owner.SetTypeColor(typeof(string), Color.Goldenrod);
}
}
/// <summary>
/// 因为属性编辑器默认并不支持Color类型数据 所以这里重写一个描述器
/// </summary>
public class DescriptorForColor : STNodePropertyDescriptor
{
private Rectangle m_rect;//此区域用作 属性窗口上绘制颜色预览
//当此属性在属性窗口中被确定位置时候发生
protected override void OnSetItemLocation() {
base.OnSetItemLocation();
Rectangle rect = base.RectangleR;
m_rect = new Rectangle(rect.Right - 25, rect.Top + 5, 19, 12);
}
//将属性值转换为字符串 属性窗口值绘制时将采用此字符串
protected override string GetStringFromValue() {
Color clr = (Color)this.GetValue(null);
return clr.A + "," + clr.R + "," + clr.G + "," + clr.B;
}
//将属性窗口中输入的字符串转化为Color属性 当属性窗口中用户确认输入时调用
protected override object GetValueFromString(string strText) {
string[] strClr = strText.Split(',');
return Color.FromArgb(
int.Parse(strClr[0]), //A
int.Parse(strClr[1]), //R
int.Parse(strClr[2]), //G
int.Parse(strClr[3])); //B
}
//绘制属性窗口值区域时候调用
protected override void OnDrawValueRectangle(DrawingTools dt) {
base.OnDrawValueRectangle(dt);//先采用默认的绘制 并再绘制颜色预览
dt.SolidBrush.Color = (Color)this.GetValue(null);
dt.Graphics.FillRectangle(dt.SolidBrush, m_rect);//填充颜色
dt.Graphics.DrawRectangle(Pens.Black, m_rect); //绘制边框
}
protected override void OnMouseClick(MouseEventArgs e) {
//如果用户点击在 颜色预览区域 则弹出系统颜色对话框
if (m_rect.Contains(e.Location)) {
ColorDialog cd = new ColorDialog();
if (cd.ShowDialog() != DialogResult.OK) return;
this.SetValue(cd.Color, null);
this.Invalidate();
return;
}
//否则其他区域将采用默认处理方式 弹出字符串输入框
base.OnMouseClick(e);
}
}
}

View File

@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI.NodeEditor;
using System.Drawing;
namespace WinNodeEditorDemo.Blender
{
/// <summary>
/// 此类仅仅是演示 并不包含颜色混合功能
/// </summary>
[STNode("/Blender/", "Crystal_lz", "2212233137@qq.com", "st233.com", "this is blender mixrgb node")]
public class BlenderMixColorNode : STNode
{
private ColorMixType _MixType;
[STNodeProperty("MixType","This is MixType")]
public ColorMixType MixType {
get { return _MixType; }
set {
_MixType = value;
m_ctrl_select.Enum = value; //当属性被赋值后 对应控件状态值也应当被修改
}
}
private bool _Clamp;
[STNodeProperty("Clamp","This is Clamp")]
public bool Clamp {
get { return _Clamp; }
set { _Clamp = value; m_ctrl_checkbox.Checked = value; }
}
private int _Fac = 50;
[STNodeProperty("Fac", "This is Fac")]
public int Fac {
get { return _Fac; }
set {
if (value < 0) value = 0;
if (value > 100) value = 100;
_Fac = value; m_ctrl_progess.Value = value;
}
}
private Color _Color1 = Color.LightGray;//默认的DescriptorType不支持颜色的显示 需要扩展
[STNodeProperty("Color1", "This is color1", DescriptorType = typeof(WinNodeEditorDemo.DescriptorForColor))]
public Color Color1 {
get { return _Color1; }
set { _Color1 = value; m_ctrl_btn_1.BackColor = value; }
}
private Color _Color2 = Color.LightGray;
[STNodeProperty("Color2", "This is color2", DescriptorType = typeof(WinNodeEditorDemo.DescriptorForColor))]
public Color Color2 {
get { return _Color2; }
set { _Color2 = value; m_ctrl_btn_2.BackColor = value; }
}
public enum ColorMixType {
Mix,
Value,
Color,
Hue,
Add,
Subtract
}
private STNodeSelectEnumBox m_ctrl_select; //自定义控件
private STNodeProgress m_ctrl_progess;
private STNodeCheckBox m_ctrl_checkbox;
private STNodeColorButton m_ctrl_btn_1;
private STNodeColorButton m_ctrl_btn_2;
protected override void OnCreate() {
base.OnCreate();
this.TitleColor = Color.FromArgb(200, Color.DarkKhaki);
this.Title = "MixRGB";
this.AutoSize = false;
this.Size = new Size(140, 142);
this.OutputOptions.Add("Color", typeof(Color), true);
this.InputOptions.Add(STNodeOption.Empty); //空白节点 仅站位 不参与绘制与事件触发
this.InputOptions.Add(STNodeOption.Empty);
this.InputOptions.Add(STNodeOption.Empty);
this.InputOptions.Add("", typeof(float), true);
this.InputOptions.Add("Color1", typeof(Color), true);
this.InputOptions.Add("Color2", typeof(Color), true);
m_ctrl_progess = new STNodeProgress(); //创建控件并添加到节点中
m_ctrl_progess.Text = "Fac";
m_ctrl_progess.DisplayRectangle = new Rectangle(10, 61, 120, 18);
m_ctrl_progess.ValueChanged += (s, e) => this._Fac = m_ctrl_progess.Value;
this.Controls.Add(m_ctrl_progess);
m_ctrl_checkbox = new STNodeCheckBox();
m_ctrl_checkbox.Text = "Clamp";
m_ctrl_checkbox.DisplayRectangle = new Rectangle(10, 40, 120, 20);
m_ctrl_checkbox.ValueChanged += (s, e) => this._Clamp = m_ctrl_checkbox.Checked;
this.Controls.Add(m_ctrl_checkbox);
m_ctrl_btn_1 = new STNodeColorButton();
m_ctrl_btn_1.Text = "";
m_ctrl_btn_1.BackColor = this._Color1;
m_ctrl_btn_1.DisplayRectangle = new Rectangle(80, 82, 50, 16);
m_ctrl_btn_1.ValueChanged += (s, e) => this._Color1 = m_ctrl_btn_1.BackColor;
this.Controls.Add(m_ctrl_btn_1);
m_ctrl_btn_2 = new STNodeColorButton();
m_ctrl_btn_2.Text = "";
m_ctrl_btn_2.BackColor = this._Color2;
m_ctrl_btn_2.DisplayRectangle = new Rectangle(80, 102, 50, 16);
m_ctrl_btn_2.ValueChanged += (s, e) => this._Color2 = m_ctrl_btn_2.BackColor;
this.Controls.Add(m_ctrl_btn_2);
m_ctrl_select = new STNodeSelectEnumBox();
m_ctrl_select.DisplayRectangle = new Rectangle(10, 21, 120, 18);
m_ctrl_select.Enum = this._MixType;
m_ctrl_select.ValueChanged += (s, e) => this._MixType = (ColorMixType)m_ctrl_select.Enum;
this.Controls.Add(m_ctrl_select);
}
protected override void OnOwnerChanged() { //当控件被添加时候 向编辑器提交自己的数据类型希望显示的颜色
base.OnOwnerChanged();
if (this.Owner == null) return;
this.Owner.SetTypeColor(typeof(float), Color.Gray);
this.Owner.SetTypeColor(typeof(Color), Color.Yellow);
}
}
}

View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
namespace WinNodeEditorDemo.Blender
{
/// <summary>
/// 此类仅演示 作为MixRGB节点的下拉选择框弹出菜单
/// </summary>
public class FrmEnumSelect : Form
{
private Point m_pt;
private int m_nWidth;
private float m_scale;
private List<object> m_lst = new List<object>();
private StringFormat m_sf;
public Enum Enum { get; set; }
private bool m_bClosed;
public FrmEnumSelect(Enum e, Point pt, int nWidth, float scale) {
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
foreach (var v in Enum.GetValues(e.GetType())) m_lst.Add(v);
this.Enum = e;
m_pt = pt;
m_scale = scale;
m_nWidth = nWidth;
m_sf = new StringFormat();
m_sf.LineAlignment = StringAlignment.Center;
this.ShowInTaskbar = false;
this.BackColor = Color.FromArgb(255, 34, 34, 34);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
}
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
this.Location = m_pt;
this.Width = (int)(m_nWidth * m_scale);
this.Height = (int)(m_lst.Count * 20 * m_scale);
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
g.ScaleTransform(m_scale, m_scale);
Rectangle rect = new Rectangle(0, 0, this.Width, 20);
foreach (var v in m_lst) {
g.DrawString(v.ToString(), this.Font, Brushes.White, rect, m_sf);
rect.Y += rect.Height;
}
}
protected override void OnMouseClick(MouseEventArgs e) {
base.OnMouseClick(e);
int nIndex = e.Y / (int)(20 * m_scale);
if (nIndex >= 0 && nIndex < m_lst.Count) this.Enum = (Enum)m_lst[nIndex];
this.DialogResult = System.Windows.Forms.DialogResult.OK;
m_bClosed = true;
}
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave(e);
if (m_bClosed) return;
//this.DialogResult = System.Windows.Forms.DialogResult.None;
this.Close();
}
}
}

View File

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using ST.Library.UI.NodeEditor;
namespace WinNodeEditorDemo.Blender
{
/// <summary>
/// 此类仅演示 作为MixRGB节点的复选框控件
/// </summary>
public class STNodeCheckBox : STNodeControl
{
private bool _Checked;
public bool Checked {
get { return _Checked; }
set {
_Checked = value;
this.Invalidate();
}
}
public event EventHandler ValueChanged;
protected virtual void OnValueChanged(EventArgs e) {
if (this.ValueChanged != null) this.ValueChanged(this, e);
}
protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) {
base.OnMouseClick(e);
this.Checked = !this.Checked;
this.OnValueChanged(new EventArgs());
}
protected override void OnPaint(DrawingTools dt) {
//base.OnPaint(dt);
Graphics g = dt.Graphics;
g.FillRectangle(Brushes.Gray, 0, 5, 10, 10);
m_sf.Alignment = StringAlignment.Near;
g.DrawString(this.Text, this.Font, Brushes.LightGray, new Rectangle(15, 0, this.Width - 20, 20), m_sf);
if (this.Checked) {
g.FillRectangle(Brushes.Black, 2, 7, 6, 6);
}
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using ST.Library.UI.NodeEditor;
namespace WinNodeEditorDemo.Blender
{
/// <summary>
/// 此类仅演示 作为MixRGB节点的颜色选择按钮
/// </summary>
public class STNodeColorButton : STNodeControl
{
public event EventHandler ValueChanged;
protected virtual void OnValueChanged(EventArgs e) {
if (this.ValueChanged != null) this.ValueChanged(this, e);
}
protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) {
base.OnMouseClick(e);
ColorDialog cd = new ColorDialog();
if (cd.ShowDialog() != DialogResult.OK) return;
//this._Color = cd.Color;
this.BackColor = cd.Color;
this.OnValueChanged(new EventArgs());
}
}
}

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI.NodeEditor;
using System.Drawing;
namespace WinNodeEditorDemo.Blender
{
/// <summary>
/// 此类仅演示 作为MixRGB节点的进度条控件
/// </summary>
public class STNodeProgress : STNodeControl
{
private int _Value = 50;
public int Value {
get { return _Value; }
set {
_Value = value;
this.Invalidate();
}
}
private bool m_bMouseDown;
public event EventHandler ValueChanged;
protected virtual void OnValueChanged(EventArgs e) {
if (this.ValueChanged != null) this.ValueChanged(this, e);
}
protected override void OnPaint(DrawingTools dt) {
base.OnPaint(dt);
Graphics g = dt.Graphics;
g.FillRectangle(Brushes.Gray, this.ClientRectangle);
g.FillRectangle(Brushes.CornflowerBlue, 0, 0, (int)((float)this._Value / 100 * this.Width), this.Height);
m_sf.Alignment = StringAlignment.Near;
g.DrawString(this.Text, this.Font, Brushes.White, this.ClientRectangle, m_sf);
m_sf.Alignment = StringAlignment.Far;
g.DrawString(((float)this._Value / 100).ToString("F2"), this.Font, Brushes.White, this.ClientRectangle, m_sf);
}
protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) {
base.OnMouseDown(e);
m_bMouseDown = true;
}
protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e) {
base.OnMouseUp(e);
m_bMouseDown = false;
}
protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e) {
base.OnMouseMove(e);
if (!m_bMouseDown) return;
int v = (int)((float)e.X / this.Width * 100);
if (v < 0) v = 0;
if (v > 100) v = 100;
this._Value = v;
this.OnValueChanged(new EventArgs());
this.Invalidate();
}
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using ST.Library.UI.NodeEditor;
namespace WinNodeEditorDemo.Blender
{
/// <summary>
/// 此类仅演示 作为MixRGB节点的下拉框控件
/// </summary>
public class STNodeSelectEnumBox : STNodeControl
{
private Enum _Enum;
public Enum Enum {
get { return _Enum; }
set {
_Enum = value;
this.Invalidate();
}
}
public event EventHandler ValueChanged;
protected virtual void OnValueChanged(EventArgs e) {
if (this.ValueChanged != null) this.ValueChanged(this, e);
}
protected override void OnPaint(DrawingTools dt) {
Graphics g = dt.Graphics;
dt.SolidBrush.Color = Color.FromArgb(80, 0, 0, 0);
g.FillRectangle(dt.SolidBrush, this.ClientRectangle);
m_sf.Alignment = StringAlignment.Near;
g.DrawString(this.Enum.ToString(), this.Font, Brushes.White, this.ClientRectangle, m_sf);
g.FillPolygon(Brushes.Gray, new Point[]{
new Point(this.Right - 25, 7),
new Point(this.Right - 15, 7),
new Point(this.Right - 20, 12)
});
}
protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) {
base.OnMouseClick(e);
Point pt = new Point(this.Left + this.Owner.Left, this.Top + this.Owner.Top + this.Owner.TitleHeight);
pt = this.Owner.Owner.CanvasToControl(pt);
pt = this.Owner.Owner.PointToScreen(pt);
FrmEnumSelect frm = new FrmEnumSelect(this.Enum, pt, this.Width, this.Owner.Owner.CanvasScale);
var v = frm.ShowDialog();
if (v != System.Windows.Forms.DialogResult.OK) return;
this.Enum = frm.Enum;
this.OnValueChanged(new EventArgs());
}
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI.NodeEditor;
using System.Drawing;
namespace WinNodeEditorDemo
{
/// <summary>
/// 此节点仅演示UI自定义以及控件 并不包含功能
/// </summary>
[STNode("/", "DebugST", "2212233137@qq.com", "st233.com", "此节点仅演示UI自定义以及控件,并不包含功能.")]
public class CalcNode : STNode
{
private StringFormat m_f;
protected override void OnCreate() {
base.OnCreate();
m_sf = new StringFormat();
m_sf.LineAlignment = StringAlignment.Center;
this.Title = "Calculator";
this.AutoSize = false; //注意需要先设置AutoSize=false 才能够进行大小设置
this.Size = new Size(218, 308);
var ctrl = new STNodeControl();
ctrl.Text = ""; //此控件为显示屏幕
ctrl.Location = new Point(13, 31);
ctrl.Size = new Size(190, 50);
this.Controls.Add(ctrl);
ctrl.Paint += (s, e) => {
m_sf.Alignment = StringAlignment.Far;
STNodeControl c = s as STNodeControl;
Graphics g = e.DrawingTools.Graphics;
g.DrawString("0", ctrl.Font, Brushes.White, c.ClientRectangle, m_sf);
};
string[] strs = { //按钮文本
"MC", "MR", "MS", "M+",
"M-", "←", "CE", "C", "+", "√",
"7", "8", "9", "/", "%",
"4", "5", "6", "*", "1/x",
"1", "2", "3", "-", "=",
"0", " ", ".", "+" };
Point p = new Point(13, 86);
for (int i = 0; i < strs.Length; i++) {
if (strs[i] == " ") continue;
ctrl = new STNodeControl();
ctrl.Text = strs[i];
ctrl.Size = new Size(34, 27);
ctrl.Left = 13 + (i % 5) * 39;
ctrl.Top = 86 + (i / 5) * 32;
if (ctrl.Text == "=") ctrl.Height = 59;
if (ctrl.Text == "0") ctrl.Width = 73;
this.Controls.Add(ctrl);
if (i == 8) ctrl.Paint += (s, e) => {
m_sf.Alignment = StringAlignment.Center;
STNodeControl c = s as STNodeControl;
Graphics g = e.DrawingTools.Graphics;
g.DrawString("_", ctrl.Font, Brushes.White, c.ClientRectangle, m_sf);
};
ctrl.MouseClick += (s, e) => System.Windows.Forms.MessageBox.Show(((STNodeControl)s).Text);
}
this.OutputOptions.Add("Result", typeof(int), false);
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI.NodeEditor;
namespace WinNodeEditorDemo
{
[STNode("/")]
public class EmptyOptionTestNode : STNode
{
protected override void OnCreate() {
base.OnCreate();
this.Title = "EmptyTest";
this.InputOptions.Add(STNodeOption.Empty);
this.InputOptions.Add("string", typeof(string), false);
}
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI.NodeEditor;
using System.Drawing;
namespace WinNodeEditorDemo.ImageNode
{
/// <summary>
/// 图片节点基类 用于确定节点风格 标题颜色 以及 数据类型颜色
/// </summary>
public abstract class ImageBaseNode : STNode
{
/// <summary>
/// 需要作为显示绘制的图片
/// </summary>
protected Image m_img_draw;
/// <summary>
/// 输出节点
/// </summary>
protected STNodeOption m_op_img_out;
protected override void OnCreate() {
base.OnCreate();
m_op_img_out = this.OutputOptions.Add("", typeof(Image), false);
this.AutoSize = false; //此节点需要定制UI 所以无需AutoSize
//this.Size = new Size(320,240);
this.Width = 160; //手动设置节点大小
this.Height = 120;
this.TitleColor = Color.FromArgb(200, Color.DarkCyan);
}
protected override void OnOwnerChanged() { //向编辑器提交数据类型颜色
base.OnOwnerChanged();
if (this.Owner == null) return;
this.Owner.SetTypeColor(typeof(Image), Color.DarkCyan);
}
}
}

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI.NodeEditor;
using System.Drawing;
using System.Drawing.Imaging;
namespace WinNodeEditorDemo.ImageNode
{
[STNode("/Image")]
public class ImageChannelNode : ImageBaseNode
{
private STNodeOption m_op_img_in; //输入的节点
private STNodeOption m_op_img_r; //R图 输出节点
private STNodeOption m_op_img_g; //G图 输出节点
private STNodeOption m_op_img_b; //B图 输出节点
protected override void OnCreate() {
base.OnCreate();
this.Title = "ImageChannel";
m_op_img_in = this.InputOptions.Add("", typeof(Image), true);
m_op_img_r = this.OutputOptions.Add("R", typeof(Image), false);
m_op_img_g = this.OutputOptions.Add("G", typeof(Image), false);
m_op_img_b = this.OutputOptions.Add("B", typeof(Image), false);
//当输入节点有数据输入时候
m_op_img_in.DataTransfer += new STNodeOptionEventHandler(m_op_img_in_DataTransfer);
}
void m_op_img_in_DataTransfer(object sender, STNodeOptionEventArgs e) {
//如果当前不是连接状态 或者 接受到的数据为空
if (e.Status != ConnectionStatus.Connected || e.TargetOption.Data == null) {
m_op_img_out.TransferData(null); //向所有输出节点输出空数据
m_op_img_r.TransferData(null);
m_op_img_g.TransferData(null);
m_op_img_b.TransferData(null);
m_img_draw = null; //需要绘制显示的图片置为空
} else {
Bitmap bmp = (Bitmap)e.TargetOption.Data; //否则计算图片的RGB图像
Bitmap bmp_r = new Bitmap(bmp.Width, bmp.Height);
Bitmap bmp_g = new Bitmap(bmp.Width, bmp.Height);
Bitmap bmp_b = new Bitmap(bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData bmpData_r = bmp_r.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
BitmapData bmpData_g = bmp_g.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
BitmapData bmpData_b = bmp_b.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
byte[] byColor = new byte[bmpData.Height * bmpData.Stride];
byte[] byColor_r = new byte[byColor.Length];
byte[] byColor_g = new byte[byColor.Length];
byte[] byColor_b = new byte[byColor.Length];
System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, byColor, 0, byColor.Length);
for (int y = 0; y < bmpData.Height; y++) {
int ny = y * bmpData.Stride;
for (int x = 0; x < bmpData.Width; x++) {
int nx = x << 2;
byColor_b[ny + nx] = byColor[ny + nx];
byColor_g[ny + nx + 1] = byColor[ny + nx + 1];
byColor_r[ny + nx + 2] = byColor[ny + nx + 2];
byColor_r[ny + nx + 3] = byColor_g[ny + nx + 3] = byColor_b[ny + nx + 3] = byColor[ny + nx + 3];
}
}
bmp.UnlockBits(bmpData);
System.Runtime.InteropServices.Marshal.Copy(byColor_r, 0, bmpData_r.Scan0, byColor_r.Length);
System.Runtime.InteropServices.Marshal.Copy(byColor_g, 0, bmpData_g.Scan0, byColor_g.Length);
System.Runtime.InteropServices.Marshal.Copy(byColor_b, 0, bmpData_b.Scan0, byColor_b.Length);
bmp_r.UnlockBits(bmpData_r);
bmp_g.UnlockBits(bmpData_g);
bmp_b.UnlockBits(bmpData_b);
m_op_img_out.TransferData(bmp); //out选项 输出原图
m_op_img_r.TransferData(bmp_r); //R选项输出R图
m_op_img_g.TransferData(bmp_g);
m_op_img_b.TransferData(bmp_b);
m_img_draw = bmp; //需要绘制显示的图片
}
}
protected override void OnDrawBody(DrawingTools dt) {
base.OnDrawBody(dt);
Graphics g = dt.Graphics;
Rectangle rect = new Rectangle(this.Left + 10, this.Top + 30, 120, 80);
g.FillRectangle(Brushes.Gray, rect);
if (m_img_draw != null) g.DrawImage(m_img_draw, rect);
}
}
}

View File

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI.NodeEditor;
using System.Drawing;
using System.Windows.Forms;
using System.Reflection;
namespace WinNodeEditorDemo.ImageNode
{
[STNode("Image", "Crystal_lz", "2212233137@qq.com", "st233.com", "Image Node")]
public class ImageInputNode : ImageBaseNode
{
private string _FileName;//默认的DescriptorType不支持文件路径的选择 所以需要扩展
[STNodeProperty("InputImage", "Click to select a image", DescriptorType = typeof(OpenFileDescriptor))]
public string FileName {
get { return _FileName; }
set {
Image img = null; //当文件名被设置时 加载图片并 向输出节点输出
if (!string.IsNullOrEmpty(value)) {
img = Image.FromFile(value);
}
if (m_img_draw != null) m_img_draw.Dispose();
m_img_draw = img;
_FileName = value;
m_op_img_out.TransferData(m_img_draw, true);
this.Invalidate();
}
}
protected override void OnCreate() {
base.OnCreate();
this.Title = "ImageInput";
}
protected override void OnDrawBody(DrawingTools dt) {
base.OnDrawBody(dt);
Graphics g = dt.Graphics;
Rectangle rect = new Rectangle(this.Left + 10, this.Top + 30, 140, 80);
g.FillRectangle(Brushes.Gray, rect);
if (m_img_draw != null) g.DrawImage(m_img_draw, rect);
}
}
/// <summary>
/// 对默认Descriptor进行扩展 使得支持文件路径选择
/// </summary>
public class OpenFileDescriptor : STNodePropertyDescriptor
{
private Rectangle m_rect_open; //需要绘制"打开"按钮的区域
private StringFormat m_sf;
public OpenFileDescriptor() {
m_sf = new StringFormat();
m_sf.Alignment = StringAlignment.Center;
m_sf.LineAlignment = StringAlignment.Center;
}
protected override void OnSetItemLocation() { //当在STNodePropertyGrid上确定此属性需要显示的区域时候
base.OnSetItemLocation(); //计算出"打开"按钮需要绘制的区域
m_rect_open = new Rectangle(
this.RectangleR.Right - 20,
this.RectangleR.Top,
20,
this.RectangleR.Height);
}
protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) {
if (m_rect_open.Contains(e.Location)) { //点击在"打开"区域 则弹出文件选择框
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "*.jpg|*.jpg|*.png|*.png";
if (ofd.ShowDialog() != DialogResult.OK) return;
this.SetValue(ofd.FileName);
} else base.OnMouseClick(e); //否则默认处理方式 弹出文本输入框
}
protected override void OnDrawValueRectangle(DrawingTools dt) {
base.OnDrawValueRectangle(dt); //在STNodePropertyGrid绘制此属性区域时候将"打开"按钮绘制上去
dt.Graphics.FillRectangle(Brushes.Gray, m_rect_open);
dt.Graphics.DrawString("+", this.Control.Font, Brushes.White, m_rect_open, m_sf);
}
}
}

View File

@ -0,0 +1,68 @@
<Window x:Class="WpfNodeEdittorDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfNodeEdittorDemo"
xmlns:st ="clr-namespace:ST.Library.UI.NodeEditor;assembly=ST.Library.UI"
mc:Ignorable="d"
PreviewKeyDown="UserControl_PreviewKeyDown"
Title="MainWindow" Height="720" Width="1280" Initialized="Window_Initialized">
<Grid x:Name="Grid1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Margin="5" Grid.Row="0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<Button x:Name="ButtonOpen" Content="加载" Click="Button_Click_Open" Width="70" />
<Button x:Name="ButtonNew" Content="打开" Click="Button_Click_New" Width="70" />
<Button x:Name="ButtonClear" Content="清除" Click="Button_Click_Clear" Width="70"/>
<Button x:Name="ButtonSave" Content="保存" Click="Button_Click_Save" Width="70" Margin="0,0,10,0"/>
<Button Content="自动对齐" Click="AutoAlignment_Click" Width="70"/>
</StackPanel>
<Grid Grid.Column="2">
<Slider x:Name="ZoomSlider" Width="200" Value="{Binding STNodeEditorHelper.CanvasScale}" Maximum="3" Minimum="0.45" HorizontalAlignment="Right"/>
</Grid>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="300"/>
</Grid.ColumnDefinitions>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="300"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
</Grid>
<WindowsFormsHost Grid.Column="0" >
<st:STNodeEditor x:Name="STNodeEditorMain" MouseDown="STNodeEditorMain_MouseDown" MouseUp="STNodeEditorMain_MouseUp" MouseMove="STNodeEditorMain_MouseMove" MouseWheel="STNodeEditorMain_MouseWheel"/>
</WindowsFormsHost>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="250"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<WindowsFormsHost Grid.Row="0">
<st:STNodeTreeView x:Name="STNodeTreeView1" />
</WindowsFormsHost>
</ScrollViewer>
<WindowsFormsHost Grid.Row="2">
<st:STNodePropertyGrid x:Name="STNodePropertyGrid1" />
</WindowsFormsHost>
</Grid>
</Grid>
</Grid>
</Window>

View File

@ -0,0 +1,262 @@

using ColorVision.Engine.Templates.Flow;
using ST.Library.UI.NodeEditor;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Windows;
using System.Windows.Input;
namespace WpfNodeEdittorDemo
{
public class ActionCommand
{
public string Header { get; set; }
public Action UndoAction { get; set; }
public Action RedoAction { get; set; }
public ActionCommand(Action undoAction, Action redoAction)
{
UndoAction = undoAction;
RedoAction = redoAction;
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Undo, (s, e) => Undo(), (s, e) => { e.CanExecute = UndoStack.Count > 0; }));
this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Redo, (s, e) => Redo(), (s, e) => { e.CanExecute = RedoStack.Count > 0; }));
}
#region ActionCommand
public ObservableCollection<ActionCommand> UndoStack { get; set; } = new ObservableCollection<ActionCommand>();
public ObservableCollection<ActionCommand> RedoStack { get; set; } = new ObservableCollection<ActionCommand>();
public void ClearActionCommand()
{
UndoStack.Clear();
RedoStack.Clear();
}
public void AddActionCommand(ActionCommand actionCommand)
{
UndoStack.Add(actionCommand);
RedoStack.Clear();
}
public void Undo()
{
if (UndoStack.Count > 0)
{
var undoAction = UndoStack[^1]; // Access the last element
UndoStack.RemoveAt(UndoStack.Count - 1); // Remove the last element
undoAction.UndoAction();
RedoStack.Add(undoAction);
}
}
public void Redo()
{
if (RedoStack.Count > 0)
{
var redoAction = RedoStack[^1]; // Access the last element
RedoStack.RemoveAt(RedoStack.Count - 1); // Remove the last element
redoAction.RedoAction();
UndoStack.Add(redoAction);
}
}
#endregion
private void Window_Initialized(object sender, EventArgs e)
{
STNodePropertyGrid1.Text = "Node_Property";
STNodeTreeView1.LoadAssembly( System.Windows.Forms.Application.ExecutablePath.Replace("exe","dll"));
STNodeEditorMain.LoadAssembly(System.Windows.Forms.Application.ExecutablePath.Replace("exe", "dll"));
STNodeEditorHelper STNodeEditorHelper = new STNodeEditorHelper(this, STNodeEditorMain, STNodeTreeView1, STNodePropertyGrid1);
}
private void UserControl_PreviewKeyDown(object sender, KeyEventArgs e)
{
}
private void Button_Click_Open(object sender, RoutedEventArgs e)
{
System.Windows.Forms.OpenFileDialog ofd = new();
ofd.Filter = "*.stn|*.stn";
ofd.RestoreDirectory = true;
if (ofd.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
ButtonSave.Visibility = Visibility.Visible;
OpenFlow(ofd.FileName);
}
string FileFlow;
public void OpenFlow(string flowName)
{
FileFlow = flowName;
STNodeEditorMain.Nodes.Clear();
STNodeEditorMain.LoadCanvas(flowName);
Title = "流程编辑器 - " + new FileInfo(flowName).Name;
}
private bool IsMouseDown;
private System.Drawing.Point lastMousePosition;
private void STNodeEditorMain_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
lastMousePosition = e.Location;
System.Drawing.PointF m_pt_down_in_canvas = new System.Drawing.PointF();
m_pt_down_in_canvas.X = ((float)e.X - STNodeEditorMain.CanvasOffsetX) / STNodeEditorMain.CanvasScale;
m_pt_down_in_canvas.Y = ((float)e.Y - STNodeEditorMain.CanvasOffsetY) / STNodeEditorMain.CanvasScale;
NodeFindInfo nodeFindInfo = STNodeEditorMain.FindNodeFromPoint(m_pt_down_in_canvas);
if (!string.IsNullOrEmpty(nodeFindInfo.Mark))
{
}
else if (nodeFindInfo.Node != null)
{
}
else if (nodeFindInfo.NodeOption != null)
{
}
else if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
IsMouseDown = true;
}
}
private void STNodeEditorMain_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
IsMouseDown = false;
}
private void STNodeEditorMain_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && IsMouseDown)
{ // 计算鼠标移动的距离
int deltaX = e.X - lastMousePosition.X;
int deltaY = e.Y - lastMousePosition.Y;
// 更新画布偏移
STNodeEditorMain.MoveCanvas(
STNodeEditorMain.CanvasOffsetX + deltaX,
STNodeEditorMain.CanvasOffsetY + deltaY,
bAnimation: false,
CanvasMoveArgs.All
);
// 更新最后的鼠标位置
lastMousePosition = e.Location;
}
}
private void STNodeEditorMain_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
{
var mousePosition = STNodeEditorMain.PointToClient(e.Location);
if (e.Delta < 0)
{
STNodeEditorMain.ScaleCanvas(STNodeEditorMain.CanvasScale - 0.05f, mousePosition.X, mousePosition.Y);
}
else
{
STNodeEditorMain.ScaleCanvas(STNodeEditorMain.CanvasScale + 0.05f, mousePosition.X, mousePosition.Y);
}
}
private void Button_Click_Clear(object sender, RoutedEventArgs e)
{
FileFlow = string.Empty;
STNodeEditorMain.Nodes.Clear();
}
private void Button_Click_Save(object sender, RoutedEventArgs e)
{
Save();
}
private void Save()
{
if (string.IsNullOrEmpty(FileFlow) || !File.Exists(FileFlow))
{
using (System.Windows.Forms.SaveFileDialog saveFileDialog = new System.Windows.Forms.SaveFileDialog())
{
saveFileDialog.Filter = "*.stn|*.stn";
saveFileDialog.Title = "Select a File to Save";
if (saveFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
FileFlow = saveFileDialog.FileName;
}
else
{
// User cancelled the dialog
return;
}
}
}
SaveToFile(FileFlow);
MessageBox.Show("保存成功");
}
public void SaveToFile(string filePath)
{
// 获取画布数据
byte[] data = STNodeEditorMain.GetCanvasData();
// 检查数据是否为空
if (data == null || data.Length == 0)
{
Console.WriteLine("No data to save.");
return;
}
try
{
// 创建文件路径的目录(如果不存在)
string directory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// 将数据写入指定文件路径
File.WriteAllBytes(filePath, data);
Console.WriteLine("File saved successfully.");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred while saving the file: {ex.Message}");
}
}
private void Button_Click_New(object sender, RoutedEventArgs e)
{
System.Windows.Forms.OpenFileDialog ofd = new();
ofd.Filter = "*.stn|*.stn";
ofd.RestoreDirectory = true;
if (ofd.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
OpenFlow(ofd.FileName);
}
private void AutoAlignment_Click(object sender, RoutedEventArgs e)
{
}
}
}

View File

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI.NodeEditor;
using System.Drawing;
namespace WinNodeEditorDemo.NumberNode
{
[STNode("/Number/", "Crystal_lz", "2212233137@qq.com", "www.st233.com", "This node can get two numbers add result")]
public class NumberAddNode : NumberNode
{
private STNodeOption m_in_num1;
private STNodeOption m_in_num2;
private STNodeOption m_out_num;
private int m_nNum1, m_nNum2;
private StringFormat m_sf;
protected override void OnCreate() {
base.OnCreate();
this.Title = "NumberAdd";
m_sf = new StringFormat();
m_sf.LineAlignment = StringAlignment.Center;
m_in_num1 = new STNodeOption("", typeof(int), true);//只能有一个连线
m_in_num2 = new STNodeOption("", typeof(int), true);//只能有一个连线
m_out_num = new STNodeOption("", typeof(int), false);//可以多个连线
this.InputOptions.Add(m_in_num1);
this.InputOptions.Add(m_in_num2);
this.OutputOptions.Add(m_out_num);
m_in_num1.DataTransfer += new STNodeOptionEventHandler(m_in_num_DataTransfer);
m_in_num2.DataTransfer += new STNodeOptionEventHandler(m_in_num_DataTransfer);
}
//当有数据传入时
void m_in_num_DataTransfer(object sender, STNodeOptionEventArgs e) {
//判断连线是否是连接状态(建立连线 断开连线 都会触发该事件)
if (e.Status == ConnectionStatus.Connected) {
if (sender == m_in_num1) {
if (e.TargetOption.Data != null) m_nNum1 = (int)e.TargetOption.Data;//TargetOption为触发此事件的Option
} else {
if (e.TargetOption.Data != null) m_nNum2 = (int)e.TargetOption.Data;
}
} else {
if (sender == m_in_num1) m_nNum1 = 0; else m_nNum2 = 0;
}
//向输出选项上的所有连线传输数据 输出选项上的所有连线都会触发 DataTransfer 事件
m_out_num.TransferData(m_nNum1 + m_nNum2); //m_out_num.Data 将被自动设置
this.Invalidate();
}
/// <summary>
/// 当绘制选项文本时候 将数字绘制 因为STNodeOption.Text被protected修饰 STNode无法进行设置
/// 因为作者并不建议对已经添加在STNode上的选项进行修改 尤其是在AutoSize被设置的情况下
/// 若有需求 应当采用其他方式 比如:重绘 或者添加STNodeControl来显示变化的文本信息
/// </summary>
/// <param name="dt">绘制工具</param>
/// <param name="op">需要绘制的选项</param>
protected override void OnDrawOptionText(DrawingTools dt, STNodeOption op) {
base.OnDrawOptionText(dt, op);
string strText = "";
if (op == m_in_num1) {
m_sf.Alignment = StringAlignment.Near;
strText = m_nNum1.ToString();
} else if (op == m_in_num2) {
m_sf.Alignment = StringAlignment.Near;
strText = m_nNum2.ToString();
} else {
m_sf.Alignment = StringAlignment.Far;
strText = (m_nNum1 + m_nNum2).ToString();
}
dt.Graphics.DrawString(strText, this.Font, Brushes.White, op.TextRectangle, m_sf);
}
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI.NodeEditor;
using System.Drawing;
namespace WinNodeEditorDemo.NumberNode
{
/// <summary>
/// 此节点通过Number属性提供一个整数的输入
/// </summary>
[STNode("/Number","Crystal_lz","2212233137@qq.com","st233.com","Number input node")]
public class NumberInputNode : NumberNode
{
private int _Number;
[STNodeProperty("Input","this is input number")]
public int Number {
get { return _Number; }
set {
_Number = value;
m_op_number.TransferData(value); //将数据向下传递
this.Invalidate();
}
}
private STNodeOption m_op_number; //输出选项
private StringFormat m_sf = new StringFormat();
protected override void OnCreate() {
base.OnCreate();
this.Title = "NumberInput";
m_op_number = new STNodeOption("", typeof(int), false);
this.OutputOptions.Add(m_op_number);
m_sf = new StringFormat();
m_sf.LineAlignment = StringAlignment.Center;
m_sf.Alignment = StringAlignment.Far;
}
/// <summary>
/// 当绘制选项文本时候 将数字绘制 因为STNodeOption.Text被protected修饰 STNode无法进行设置
/// 因为作者并不建议对已经添加在STNode上的选项进行修改 尤其是在AutoSize被设置的情况下
/// 若有需求 应当采用其他方式 比如:重绘 或者添加STNodeControl来显示变化的文本信息
/// </summary>
/// <param name="dt">绘制工具</param>
/// <param name="op">需要绘制的选项</param>
protected override void OnDrawOptionText(DrawingTools dt, STNodeOption op) {
base.OnDrawOptionText(dt, op);
dt.Graphics.DrawString(this._Number.ToString(), this.Font, Brushes.White, op.TextRectangle, m_sf);
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI.NodeEditor;
using System.Drawing;
namespace WinNodeEditorDemo.NumberNode
{
/// <summary>
/// Number节点基类 用于确定节点风格 标题颜色 以及 数据类型颜色
/// </summary>
public abstract class NumberNode : STNode
{
protected override void OnCreate() {
base.OnCreate();
this.TitleColor = Color.FromArgb(200, Color.CornflowerBlue);
}
protected override void OnOwnerChanged() {
base.OnOwnerChanged();
if (this.Owner != null) this.Owner.SetTypeColor(typeof(int), Color.CornflowerBlue);
}
}
}

View File

@ -0,0 +1,514 @@
#pragma warning disable CS8603,CS8604
using ST.Library.UI.NodeEditor;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace ColorVision.Engine.Templates.Flow
{
public class STNodeEditorHelper
{
public STNodeEditor STNodeEditor { get; set; }
public STNodePropertyGrid STNodePropertyGrid1 { get; set; }
public STNodeTreeView STNodeTreeView1 { get; set; }
public STNodeEditorHelper(Control Paraent,STNodeEditor sTNodeEditor, STNodeTreeView sTNodeTreeView1, STNodePropertyGrid sTNodePropertyGrid)
{
STNodeEditor = sTNodeEditor;
STNodeTreeView1 = sTNodeTreeView1;
STNodePropertyGrid1 = sTNodePropertyGrid;
STNodeEditor.NodeAdded += StNodeEditor1_NodeAdded;
STNodeEditor.ActiveChanged += STNodeEditorMain_ActiveChanged;
AddContentMenu();
Paraent.CommandBindings.Add(new CommandBinding(ApplicationCommands.Delete, (s, e) =>
{
foreach (var item in STNodeEditor.GetSelectedNode())
STNodeEditor.Nodes.Remove(item);
} , (s, e) => { e.CanExecute = sTNodeEditor.GetSelectedNode().Length > 0; }));
Paraent.CommandBindings.Add(new CommandBinding(ApplicationCommands.New, (s, e) => sTNodeEditor.Nodes.Clear(), (s, e) => { e.CanExecute = true; }));
Paraent.CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, (s, e) => Copy(), (s, e) => { e.CanExecute = true; }));
Paraent.CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, (s, e) => Paste(), (s, e) => { e.CanExecute = CopyNodes.Count >0;}));
Paraent.CommandBindings.Add(new CommandBinding(ApplicationCommands.SelectAll, (s, e) => SelectAll(), (s, e) => { e.CanExecute = true; }));
Paraent.CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, (s, e) => sTNodeEditor.Nodes.Clear(), (s, e) => { e.CanExecute = true; }));
}
private List<STNode> CopyNodes = new List<STNode>();
public void SelectAll()
{
foreach (var item in STNodeEditor.Nodes.OfType<STNode>())
{
STNodeEditor.AddSelectedNode(item);
}
}
public void Copy()
{
CopyNodes.Clear();
foreach (var item in STNodeEditor.GetSelectedNode())
{
CopyNodes.Add(item);
}
}
public void Paste()
{
int offset = 10;
foreach (var item in CopyNodes)
{
Type type = item.GetType();
STNode sTNode1 = (STNode)Activator.CreateInstance(type);
if (sTNode1 != null)
{
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.CanRead && property.CanWrite)
{
object value = property.GetValue(item);
property.SetValue(sTNode1, value);
}
}
sTNode1.Left = item.Left + offset;
sTNode1.Top = item.Top + offset;
sTNode1.IsSelected = true;
STNodeEditor.Nodes.Add(sTNode1);
if (CopyNodes.Count == 1)
{
item.IsSelected = false;
STNodeEditor.RemoveSelectedNode(item);
STNodeEditor.AddSelectedNode(sTNode1);
STNodeEditor.SetActiveNode(sTNode1);
}
else
{
STNodeEditor.RemoveSelectedNode(item);
STNodeEditor.AddSelectedNode(sTNode1);
}
}
}
CopyNodes.Clear();
foreach (var item in STNodeEditor.GetSelectedNode())
{
CopyNodes.Add(item);
}
}
#region Activate
private void STNodeEditorMain_ActiveChanged(object? sender, EventArgs e)
{
STNodePropertyGrid1.SetNode(STNodeEditor.ActiveNode);
}
#endregion
#region ContextMenu
public void AddNodeContext()
{
foreach (var item in STNodeEditor.Nodes)
{
if (item is STNode node)
{
node.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();
node.ContextMenuStrip.Items.Add("复制", null, (s, e1) => CopySTNode(node));
node.ContextMenuStrip.Items.Add("删除", null, (s, e1) => STNodeEditor.Nodes.Remove(node));
node.ContextMenuStrip.Items.Add("LockOption", null, (s, e1) => STNodeEditor.ActiveNode.LockOption = !STNodeEditor.ActiveNode.LockOption);
node.ContextMenuStrip.Items.Add("LockLocation", null, (s, e1) => STNodeEditor.ActiveNode.LockLocation = !STNodeEditor.ActiveNode.LockLocation);
}
}
}
private void StNodeEditor1_NodeAdded(object sender, STNodeEditorEventArgs e)
{
STNode node = e.Node;
node.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();
node.ContextMenuStrip.Items.Add("删除", null, (s, e1) => STNodeEditor.Nodes.Remove(node));
node.ContextMenuStrip.Items.Add("复制", null, (s, e1) => CopySTNode(node));
node.ContextMenuStrip.Items.Add("LockOption", null, (s, e1) => STNodeEditor.ActiveNode.LockOption = !STNodeEditor.ActiveNode.LockOption);
node.ContextMenuStrip.Items.Add("LockLocation", null, (s, e1) => STNodeEditor.ActiveNode.LockLocation = !STNodeEditor.ActiveNode.LockLocation);
}
public void CopySTNode(STNode sTNode)
{
Type type = sTNode.GetType();
STNode sTNode1 = (STNode)Activator.CreateInstance(type);
if (sTNode1 != null)
{
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.CanRead && property.CanWrite)
{
object value = property.GetValue(sTNode);
property.SetValue(sTNode1, value);
}
}
sTNode1.Left = sTNode.Left;
sTNode1.Top = sTNode.Top;
STNodeEditor.Nodes.Add(sTNode1);
}
}
public void AddContentMenu()
{
STNodeEditor.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();
Type STNodeTreeViewtype = STNodeTreeView1.GetType();
// 获取私有字段信息
FieldInfo fieldInfo = STNodeTreeViewtype.GetField("m_dic_all_type", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
// 获取字段的值
var value = fieldInfo.GetValue(STNodeTreeView1);
Dictionary<string, List<Type>> values = new Dictionary<string, List<Type>>();
if (value is Dictionary<Type, string> m_dic_all_type)
{
foreach (var item in m_dic_all_type)
{
if (values.TryGetValue(item.Value, out List<Type>? value1))
{
value1.Add(item.Key);
}
else
{
values.Add(item.Value, new List<Type>() { item.Key });
}
}
foreach (var nodetype in values.OrderBy(x => x.Key, Comparer<string>.Create((x, y) =>string.Compare(x,y))))
{
string header = nodetype.Key.Replace("WpfNodeEdittorDemo/", "");
var toolStripItem = new System.Windows.Forms.ToolStripMenuItem(header);
foreach (var type in nodetype.Value)
{
if (type.IsSubclassOf(typeof(STNode)))
{
if (Activator.CreateInstance(type) is STNode sTNode)
{
toolStripItem.DropDownItems.Add(sTNode.Title, null, (s, e) =>
{
STNode sTNode1 = (STNode)Activator.CreateInstance(type);
if (sTNode1 != null)
{
var p = STNodeEditor.PointToClient(lastMousePosition);
p = STNodeEditor.ControlToCanvas(p);
sTNode1.Left = p.X;
sTNode1.Top = p.Y;
STNodeEditor.Nodes.Add(sTNode1);
}
});
}
}
}
STNodeEditor.ContextMenuStrip.Items.Add(toolStripItem);
}
}
}
STNodeEditor.ContextMenuStrip.Opening += (s, e) =>
{
if (IsOptionDisConnected) e.Cancel = true;
if (IsHover())
e.Cancel = true;
IsOptionDisConnected = false;
};
STNodeEditor.OptionDisConnected += (s, e) =>
{
IsOptionDisConnected = true;
};
}
bool IsOptionDisConnected;
private System.Drawing.Point lastMousePosition;
public bool IsHover()
{
lastMousePosition = System.Windows.Forms.Cursor.Position;
var p = STNodeEditor.PointToClient(System.Windows.Forms.Cursor.Position);
p = STNodeEditor.ControlToCanvas(p);
foreach (var item in STNodeEditor.Nodes)
{
if (item is STNode sTNode)
{
bool result = sTNode.Rectangle.Contains(p);
if (result)
return true;
if (sTNode.GetInputOptions() is STNodeOption[] inputOptions)
{
foreach (STNodeOption inputOption in inputOptions)
{
if (inputOption != STNodeOption.Empty && inputOption.DotRectangle.Contains(p))
{
return true;
}
}
}
if (sTNode.GetOutputOptions() is STNodeOption[] outputOptions)
{
foreach (STNodeOption outputOption in outputOptions)
{
if (outputOption != STNodeOption.Empty && outputOption.DotRectangle.Contains(p))
{
return true;
}
}
}
}
}
return false;
}
#endregion
#region AutoLayout
public ConnectionInfo[] ConnectionInfo { get; set; }
public float CanvasScale { get => STNodeEditor.CanvasScale; set { STNodeEditor.ScaleCanvas(value, STNodeEditor.CanvasValidBounds.X + STNodeEditor.CanvasValidBounds.Width / 2, STNodeEditor.CanvasValidBounds.Y + STNodeEditor.CanvasValidBounds.Height / 2); } }
public void AutoSize()
{
// Calculate the centers
var boundsCenterX = STNodeEditor.Bounds.Width / 2;
var boundsCenterY = STNodeEditor.Bounds.Height / 2;
// Calculate the scale factor to fit CanvasValidBounds within Bounds
var scaleX = (float)STNodeEditor.Bounds.Width / (float)STNodeEditor.CanvasValidBounds.Width;
var scaleY = (float)STNodeEditor.Bounds.Height / (float)STNodeEditor.CanvasValidBounds.Height;
CanvasScale = Math.Min(scaleX, scaleY);
CanvasScale = CanvasScale > 1 ? 1 : CanvasScale;
// Apply the scale
STNodeEditor.ScaleCanvas(CanvasScale, STNodeEditor.CanvasValidBounds.X + STNodeEditor.CanvasValidBounds.Width / 2, STNodeEditor.CanvasValidBounds.Y + STNodeEditor.CanvasValidBounds.Height / 2);
var validBoundsCenterX = STNodeEditor.CanvasValidBounds.Width / 2;
var validBoundsCenterY = STNodeEditor.CanvasValidBounds.Height / 2;
// Calculate the offsets to move CanvasValidBounds to the center of Bounds
var offsetX = boundsCenterX - validBoundsCenterX * CanvasScale - 50 * CanvasScale;
var offsetY = boundsCenterY - validBoundsCenterY * CanvasScale - 50 * CanvasScale;
// Move the canvas
STNodeEditor.MoveCanvas(offsetX, STNodeEditor.CanvasOffset.Y, bAnimation: true, CanvasMoveArgs.Left);
STNodeEditor.MoveCanvas(offsetX, offsetY, bAnimation: true, CanvasMoveArgs.Top);
}
public void ApplyTreeLayout(int startX, int startY, int horizontalSpacing, int verticalSpacing)
{
ConnectionInfo = STNodeEditor.GetConnectionInfo();
STNode rootNode = null;
if (rootNode == null) return;
int currentY = startY;
HashSet<STNode> MoreParens = new HashSet<STNode>();
void LayoutNode(STNode node, int current)
{
int depeth = GetMaxDepth(node);
// 设置当前节点的位置
node.Left = startX + depeth * horizontalSpacing;
node.Top = current;
var parent = GetParent(node);
// 递归布局子节点
var children = GetChildren(node);
foreach (var child in children)
{
if (GetParent(child).Count > 1)
{
MoreParens.Add(child);
}
else
{
LayoutNode(child, currentY);
var childrenWithout1 = GetChildrenWithout(node);
if (childrenWithout1.Count > 1)
{
currentY += verticalSpacing;
}
}
}
var childrenWithout = GetChildrenWithout(node);
if (childrenWithout.Count > 1)
{
currentY = childrenWithout.Last().Top;
}
// 调整父节点位置到子节点的中心
if (childrenWithout.Count != 0)
{
int firstChildY = childrenWithout.First().Top;
int lastChildY = childrenWithout.Last().Top;
node.Top = (firstChildY + lastChildY) / 2;
}
if (parent.Count > 1)
{
int firstChildY = parent.First().Top;
int lastChildY = parent.Last().Top;
node.Top = (firstChildY + lastChildY) / 2;
}
}
void MoreParentsLayoutNode(STNode node)
{
node.Left = startX + GetMaxDepth(node) * horizontalSpacing;
var parent = GetParent(node);
// 递归布局子节点
var children = GetChildren(node);
int minParentY = parent.Min(c => c.Top);
int maxParentY = parent.Max(c => c.Top);
node.Top = (minParentY + maxParentY) / 2;
SetCof(node, verticalSpacing);
int currenty = node.Top;
foreach (var child in children)
{
LayoutNode(child, currenty);
currenty += verticalSpacing;
}
MoreParens.Remove(node);
}
LayoutNode(rootNode, currentY);
while (MoreParens.Count > 0)
{
foreach (var item in MoreParens.Cast<STNode>().ToList())
{
MoreParentsLayoutNode(item);
}
}
}
public void SetCof(STNode node, int verticalSpacing)
{
foreach (var item in STNodeEditor.Nodes)
{
if (item is STNode onode)
{
if (onode != node && onode.Left == node.Left && onode.Top == node.Top)
{
onode.Top += verticalSpacing;
SetCof(node, verticalSpacing);
}
}
}
}
public int GetMaxDepth(STNode node)
{
var parent = GetParent(node);
if (parent.Count == 0)
{
return 0;
}
return parent.Max(c => GetMaxDepth(c)) + 1;
}
List<STNode> GetParent(STNode node)
{
var list = ConnectionInfo.Where(c => c.Input.Owner == node);
List<STNode> children = new();
foreach (var item in list)
{
children.Add(item.Output.Owner);
}
return children;
}
List<STNode> GetChildrenWithout(STNode node)
{
var list = ConnectionInfo.Where(c => c.Output.Owner == node);
List<STNode> children = new();
foreach (var item in list)
{
if (GetParent(item.Input.Owner).Count == 1)
{
children.Add(item.Input.Owner);
}
}
return children;
}
List<STNode> GetChildren(STNode node)
{
var list = ConnectionInfo.Where(c => c.Output.Owner == node);
List<STNode> children = new();
foreach (var item in list)
{
children.Add(item.Input.Owner);
}
return children;
}
private bool IsPathExists(STNode startNode, STNode endNode)
{
var visited = new HashSet<STNode>();
var queue = new Queue<STNode>();
queue.Enqueue(startNode);
while (queue.Count > 0)
{
var currentNode = queue.Dequeue();
if (currentNode == endNode)
{
return true;
}
visited.Add(currentNode);
var children = GetChildren(currentNode);
foreach (var child in children)
{
if (!visited.Contains(child))
{
queue.Enqueue(child);
}
}
}
return false;
}
#endregion
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI.NodeEditor;
namespace WinNodeEditorDemo
{
/// <summary>
/// 类库自带的STNodeHub并未被STNodeAttribute标记 无法被STNodeTreeView显示 所以需要扩展
/// </summary>
[STNode("/", "Crystal_lz", "2212233137@qq.com", "st233.com", "This is single Hub")]
public class STNodeHubSingle : STNodeHub
{
public STNodeHubSingle()
: base(true) {
this.Title = "S_HUB";
}
}
/// <summary>
/// 类库自带的STNodeHub并未被STNodeAttribute标记 无法被STNodeTreeView显示 所以需要扩展
/// </summary>
[STNode("/", "Crystal_lz", "2212233137@qq.com", "st233.com", "This multi is Hub")]
public class STNodeHubMulti : STNodeHub
{
public STNodeHubMulti()
: base(false) {
this.Title = "M_HUB";
}
}
}

View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<UseWPF>true</UseWPF>
<UseWindowsForms>True</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ST.Library.UI\ST.Library.UI.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Blender\FrmEnumSelect.cs">
<SubType>Form</SubType>
</Compile>
</ItemGroup>
</Project>

View File

@ -23,7 +23,8 @@ hr{
body{
margin:0px;
position:relative;
font-size:14px;
font-size:0.85rem;
line-height:1rem;
background-color:#343434;
}
a{
@ -196,12 +197,16 @@ td{
color:white;
font-size: 12px;
border-radius: 2px;
background-color: gray;
background-color: rgba(125,125,125,.5);
padding: 1px 4px;
margin:0px 4px;
}
.p_hightlight{
color:hotpink;
color: dimgray;
background-color: goldenrod;
padding: 5px;
border-radius: 5px;
}
}
.div_table{
overflow:auto;
@ -215,6 +220,7 @@ td{
display:inline-block;
max-width:100%;
margin-bottom:10px;
line-height:0px;
}
.span_code_title{
display: inline-block;
@ -224,13 +230,14 @@ td{
}
.pre_code{
background-color: #1a1a1a;
font-size: 12px;
font-size: 0.75rem;
overflow: auto;
padding:10px 10px 10px 35px;
display:inline-block;
margin:0px;
min-width:680px;
position:relative;
line-height:1rem;
}
.pre_code:before{
content: ' ';

View File

@ -64,10 +64,10 @@
<div id="div_top_left"><img src="./images/page_top.png"/></div>
<div id="div_top_right">
<h1 id="h1_title">STNodeEditor</h1>
<p style="margin-top:2rem;text-align:left;">STNodeEditor 是一个轻量且功能强大的节点编辑器 使用方式非常简洁 提供了丰富的属性以及事件可以非常方便的完成节点之间数据的交互及通知 大量的重载函数供开发者使用具有很高的自由性</p>
<p style="margin-top:2rem;text-align:left;">STNodeEditor 是一个轻量且功能强大的节点编辑器 使用方式非常简洁 提供了丰富的属性以及事件可以非常方便的完成节点之间数据的交互及通知 大量的虚函数可供开发者重写具有很高的自由性</p>
<center>
<a id="a_btn_down" href="https://github.com/DebugST/DotNet_WinForm_NodeEditor/releases">下载 STNodeEditor</a><br/>
<a class="a_icon" attr_text="Github" style="background-image:url('./images/github.png'") href="https://github.com/DebugST/DotNet_WinForm_NodeEditor"></a>
<a class="a_icon" attr_text="Github" style="background-image:url('./images/github.png')" href="https://github.com/DebugST/STNodeEditor"></a>
<a class="a_icon" attr_text="Gitee" style="background-image:url('./images/gitee.png')" href="https://gitee.com/DebugST/DotNet_WinForm_NodeEditor"></a>
<a class="a_icon" attr_text="API手册" style="background-image:url('./images/api.png')" href="./doc_cn.html"></a>
</center>
@ -215,4 +215,4 @@
</div>
</div>
</body>
</html>
</html>

View File

@ -32,10 +32,10 @@
<div id="div_top_left"><img src="./images/page_top.png"/></div>
<div id="div_top_right">
<h1 id="h1_title">STNodeEditor</h1>
<p style="margin-top:2rem;text-align:left;">STNodeEditor is a lightweight and powerful node editor, 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 overloaded functions are available for developers to use. It is very free.</p>
<p style="margin-top:2rem;text-align:left;">STNodeEditor is a lightweight and powerful node editor, 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 vitual functions are available for developers to use. It is very free.</p>
<center>
<a id="a_btn_down" href="https://github.com/DebugST/DotNet_WinForm_NodeEditor/releases">Download STNodeEditor</a><br/>
<a class="a_icon" style="background-image:url('./images/github.png'") href="https://github.com/DebugST/DotNet_WinForm_NodeEditor"></a>
<a class="a_icon" style="background-image:url('./images/github.png')" href="https://github.com/DebugST/STNodeEditor"></a>
<a class="a_icon" style="background-image:url('./images/gitee.png')" href="https://gitee.com/DebugST/DotNet_WinForm_NodeEditor"></a>
<a class="a_icon" style="background-image:url('./images/api.png')" href="./doc_en.html"></a>
</center>
@ -176,4 +176,4 @@
</div>
</div>
</body>
</html>
</html>

View File

@ -101,10 +101,10 @@
<h1 class='h_title anchor_point' anchor='a_a'>Foreword</h1>
<div><h2 class='h_option anchor_point' anchor='a_b'>Introduction</h2></div>
<p>It was a winter. The author who was studying radio security used <a target='_bank' href='https://www.gnuradio.org/'>GNURadio</a>. That was the first time the author used the node editor.</p>
<p>-&gt; What? Excuse me... What"s this?.. What the hell is this?...</p>
<p>It was a spring season, and I don"t know why the whole world has changed after the Chinese New Year. Everyone was forced to stay at home. The extremely boring author learned <a target='_bank' href='https://www.blender.org/'>Blender</a>. That was the second time the author used the node editor.</p>
<p>-&gt; What? Excuse me... What's this?.. What the hell is this?...</p>
<p>It was a spring season, and I don't know why the whole world has changed after the Chinese New Year. Everyone was forced to stay at home. The extremely boring author learned <a target='_bank' href='https://www.blender.org/'>Blender</a>. That was the second time the author used the node editor.</p>
<p>-&gt; Wo...It turns out that this one is really convenient to use.</p>
<p>So some ideas gradually emerged in the author"s mind, making the author want to make one.</p>
<p>So some ideas gradually emerged in the author's mind, making the author want to make one.</p>
<p>It was a summer, I dont know why the author started to learn <a target='_bank' href='http://www.blackmagicdesign.com/cn/products/davinciresolve/'>Davinci</a> 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.</p>
<p>So <span class='span_mark'>STNodeEditor</span> appeared.</p>
<img width=990 src='./images/page_top.png'/>
@ -196,8 +196,8 @@
<span class='span_code_line'></span>}</pre>
</div>
<img width=208 src='./images/tu_mynode_single.png'/>
<p class='p_hightlight'>In <span class='span_mark'>multi-connection</span> mode, an option can be connected by multiple options of <span class='span_mark'>the same data type</span> (rectangle)</p>
<p class='p_hightlight'>In single-connection mode, an option can only be connected by one option of the same data type (circle)</p>
<p class='p_hightlight'>In <span class='span_mark'>multi-connection</span> mode, an option can be connected by multiple options of the same data type (rectangle)</p>
<p class='p_hightlight'>In <span class='span_mark'>single-connection</span> mode, an option can only be connected by one option of the same data type (circle)</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_f'>STNodeOption.Empty</h2></div>
<hr/>
@ -534,8 +534,8 @@
<span class='span_code_line'></span>}</pre>
</div>
<img width=418 src='./images/tu_imagenode.png'/>
<p>clickthe <span class='span_mark'>Open Image</span> button, you can select an image and display it in the node. After the <span class='span_mark'>ImageSizeNode</span> is connected, the size of the image will be display.</p>
<p>The code of the <span class='span_mark'>ImageChannel</span> node is not given here. The code is used in the <span class='span_mark'>WinNodeEditorDemo</span> project to extract the RGB channel of an image. For <span class='span_mark'>ImageShowNode</span>, it just provides the data source and displays it. For the <span class='span_mark'>ImageSizeNode</span> and <span class='span_mark'>ImageChannel</span> 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</p>
<p>click the <span class='span_mark'>Open Image</span> button, you can select an image and display it in the node. After the <span class='span_mark'>ImageSizeNode</span> is connected, the size of the image will be display.</p>
<p>The code of the <span class='span_mark'>ImageChannel</span> node is not given here. The code is used in the <span class='span_mark'>WinNodeEditorDemo</span> project to extract the RGB channel of an image. For <span class='span_mark'>ImageShowNode</span>, it just provides the data source and displays it. For the <span class='span_mark'>ImageSizeNode</span> and <span class='span_mark'>ImageChannel</span> 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</p>
<p>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 <span class='span_mark'>Image</span> data type, so that nodes and nodes There is no coupling relationship between them. High class poly low coupling.</p>
<h1 class='h_title anchor_point' anchor='a_p'>[basic]STNodeEditor</h1>
<p><span class='span_mark'>STNodeEditor</span> as a container of <span class='span_mark'>STNode</span> also provides a large number of properties and events for developers to use. For more detailed API list of <span class='span_mark'>STNodeEditor</span>, please refer to <a target='_bank' href='./doc.html'>API document</a></p>