Compare commits

..

1 Commits
main ... 1.0

Author SHA1 Message Date
DebugST
ae9c9f09ff backup 1.0 2021-04-30 13:26:59 +08:00
168 changed files with 7516 additions and 18086 deletions

368
.gitignore vendored
View File

@ -1,364 +1,4 @@
## 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
.DS_Store
/Bin
bin/
obj/

451
README.md
View File

@ -1,399 +1,114 @@
# Version 3.0
## STNodeEditor
现在: 2022-08-30
`STNodeEditor` 是一个很不错的节点编辑器 使用方式非常简洁 提供了丰富的属性以及事件 可以非常方便的完成节点之间数据的交互及通知 大量的重载函数供开发者使用具有很高的自由性
`3.0`版本进入排期开发,开发进度将同步更新。具体说明请查看 [V3_CN.md](./V3_CN.md)
编译环境: `VS 2010 (.Net 3.5)`
![STNodeEditor](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/node.png)
now: 2022-08-30
此控件采用 [`MIT`](https://opensource.org/licenses/mit-license.php) 开源协议开源 使开发者能够拥有更大的自由度更少的限制
The version `3.0` has been start coding,The development progress is updated synchronously. more info refer [V3_EN.md](./V3_EN.md)
`STNodeEditor` 拥有非常方便的UI自定义能力 提供的 `STNodeControl` 基类 可以让开发者能够像自定义 `WinForm` 控件一样去定义 节点需要的控件
2023-04-20
![STNodeEditor](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/formImage.png)
老铁们,最近问更新情况的人比较多。确实摆烂了一段时间,什么也不想做。不过现在陆陆续续回到状态了。不过在继续更新之前手里还有一个[STJson](https://github.com/DebugST/STJson)。也正是当前项目需要的。`STJson`项目也摆烂很久了。。不过这两天应该会完工了,提供了全套的`Json`解析操作包括`JsonPath`的支持。。如果有经常使用`Json`的小伙伴可以关注一下。现在已经进入最后的调试和文档教程编写阶段。。等完工后回到`STNodeEditor`的开发,并且`STNodeEditor`的数据保存也将提供`Json`格式。
## 简要说明
* 画布
* 移动 `鼠标中键` 拖动 Mac用户可二指拖动 `触控板`
* 缩放 按下 `Control` + `鼠标滚轮`
* 画布中的节点内容以及连线关系可通过 `STNodeEditor.Load/SaveCanvas()` 加载或者保存
* 删除连线
* 悬停到连线上 `鼠标右键`
* 移动节点
* `鼠标左键` 拖动 `节点标题`
* 之所以是拖动标题而不是节点任意位置 是因为作者的设计思路是将节点视为一个 `窗体` 窗体的客户区域留给开发者自定义
* STNode
* 如同 `System.Windows.Forms.TreeView` 一样 所有的节点都保存在 `STNodeEditor.Nodes` 中 其数据类型为 `STNode`
* `STNode` 为抽象类被 `abstract` 修饰 需要开发者自己继承向节点中添加选项
* `STNode` 有三个重要属性 `InputOptions` `OutputOptions` `Controls`
# 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 是一个轻量且功能强大的节点编辑器 纯`GDI`实现无任何依赖库仅仅`100+Kb` 使用方式非常简洁 提供了丰富的属性以及事件可以非常方便的完成节点之间数据的交互及通知 大量的虚函数可供开发者重写具有很高的自由性
Environment: VS2010(.NET 3.5)
![STNodeEditor](https://debugst.github.io/STNodeEditor/images/page_top.png)
![STNodeEditor](https://debugst.github.io/STNodeEditor/images/node_scan.png)
项目主页 (Project home): [DebugST.github.io/STNodeEditor](https://DebugST.github.io/STNodeEditor) (简体中文, English)
教程文档: [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)
NuGet: [https://www.nuget.org/packages/ST.Library.UI/](https://www.nuget.org/packages/ST.Library.UI/)
```
PM> Install-Package ST.Library.UI -Version 2.0.0
```
---
# 简介
那是一个冬季 在研究无线电安全的作者接触到了[GNURadio](https://www.gnuradio.org/) 那是作者第一次接触到节点编辑器
-> What? Excuse me... What"s this?.. 这是什么鬼东西?...
那是一个春季 不知道为什么 过完年整个世界都变了 大家被迫窝在家里 无聊至极的作者学起了[Blender](https://www.blender.org/)那是作者第二次接触到节点编辑器
-> Wo...原来这东西可以这么玩...真方便
于是一些想法在作者脑中逐渐诞生 让作者有了想做一个这样的东西的想法
那是一个夏季 不知道为什么 作者又玩起了[Davinci](http://www.blackmagicdesign.com/cn/products/davinciresolve/)那是作者第三次接触到节点编辑器 这一次的接触让作者对节点编辑器的好感倍增 作者瞬间觉得 只要是可以模块化流程化的功能 万物皆可节点化
---
# 像流程图一样使用你的功能
你是否有设想过流程图不再是流程图 而是直接可以执行的?
在一些开发过程中我们可能会为整个程序设计一个流程图 上面包含了我们存在的功能模块以及执行流程 然后由开发者逐一实现
但是这样会带来一些问题 程序的执行流程可能会被硬编码到程序中去 如果突然有一天可能需要改变执行顺序或者添加删除一个执行模块 可能需要开发者对代码重新编辑然后编译 而且各个功能模块之间的调用也需要开发者进行编码调度 增加开发成本 等一系列的问题
`STNodeEditor` 就是为此诞生
---
`STNodeEditor` 包含3部分 `TreeView` `PropertyGrid` `NodeEditor` 这三部分组成了一套完整的可使用框架
* TreeView
* 可以把执行功能编码到一个节点中 而 `TreeView` 则负责展示以及检索节点 在 `TreeView` 中的节点可直接拖拽添加到 `NodeEditor`
* PropertyGrid
* 类似与 `WinForm` 开发使用的属性窗口 作为一个节点 它也是可以有属性的 而作者在编辑器进行设计的过程中也把一个节点视作一个 `Form` 让开发者几乎没有什么学习成本直接上手一个节点的开发
*NodeEditor
*`NodeEditor` 是用户组合自己执行流程的地方 使得功能模块执行流程可视化
---
# 如何使用它?
STNodeEditor的使用非常简单 你几乎可以没有任何学习成本的去使用的 当然最重要的一点就是 你需要知道如何去创建一个节点
你可以像创建一个Form一样去创建一个Node
``` cs
using ST.Library.UI.NodeEditor;
public class MyNode : STNode
## 示例
```cs
public class DemoNode : 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">
可以看到它的使用方式和 `Form` 确实很像 其实目前还暂时没有提供所见即所得的UI设计器 而且一个 `STNode` 它同样有它的控件集合且数据类型为 `STNodeControl`
`STNodeControl` 作为 `STNode` 控件的基类 它拥有着和 `System.Windows.Forms.Control` 许多同名的属性和事件 一切的初衷都只为与 `WinForm` 靠近
**注意:在目前的版本中(2.0) STNodeEditor仅仅提供了STNodeControl基类 并未提供任何一个可用控件 当然在附随的Demo工程中包含了部分示例演示如何自定义一个控件 由于这属于自定义控件的范畴 所以演示并未太多 若需了解关于自定义控件如何开发可参考作者:[自定义控件开发](http://st233.com/blog.php?group=1) 系列文章 当然在后续的版本中 作者将提供部分常用控件 虽说作者想把使用方式往WinForm上靠 单仅仅是把它当作WinForm使用并不是作者的初衷**
上面的演示仅仅是为了让大家感到亲切感 毕竟 `WinForm` 可能是大家熟悉的一个东西 但是如果仅仅是把它当作 `WinForm` 使用毫无意义 对于一个节点来说 最重要的属性当然是数据的输入和输出
``` cs
public class MyNode : STNode
{
protected override void OnCreate() {//等同 [public MyNode(){}]
protected override void OnCreate() {
//在创建节点时候向其添加需要的选项
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()中设置
this.InputOptions.Add(new STNodeOption("Input", typeof(string), false));
this.InputOptions.Add(new STNodeOption("SingleNode", typeof(System.Drawing.Image), true));
this.InputOptions.Add(new STNodeOption("SingleNode", typeof(object), true));
this.OutputOptions.Add(new STNodeOption("output", typeof(string), false));
this.OutputOptions.Add(new STNodeOption("Single", typeof(System.Drawing.Icon), true));
this.OutputOptions.Add(new STNodeOption("Single", typeof(object), true));
this.Title = "Demo_Node";
}
}
//stNodeEditor1.SetTypeColor(type, color);此方法会自动替换已存在值
stNodeEditor1.TypeColor.Add(typeof(string), Color.Yellow);
stNodeEditor1.TypeColor.Add(typeof(Image), Color.Red);
stNodeEditor1.Nodes.Add(new DemoNode());
```
上述代码的 `DemoNode` 被添加到节点编辑器中的效果为
<img alt="MyNode.png" src="https://img-blog.csdnimg.cn/img_convert/a7bb83f3bc1c39143d71a42f26668e4f.png" width="208">
![STNodeEditor](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/node_demo.png)
通过上面的案例你可以看到 `STNode` 有两个重要的属性 `InputOptions``OutputOptions` 其数据类型为 `STNodeOption``STNodeOption` 有两种连接模式 `single-connection``multi-connection`
由此可见 开发者要自定义一个节点相当便捷 当然上述代码并没有包含事件处理 上述代码仅仅是在演示 `STNodeOption` 的效果 下列代码则是一个包含了计算两个整数和的功能
```cs
public class NodeNumberAdd : STNode
{
private STNodeOption m_in_num1;
private STNodeOption m_in_num2;
private STNodeOption m_out_num;
private int m_nNum1, m_nNum2;
* 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);
this.Title = "NumberAdd";
m_in_num1 = new STNodeOption("num1", typeof(int), true);//只能有一个连线
m_in_num2 = new STNodeOption("num2", typeof(int), true);//只能有一个连线
m_out_num = new STNodeOption("result", 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);
}
}
```
<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, "--");
//当有数据传入时
void m_in_num_DataTransfer(object sender, STNodeOptionEventArgs e) {
//判断连线是否是连接状态(建立连线 断开连线 都会触发该事件)
if (e.Status == ConnectionStatus.Connected) {
if (sender == m_in_num1)
m_nNum1 = (int)e.TargetOption.Data;//TargetOption为触发此事件的Option
else
m_nNum2 = (int)e.TargetOption.Data;
} else {
this.SetOptionText(m_op_time_in, ((DateTime)e.TargetOption.Data).ToString());
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 将被自动设置
}
protected override void OnOwnerChanged() {
//通常刚被添加到节点编辑器时触发 如是以插件方式提供的节点 应当向编辑器提交数据类型颜色
base.OnOwnerChanged();
if (this.Owner == null) return; //或者通过m_in_num1.DotColor = Color.Red;进行设置
this.Owner.SetTypeColor(typeof(int),Color.Red);
}
}
```
<img alt="TimeNode.gif" src="https://img-blog.csdnimg.cn/img_convert/91c426fda7d1e8dd7d85307168868116.gif" width="208">
效果为
可以看到当连接被建立时 `ShowTime` 节点每秒都在刷新 下面是一个更加复杂一点的案例 但是这里并没有给出代码请参考附随工程的 `Demo`
![STNodeEditor](https://debugst.github.io/DotNet_WinForm_NodeEditor/images/node_add.png)
<img alt="ImageNode.png" src="https://img-blog.csdnimg.cn/img_convert/2ffaa58c22f91f1219143256dfa49799.png" width="418">
更多细节请参考文档
点击 `Open Image` 按钮可打开并显示一张图片在 `ImageShowNode` 中并将图片作为输出数据 `ImageChanel` 则负责接收一张图像并处理输出图像的RGB图像及原图 `ImageSize` 则负责接收并显示一张图像的尺寸信息
项目主页: [DebugST.github.io/DotNet_WinForm_NodeEditor](https://DebugST.github.io/DotNet_WinForm_NodeEditor)
对于上面的节点在开发期间它们并不知道会被什么样的节点连接 也并不知道会被连接到什么节点上 开发者仅仅是完成了自己的功能处理接收到的数据并将结果打包给 `STNodeOption` 无需关系最终会被谁把结果拿走并处理 使得节点之间与节点之间的耦合关系大大降低 唯一将它们联系在一起的是一个 `Image` 数据类型 最终执行的逻辑交给用户自己拖拽节点组合他们自己想要的流程 使得功能的执行流程变得可视化 这也是作者的初衷
开发文档: [DebugST.github.io/DotNet_WinForm_NodeEditor/doc.html](https://DebugST.github.io/DotNet_WinForm_NodeEditor/doc.html)
**关于更多的教程和文档请参考:[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/)
## 关于作者
* Github: [DebugST](https://DebugST.github.io)
* Blog: [Crystal_lz](http://st233.com)
* Mail: (2212233137@qq.com)

View File

@ -1,144 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace ST.Library.UI.NodeEditor
{
internal class FrmNodePreviewPanel : Form
{
public Color BorderColor { get; set; }
public bool AutoBorderColor { get; set; }
private bool m_bRight;
private Point m_ptHandle;
private int m_nHandleSize;
private Rectangle m_rect_handle;
private Rectangle m_rect_panel;
private Rectangle m_rect_exclude;
private Region m_region;
private Type m_type;
private STNode m_node;
private STNodeEditor m_editor;
private STNodePropertyGrid m_property;
private Pen m_pen = new Pen(Color.Black);
private SolidBrush m_brush = new SolidBrush(Color.Black);
private static FrmNodePreviewPanel m_last_frm;
[DllImport("user32.dll")]
private static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw);
public FrmNodePreviewPanel(Type stNodeType, Point ptHandle, int nHandleSize, bool bRight, STNodeEditor editor, STNodePropertyGrid propertyGrid) {
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
if (m_last_frm != null) m_last_frm.Close();
m_last_frm = this;
m_editor = editor;
m_property = propertyGrid;
m_editor.Size = new Size(200, 200);
m_property.Size = new Size(200, 200);
m_editor.Location = new Point(1 + (bRight ? nHandleSize : 0), 1);
m_property.Location = new Point(m_editor.Right, 1);
m_property.InfoFirstOnDraw = true;
this.Controls.Add(m_editor);
this.Controls.Add(m_property);
this.ShowInTaskbar = false;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Size = new Size(402 + nHandleSize, 202);
m_type = stNodeType;
m_ptHandle = ptHandle;
m_nHandleSize = nHandleSize;
m_bRight = bRight;
this.AutoBorderColor = true;
this.BorderColor = Color.DodgerBlue;
}
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
m_node = (STNode)Activator.CreateInstance(m_type);
m_node.Left = 20; m_node.Top = 20;
m_editor.Nodes.Add(m_node);
m_property.SetNode(m_node);
m_rect_panel = new Rectangle(0, 0, 402, 202);
m_rect_handle = new Rectangle(m_ptHandle.X, m_ptHandle.Y, m_nHandleSize, m_nHandleSize);
m_rect_exclude = new Rectangle(0, m_nHandleSize, m_nHandleSize, this.Height - m_nHandleSize);
if (m_bRight) {
this.Left = m_ptHandle.X;
m_rect_panel.X = m_ptHandle.X + m_nHandleSize;
} else {
this.Left = m_ptHandle.X - this.Width + m_nHandleSize;
m_rect_exclude.X = this.Width - m_nHandleSize;
m_rect_panel.X = this.Left;
}
if (m_ptHandle.Y + this.Height > Screen.GetWorkingArea(this).Bottom) {
this.Top = m_ptHandle.Y - this.Height + m_nHandleSize;
m_rect_exclude.Y -= m_nHandleSize;
} else this.Top = m_ptHandle.Y;
m_rect_panel.Y = this.Top;
m_region = new Region(new Rectangle(Point.Empty, this.Size));
m_region.Exclude(m_rect_exclude);
using (Graphics g = this.CreateGraphics()) {
IntPtr h = m_region.GetHrgn(g);
FrmNodePreviewPanel.SetWindowRgn(this.Handle, h, false);
m_region.ReleaseHrgn(h);
}
this.MouseLeave += Event_MouseLeave;
m_editor.MouseLeave += Event_MouseLeave;
m_property.MouseLeave += Event_MouseLeave;
this.BeginInvoke(new MethodInvoker(() => {
m_property.Focus();
}));
}
protected override void OnClosing(CancelEventArgs e) {
base.OnClosing(e);
this.Controls.Clear();
m_editor.Nodes.Clear();
m_editor.MouseLeave -= Event_MouseLeave;
m_property.MouseLeave -= Event_MouseLeave;
m_last_frm = null;
}
void Event_MouseLeave(object sender, EventArgs e) {
Point pt = Control.MousePosition;
if (m_rect_panel.Contains(pt) || m_rect_handle.Contains(pt)) return;
this.Close();
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
m_pen.Color = this.AutoBorderColor ? m_node.TitleColor : this.BorderColor;
m_brush.Color = m_pen.Color;
g.DrawRectangle(m_pen, 0, 0, this.Width - 1, this.Height - 1);
g.FillRectangle(m_brush, m_rect_exclude.X - 1, m_rect_exclude.Y - 1, m_rect_exclude.Width + 2, m_rect_exclude.Height + 2);
Rectangle rect = this.RectangleToClient(m_rect_handle);
rect.Y = (m_nHandleSize - 14) / 2;
rect.X += rect.Y + 1;
rect.Width = rect.Height = 14;
m_pen.Width = 2;
g.DrawLine(m_pen, rect.X + 4, rect.Y + 3, rect.X + 10, rect.Y + 3);
g.DrawLine(m_pen, rect.X + 4, rect.Y + 6, rect.X + 10, rect.Y + 6);
g.DrawLine(m_pen, rect.X + 4, rect.Y + 11, rect.X + 10, rect.Y + 11);
g.DrawLine(m_pen, rect.X + 7, rect.Y + 7, rect.X + 7, rect.Y + 10);
m_pen.Width = 1;
g.DrawRectangle(m_pen, rect.X, rect.Y, rect.Width - 1, rect.Height - 1);
}
}
}

View File

@ -1,114 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using ST.Library.UI.NodeEditor;
namespace ST.Library.UI
{
internal class FrmSTNodePropertyInput : Form
{
private STNodePropertyDescriptor m_descriptor;
private Rectangle m_rect;
private Pen m_pen;
private SolidBrush m_brush;
private TextBox m_tbx;
public FrmSTNodePropertyInput(STNodePropertyDescriptor descriptor) {
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
m_rect = descriptor.RectangleR;
m_descriptor = descriptor;
this.ShowInTaskbar = false;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.BackColor = descriptor.Control.AutoColor ? descriptor.Node.TitleColor : descriptor.Control.ItemSelectedColor;
m_pen = new Pen(descriptor.Control.ForeColor, 1);
m_brush = new SolidBrush(this.BackColor);
}
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
Point pt = m_descriptor.Control.PointToScreen(m_rect.Location);
pt.Y += m_descriptor.Control.ScrollOffset;
this.Location = pt;
this.Size = new System.Drawing.Size(m_rect.Width + m_rect.Height, m_rect.Height);
m_tbx = new TextBox();
m_tbx.Font = m_descriptor.Control.Font;
m_tbx.ForeColor = m_descriptor.Control.ForeColor;
m_tbx.BackColor = Color.FromArgb(255, m_descriptor.Control.ItemValueBackColor);
m_tbx.BorderStyle = BorderStyle.None;
m_tbx.Size = new Size(this.Width - 4 - m_rect.Height, this.Height - 2);
m_tbx.Text = m_descriptor.GetStringFromValue();
this.Controls.Add(m_tbx);
m_tbx.Location = new Point(2, (this.Height - m_tbx.Height) / 2);
m_tbx.SelectAll();
m_tbx.LostFocus += (s, ea) => this.Close();
m_tbx.KeyDown += new KeyEventHandler(tbx_KeyDown);
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
m_brush.Color = m_tbx.BackColor;
g.FillRectangle(m_brush, 1, 1, this.Width - 2 - m_rect.Height, this.Height - 2);
m_brush.Color = m_descriptor.Control.ForeColor;
//Enter
g.FillPolygon(m_brush, new Point[]{
new Point(this.Width - 21, this.Height - 2),
new Point(this.Width - 14, this.Height - 2),
new Point(this.Width - 14, this.Height - 8)
});
g.DrawLine(m_pen, this.Width - 14, this.Height - 3, this.Width - 4, this.Height - 3);
g.DrawLine(m_pen, this.Width - 4, this.Height - 3, this.Width - 4, 14);
g.DrawLine(m_pen, this.Width - 8, 13, this.Width - 4, 13);
//----
g.DrawLine(m_pen, this.Width - 19, 11, this.Width - 4, 11);
//E
g.DrawLine(m_pen, this.Width - 19, 3, this.Width - 16, 3);
g.DrawLine(m_pen, this.Width - 19, 6, this.Width - 16, 6);
g.DrawLine(m_pen, this.Width - 19, 9, this.Width - 16, 9);
g.DrawLine(m_pen, this.Width - 19, 3, this.Width - 19, 9);
//S
g.DrawLine(m_pen, this.Width - 13, 3, this.Width - 10, 3);
g.DrawLine(m_pen, this.Width - 13, 6, this.Width - 10, 6);
g.DrawLine(m_pen, this.Width - 13, 9, this.Width - 10, 9);
g.DrawLine(m_pen, this.Width - 13, 3, this.Width - 13, 6);
g.DrawLine(m_pen, this.Width - 10, 6, this.Width - 10, 9);
//C
g.DrawLine(m_pen, this.Width - 7, 3, this.Width - 4, 3);
g.DrawLine(m_pen, this.Width - 7, 9, this.Width - 4, 9);
g.DrawLine(m_pen, this.Width - 7, 3, this.Width - 7, 9);
}
void tbx_KeyDown(object sender, KeyEventArgs e) {
if (e.KeyCode == Keys.Escape) this.Close();
if (e.KeyCode != Keys.Enter) return;
try {
m_descriptor.SetValue(((TextBox)sender).Text, null);
m_descriptor.Control.Invalidate();//add rect;
} catch (Exception ex) {
m_descriptor.OnSetValueError(ex);
}
this.Close();
}
private void InitializeComponent() {
this.SuspendLayout();
//
// FrmSTNodePropertyInput
//
this.ClientSize = new System.Drawing.Size(292, 273);
this.Name = "FrmSTNodePropertyInput";
this.ResumeLayout(false);
}
}
}

View File

@ -1,116 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace ST.Library.UI.NodeEditor
{
internal class FrmSTNodePropertySelect : Form
{
private STNodePropertyDescriptor m_descriptor;
private int m_nItemHeight = 25;
private static Type m_t_bool = typeof(bool);
private Pen m_pen;
private SolidBrush m_brush;
private StringFormat m_sf;
private Color m_clr_item_1 = Color.FromArgb(10, 0, 0, 0);// Color.FromArgb(255, 40, 40, 40);
private Color m_clr_item_2 = Color.FromArgb(10, 255, 255, 255);// Color.FromArgb(255, 50, 50, 50);
private object m_item_hover;
public FrmSTNodePropertySelect(STNodePropertyDescriptor descriptor) {
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
m_descriptor = descriptor;
this.Size = descriptor.RectangleR.Size;
this.ShowInTaskbar = false;
this.BackColor = descriptor.Control.BackColor;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
m_pen = new Pen(descriptor.Control.AutoColor ? descriptor.Node.TitleColor : descriptor.Control.ItemSelectedColor, 1);
m_brush = new SolidBrush(this.BackColor);
m_sf = new StringFormat();
m_sf.LineAlignment = StringAlignment.Center;
m_sf.FormatFlags = StringFormatFlags.NoWrap;
}
private List<object> m_lst_item = new List<object>();
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
Point pt = m_descriptor.Control.PointToScreen(m_descriptor.RectangleR.Location);
pt.Y += m_descriptor.Control.ScrollOffset;
this.Location = pt;
if (m_descriptor.PropertyInfo.PropertyType.IsEnum) {
foreach (var v in Enum.GetValues(m_descriptor.PropertyInfo.PropertyType)) m_lst_item.Add(v);
} else if (m_descriptor.PropertyInfo.PropertyType == m_t_bool) {
m_lst_item.Add(true);
m_lst_item.Add(false);
} else {
this.Close();
return;
}
this.Height = m_lst_item.Count * m_nItemHeight;
Rectangle rect = Screen.GetWorkingArea(this);
if (this.Bottom > rect.Bottom) this.Top -= (this.Bottom - rect.Bottom);
this.MouseLeave += (s, ea) => this.Close();
this.LostFocus += (s, ea) => this.Close();
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
Rectangle rect_back = new Rectangle(0, 0, this.Width, m_nItemHeight);
Rectangle rect_font = new Rectangle(10, 0, this.Width - 13, m_nItemHeight);
int nIndex = 0;
string strVal = m_descriptor.GetStringFromValue();
foreach (var v in m_lst_item) {
m_brush.Color = nIndex++ % 2 == 0 ? m_clr_item_1 : m_clr_item_2;
g.FillRectangle(m_brush, rect_back);
if (v == m_item_hover) {
m_brush.Color = m_descriptor.Control.ItemHoverColor;
g.FillRectangle(m_brush, rect_back);
}
if (v.ToString() == strVal) {
m_brush.Color = m_descriptor.Control.ItemSelectedColor;
g.FillRectangle(m_brush, 4, rect_back.Top + 10, 5, 5);
}
m_brush.Color = m_descriptor.Control.ForeColor;
g.DrawString(v.ToString(), m_descriptor.Control.Font, m_brush, rect_font, m_sf);
rect_back.Y += m_nItemHeight;
rect_font.Y += m_nItemHeight;
}
g.DrawRectangle(m_pen, 0, 0, this.Width - 1, this.Height - 1);
}
protected override void OnMouseMove(MouseEventArgs e) {
base.OnMouseMove(e);
int nIndex = e.Y / m_nItemHeight;
if (nIndex < 0 || nIndex >= m_lst_item.Count) return;
var item = m_lst_item[e.Y / m_nItemHeight];
if (m_item_hover == item) return;
m_item_hover = item;
this.Invalidate();
}
protected override void OnMouseClick(MouseEventArgs e) {
base.OnMouseClick(e);
this.Close();
int nIndex = e.Y / m_nItemHeight;
if (nIndex < 0) return;
if (nIndex > m_lst_item.Count) return;
try {
m_descriptor.SetValue(m_lst_item[nIndex], null);
} catch (Exception ex) {
m_descriptor.OnSetValueError(ex);
}
}
}
}

View File

@ -1,121 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Text.RegularExpressions;
namespace ST.Library.UI.NodeEditor
{
/// <summary>
/// STNode节点特性
/// 用于描述STNode开发者信息 以及部分行为
/// </summary>
public class STNodeAttribute : Attribute
{
private string _Path;
/// <summary>
/// 获取STNode节点期望在树形控件的路径
/// </summary>
public string Path {
get { return _Path; }
}
private string _Author;
/// <summary>
/// 获取STNode节点的作者名称
/// </summary>
public string Author {
get { return _Author; }
}
private string _Mail;
/// <summary>
/// 获取STNode节点的作者邮箱
/// </summary>
public string Mail {
get { return _Mail; }
}
private string _Link;
/// <summary>
/// 获取STNode节点的作者链接
/// </summary>
public string Link {
get { return _Link; }
}
private string _Description;
/// <summary>
/// 获取STNode节点的描述信息
/// </summary>
public string Description {
get { return _Description; }
}
private static char[] m_ch_splitter = new char[] { '/', '\\' };
private static Regex m_reg = new Regex(@"^https?://", RegexOptions.IgnoreCase);
/// <summary>
/// 构造一个STNode特性
/// </summary>
/// <param name="strPath">期望路径</param>
public STNodeAttribute(string strPath) : this(strPath, null, null, null, null) { }
/// <summary>
/// 构造一个STNode特性
/// </summary>
/// <param name="strPath">期望路径</param>
/// <param name="strDescription">描述信息</param>
public STNodeAttribute(string strPath, string strDescription) : this(strPath, null, null, null, strDescription) { }
/// <summary>
/// 构造一个STNode特性
/// </summary>
/// <param name="strPath">期望路径</param>
/// <param name="strAuthor">STNode作者名称</param>
/// <param name="strMail">STNode作者邮箱</param>
/// <param name="strLink">STNode作者链接</param>
/// <param name="strDescription">STNode节点描述信息</param>
public STNodeAttribute(string strPath, string strAuthor, string strMail, string strLink, string strDescription) {
if (!string.IsNullOrEmpty(strPath))
strPath = strPath.Trim().Trim(m_ch_splitter).Trim();
this._Path = strPath;
this._Author = strAuthor;
this._Mail = strMail;
this._Description = strDescription;
if (string.IsNullOrEmpty(strLink) || strLink.Trim() == string.Empty) return;
strLink = strLink.Trim();
if (m_reg.IsMatch(strLink))
this._Link = strLink;
else
this._Link = "http://" + strLink;
}
private static Dictionary<Type, MethodInfo> m_dic = new Dictionary<Type, MethodInfo>();
/// <summary>
/// 获取类型的帮助函数
/// </summary>
/// <param name="stNodeType">节点类型</param>
/// <returns>函数信息</returns>
public static MethodInfo GetHelpMethod(Type stNodeType) {
if (m_dic.ContainsKey(stNodeType)) return m_dic[stNodeType];
var mi = stNodeType.GetMethod("ShowHelpInfo");
if (mi == null) return null;
if (!mi.IsStatic) return null;
var ps = mi.GetParameters();
if (ps.Length != 1) return null;
if (ps[0].ParameterType != typeof(string)) return null;
m_dic.Add(stNodeType, mi);
return mi;
}
/// <summary>
/// 执行对应节点类型的帮助函数
/// </summary>
/// <param name="stNodeType">节点类型</param>
public static void ShowHelp(Type stNodeType) {
var mi = STNodeAttribute.GetHelpMethod(stNodeType);
if (mi == null) return;
mi.Invoke(null, new object[] { stNodeType.Module.FullyQualifiedName });
}
}
}

View File

@ -1,329 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace ST.Library.UI.NodeEditor
{
public class STNodeEditorPannel : Control
{
private bool _LeftLayout = true;
/// <summary>
/// 获取或设置是否是左边布局
/// </summary>
[Description("获取或设置是否是左边布局"), DefaultValue(true)]
public bool LeftLayout {
get { return _LeftLayout; }
set {
if (value == _LeftLayout) return;
_LeftLayout = value;
this.SetLocation();
this.Invalidate();
}
}
private Color _SplitLineColor = Color.Black;
/// <summary>
/// 获取或这是分割线颜色
/// </summary>
[Description("获取或这是分割线颜色"), DefaultValue(typeof(Color), "Black")]
public Color SplitLineColor {
get { return _SplitLineColor; }
set { _SplitLineColor = value; }
}
private Color _HandleLineColor = Color.Gray;
/// <summary>
/// 获取或设置分割线手柄颜色
/// </summary>
[Description("获取或设置分割线手柄颜色"), DefaultValue(typeof(Color), "Gray")]
public Color HandleLineColor {
get { return _HandleLineColor; }
set { _HandleLineColor = value; }
}
private bool _ShowScale = true;
/// <summary>
/// 获取或设置编辑器缩放时候显示比例
/// </summary>
[Description("获取或设置编辑器缩放时候显示比例"), DefaultValue(true)]
public bool ShowScale {
get { return _ShowScale; }
set { _ShowScale = value; }
}
private bool _ShowConnectionStatus = true;
/// <summary>
/// 获取或设置节点连线时候是否显示状态
/// </summary>
[Description("获取或设置节点连线时候是否显示状态"), DefaultValue(true)]
public bool ShowConnectionStatus {
get { return _ShowConnectionStatus; }
set { _ShowConnectionStatus = value; }
}
private int _X;
/// <summary>
/// 获取或设置分割线水平宽度
/// </summary>
[Description("获取或设置分割线水平宽度"), DefaultValue(201)]
public int X {
get { return _X; }
set {
if (value < 122) value = 122;
else if (value > this.Width - 122) value = this.Width - 122;
if (_X == value) return;
_X = value;
this.SetLocation();
}
}
private int _Y;
/// <summary>
/// 获取或设置分割线垂直高度
/// </summary>
[Description("获取或设置分割线垂直高度")]
public int Y {
get { return _Y; }
set {
if (value < 122) value = 122;
else if (value > this.Height - 122) value = this.Height - 122;
if (_Y == value) return;
_Y = value;
this.SetLocation();
}
}
/// <summary>
/// 获取面板中的STNodeEditor
/// </summary>
[Description("获取面板中的STNodeEditor"), Browsable(false)]
public STNodeEditor Editor {
get { return m_editor; }
}
/// <summary>
/// 获取面板中的STNodeTreeView
/// </summary>
[Description("获取面板中的STNodeTreeView"), Browsable(false)]
public STNodeTreeView TreeView {
get { return m_tree; }
}
/// <summary>
/// 获取面板中的STNodePropertyGrid
/// </summary>
[Description("获取面板中的STNodePropertyGrid"), Browsable(false)]
public STNodePropertyGrid PropertyGrid {
get { return m_grid; }
}
private Point m_pt_down;
private bool m_is_mx;
private bool m_is_my;
private Pen m_pen;
private bool m_nInited;
private Dictionary<ConnectionStatus, string> m_dic_status_key = new Dictionary<ConnectionStatus, string>();
private STNodeEditor m_editor;
private STNodeTreeView m_tree;
private STNodePropertyGrid m_grid;
public override Size MinimumSize {
get {
return base.MinimumSize;
}
set {
value = new Size(250, 250);
base.MinimumSize = value;
}
}
[DllImport("user32.dll")]
private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int w, int h, bool bRedraw);
public STNodeEditorPannel() {
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
m_editor = new STNodeEditor();
m_tree = new STNodeTreeView();
m_grid = new STNodePropertyGrid();
m_grid.Text = "NodeProperty";
this.Controls.Add(m_editor);
this.Controls.Add(m_tree);
this.Controls.Add(m_grid);
this.Size = new Size(500, 500);
this.MinimumSize = new Size(250, 250);
this.BackColor = Color.FromArgb(255, 34, 34, 34);
m_pen = new Pen(this.BackColor, 3);
Type t = typeof(ConnectionStatus);
var vv = Enum.GetValues(t);
var vvv = vv.GetValue(0);
foreach (var f in t.GetFields()) {
if (!f.FieldType.IsEnum) continue;
foreach (var a in f.GetCustomAttributes(true)) {
if (!(a is DescriptionAttribute)) continue;
m_dic_status_key.Add((ConnectionStatus)f.GetValue(f), ((DescriptionAttribute)a).Description);
}
}
m_editor.ActiveChanged += (s, e) => m_grid.SetNode(m_editor.ActiveNode);
m_editor.CanvasScaled += (s, e) => {
if (this._ShowScale)
m_editor.ShowAlert(m_editor.CanvasScale.ToString("F2"), Color.White, Color.FromArgb(127, 255, 255, 0));
};
m_editor.OptionConnected += (s, e) => {
if (this._ShowConnectionStatus)
m_editor.ShowAlert(m_dic_status_key[e.Status], Color.White, e.Status == ConnectionStatus.Connected ? Color.FromArgb(125, Color.Lime) : Color.FromArgb(125, Color.Red));
};
}
protected override void OnResize(EventArgs e) {
base.OnResize(e);
if (!m_nInited) {
this._Y = this.Height / 2;
if (this._LeftLayout)
this._X = 201;
else
this._X = this.Width - 202;
m_nInited = true;
this.SetLocation();
return;
}
this.SetLocation();
}
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
if (width < 250) width = 250;
if (height < 250) height = 250;
base.SetBoundsCore(x, y, width, height, specified);
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
m_pen.Width = 3;
m_pen.Color = this._SplitLineColor;
g.DrawLine(m_pen, this._X, 0, this._X, this.Height);
int nX = 0;
if (this._LeftLayout) {
g.DrawLine(m_pen, 0, this._Y, this._X - 1, this._Y);
nX = this._X / 2;
} else {
g.DrawLine(m_pen, this._X + 2, this._Y, this.Width, this._Y);
nX = this._X + (this.Width - this._X) / 2;
}
m_pen.Width = 1;
this._HandleLineColor = Color.Gray;
m_pen.Color = this._HandleLineColor;
g.DrawLine(m_pen, this._X, this._Y - 10, this._X, this._Y + 10);
g.DrawLine(m_pen, nX - 10, this._Y, nX + 10, this._Y);
}
private void SetLocation() {
if (this._LeftLayout) {
//m_tree.Location = Point.Empty;
//m_tree.Size = new Size(m_sx - 1, m_sy - 1);
STNodeEditorPannel.MoveWindow(m_tree.Handle, 0, 0, this._X - 1, this._Y - 1, false);
//m_grid.Location = new Point(0, m_sy + 2);
//m_grid.Size = new Size(m_sx - 1, this.Height - m_sy - 2);
STNodeEditorPannel.MoveWindow(m_grid.Handle, 0, this._Y + 2, this._X - 1, this.Height - this._Y - 2, false);
//m_editor.Location = new Point(m_sx + 2, 0);
//m_editor.Size = new Size(this.Width - m_sx - 2, this.Height);
STNodeEditorPannel.MoveWindow(m_editor.Handle, this._X + 2, 0, this.Width - this._X - 2, this.Height, false);
} else {
STNodeEditorPannel.MoveWindow(m_editor.Handle, 0, 0, this._X - 1, this.Height, false);
STNodeEditorPannel.MoveWindow(m_tree.Handle, this._X + 2, 0, this.Width - this._X - 2, this._Y - 1, false);
STNodeEditorPannel.MoveWindow(m_grid.Handle, this._X + 2, this._Y + 2, this.Width - this._X - 2, this.Height - this._Y - 2, false);
}
}
protected override void OnMouseDown(MouseEventArgs e) {
base.OnMouseDown(e);
m_pt_down = e.Location;
m_is_mx = m_is_my = false;
if (this.Cursor == Cursors.VSplit) {
m_is_mx = true;
} else if (this.Cursor == Cursors.HSplit) {
m_is_my = true;
}
}
protected override void OnMouseMove(MouseEventArgs e) {
base.OnMouseMove(e);
if (e.Button == MouseButtons.Left) {
int nw = 122;// (int)(this.Width * 0.1f);
int nh = 122;// (int)(this.Height * 0.1f);
if (m_is_mx) {
this._X = e.X;// -m_pt_down.X;
if (this._X < nw) this._X = nw;
else if (_X + nw > this.Width) this._X = this.Width - nw;
} else if (m_is_my) {
this._Y = e.Y;
if (this._Y < nh) this._Y = nh;
else if (this._Y + nh > this.Height) this._Y = this.Height - nh;
}
//m_rx = this.Width - m_sx;// (float)m_sx / this.Width;
//m_fh = (float)m_sy / this.Height;
this.SetLocation();
this.Invalidate();
return;
}
if (Math.Abs(e.X - this._X) < 2)
this.Cursor = Cursors.VSplit;
else if (Math.Abs(e.Y - this._Y) < 2)
this.Cursor = Cursors.HSplit;
else this.Cursor = Cursors.Arrow;
}
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave(e);
m_is_mx = m_is_my = false;
this.Cursor = Cursors.Arrow;
}
/// <summary>
/// 向树控件中添加一个STNode
/// </summary>
/// <param name="stNodeType">STNode类型</param>
/// <returns>是否添加成功</returns>
public bool AddSTNode(Type stNodeType) {
return m_tree.AddNode(stNodeType);
}
/// <summary>
/// 从程序集中加载STNode
/// </summary>
/// <param name="strFileName">程序集路径</param>
/// <returns>添加成功个数</returns>
public int LoadAssembly(string strFileName) {
m_editor.LoadAssembly(strFileName);
return m_tree.LoadAssembly(strFileName);
}
/// <summary>
/// 设置编辑器显示连接状态的文本
/// </summary>
/// <param name="status">连接状态</param>
/// <param name="strText">对应显示文本</param>
/// <returns>旧文本</returns>
public string SetConnectionStatusText(ConnectionStatus status, string strText) {
string strOld = null;
if (m_dic_status_key.ContainsKey(status)) {
strOld = m_dic_status_key[status];
m_dic_status_key[status] = strText;
return strOld;
}
m_dic_status_key.Add(status, strText);
return strText;
}
}
}

View File

@ -1,334 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
namespace ST.Library.UI.NodeEditor
{
/// <summary>
/// STNode节点属性特性
/// 用于描述STNode节点属性信息 以及在属性编辑器上的行为
/// </summary>
public class STNodePropertyAttribute : Attribute
{
private string _Name;
/// <summary>
/// 获取属性需要在属性编辑器上显示的名称
/// </summary>
public string Name {
get { return _Name; }
}
private string _Description;
/// <summary>
/// 获取属性需要在属性编辑器上显示的描述
/// </summary>
public string Description {
get { return _Description; }
}
private Type _ConverterType = typeof(STNodePropertyDescriptor);
/// <summary>
/// 获取属性描述器类型
/// </summary>
public Type DescriptorType {
get { return _ConverterType; }
set { _ConverterType = value; }
}
/// <summary>
/// 构造一个STNode属性特性
/// </summary>
/// <param name="strKey">需要显示的名称</param>
/// <param name="strDesc">需要显示的描述信息</param>
public STNodePropertyAttribute(string strKey, string strDesc) {
this._Name = strKey;
this._Description = strDesc;
}
//private Type m_descriptor_type_base = typeof(STNodePropertyDescriptor);
}
/// <summary>
/// STNode属性描述器
/// 用于确定在属性编辑器上如何与属性的值进行交互 以及确定属性值在属性编辑器上将如何绘制并交互
/// </summary>
public class STNodePropertyDescriptor
{
/// <summary>
/// 获取目标节点
/// </summary>
public STNode Node { get; internal set; }
/// <summary>
/// 获取所属的节点属性编辑器控件
/// </summary>
public STNodePropertyGrid Control { get; internal set; }
/// <summary>
/// 获取选项所在区域
/// </summary>
public Rectangle Rectangle { get; internal set; }
/// <summary>
/// 获取选项名称所在区域
/// </summary>
public Rectangle RectangleL { get; internal set; }
/// <summary>
/// 获取选项值所在区域
/// </summary>
public Rectangle RectangleR { get; internal set; }
/// <summary>
/// 获取选项需要显示的名称
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// 获取属性对应的描述信息
/// </summary>
public string Description { get; internal set; }
/// <summary>
/// 获取属性信息
/// </summary>
public PropertyInfo PropertyInfo { get; internal set; }
private static Type m_t_int = typeof(int);
private static Type m_t_float = typeof(float);
private static Type m_t_double = typeof(double);
private static Type m_t_string = typeof(string);
private static Type m_t_bool = typeof(bool);
private StringFormat m_sf;
/// <summary>
/// 构造一个描述器
/// </summary>
public STNodePropertyDescriptor() {
m_sf = new StringFormat();
m_sf.LineAlignment = StringAlignment.Center;
m_sf.FormatFlags = StringFormatFlags.NoWrap;
}
/// <summary>
/// 当确定STNode属性在属性编辑器上的位置时候发生
/// </summary>
protected internal virtual void OnSetItemLocation() { }
/// <summary>
/// 将字符串形式的属性值转换为属性目标类型的值
/// 默认只支持 int float double string bool 以及上述类型的Array
/// 若目标类型不在上述中 请重写此函数自行转换
/// </summary>
/// <param name="strText">字符串形式的属性值</param>
/// <returns>属性真实目标类型的值</returns>
protected internal virtual object GetValueFromString(string strText) {
Type t = this.PropertyInfo.PropertyType;
if (t == m_t_int) return int.Parse(strText);
if (t == m_t_float) return float.Parse(strText);
if (t == m_t_double) return double.Parse(strText);
if (t == m_t_string) return strText;
if (t == m_t_bool) return bool.Parse(strText);
if (t.IsEnum) {
return Enum.Parse(t, strText);
} else if (t.IsArray) {
var t_1 = t.GetElementType();
if (t_1 == m_t_string) return strText.Split(',');
string[] strs = strText.Trim(new char[] { ' ', ',' }).Split(',');//add other place trim()
if (t_1 == m_t_int) {
int[] arr = new int[strs.Length];
for (int i = 0; i < strs.Length; i++) arr[i] = int.Parse(strs[i].Trim());
return arr;
}
if (t_1 == m_t_float) {
float[] arr = new float[strs.Length];
for (int i = 0; i < strs.Length; i++) arr[i] = float.Parse(strs[i].Trim());
return arr;
}
if (t_1 == m_t_int) {
double[] arr = new double[strs.Length];
for (int i = 0; i < strs.Length; i++) arr[i] = double.Parse(strs[i].Trim());
return arr;
}
if (t_1 == m_t_int) {
bool[] arr = new bool[strs.Length];
for (int i = 0; i < strs.Length; i++) arr[i] = bool.Parse(strs[i].Trim());
return arr;
}
}
throw new InvalidCastException("无法完成[string]到[" + t.FullName + "]的转换 请重载[STNodePropertyDescriptor.GetValueFromString(string)]");
}
/// <summary>
/// 将属性目标类型的值转换为字符串形式的值
/// 默认对类型值进行 ToString() 操作
/// 如需特殊处理 请重写此函数自行转换
/// </summary>
/// <returns>属性值的字符串形式</returns>
protected internal virtual string GetStringFromValue() {
var v = this.PropertyInfo.GetValue(this.Node, null);
var t = this.PropertyInfo.PropertyType;
if (v == null) return null;
if (t.IsArray) {
List<string> lst = new List<string>();
foreach (var item in (Array)v) lst.Add(item.ToString());
return string.Join(",", lst.ToArray());
}
return v.ToString();
}
/// <summary>
/// 将二进制形式的属性值转换为属性目标类型的值 用于从文件存储中的数据还原属性值
/// 默认将其转换为字符串然后调用 GetValueFromString(string)
/// 此函数与 GetBytesFromValue() 相对应 若需要重写函数应当两个函数一起重写
/// </summary>
/// <param name="byData">二进制数据</param>
/// <returns>属性真实目标类型的值</returns>
protected internal virtual object GetValueFromBytes(byte[] byData) {
if (byData == null) return null;
string strText = Encoding.UTF8.GetString(byData);
return this.GetValueFromString(strText);
}
/// <summary>
/// 将属性目标类型的值转换为二进制形式的值 用于文件存储时候调用
/// 默认调用 GetStringFromValue() 然后将字符串转换为二进制数据
/// 如需特殊处理 请重写此函数自行转换 并且重写 GetValueFromBytes()
/// </summary>
/// <returns>属性值的二进制形式</returns>
protected internal virtual byte[] GetBytesFromValue() {
string strText = this.GetStringFromValue();
if (strText == null) return null;
return Encoding.UTF8.GetBytes(strText);
}
/// <summary>
/// 此函数对应 System.Reflection.PropertyInfo.GetValue()
/// </summary>
/// <param name="index">索引属性的可选索引值 对于非索引属性 此值应为null</param>
/// <returns>属性值</returns>
protected internal virtual object GetValue(object[] index) {
return this.PropertyInfo.GetValue(this.Node, index);
}
/// <summary>
/// 此函数对应 System.Reflection.PropertyInfo.SetValue()
/// </summary>
/// <param name="value">需要设置的属性值</param>
protected internal virtual void SetValue(object value) {
this.PropertyInfo.SetValue(this.Node, value, null);
}
/// <summary>
/// 此函数对应 System.Reflection.PropertyInfo.SetValue()
/// 在调用之前会默认进行 GetValueFromString(strValue) 处理
/// </summary>
/// <param name="strValue">需要设置的属性字符串形式的值</param>
protected internal virtual void SetValue(string strValue) {
this.PropertyInfo.SetValue(this.Node, this.GetValueFromString(strValue), null);
}
/// <summary>
/// 此函数对应 System.Reflection.PropertyInfo.SetValue()
/// 在调用之前会默认进行 GetValueFromBytes(byte[]) 处理
/// </summary>
/// <param name="byData">需要设置的属性二进制数据</param>
protected internal virtual void SetValue(byte[] byData) {
this.PropertyInfo.SetValue(this.Node, this.GetValueFromBytes(byData), null);
}
/// <summary>
/// 此函数对应 System.Reflection.PropertyInfo.SetValue()
/// </summary>
/// <param name="value">需要设置的属性值</param>
/// <param name="index">索引属性的可选索引值 对于非索引属性 此值应为null</param>
protected internal virtual void SetValue(object value, object[] index) {
this.PropertyInfo.SetValue(this.Node, value, index);
}
/// <summary>
/// 此函数对应 System.Reflection.PropertyInfo.SetValue()
/// 在调用之前会默认进行 GetValueFromString(strValue) 处理
/// </summary>
/// <param name="strValue">需要设置的属性字符串形式的值</param>
/// <param name="index">索引属性的可选索引值 对于非索引属性 此值应为null</param>
protected internal virtual void SetValue(string strValue, object[] index) {
this.PropertyInfo.SetValue(this.Node, this.GetValueFromString(strValue), index);
}
/// <summary>
/// 此函数对应 System.Reflection.PropertyInfo.SetValue()
/// 在调用之前会默认进行 GetValueFromBytes(byte[]) 处理
/// </summary>
/// <param name="byData">需要设置的属性二进制数据</param>
/// <param name="index">索引属性的可选索引值 对于非索引属性 此值应为null</param>
protected internal virtual void SetValue(byte[] byData, object[] index) {
this.PropertyInfo.SetValue(this.Node, this.GetValueFromBytes(byData), index);
}
/// <summary>
/// 当设置属性值发生错误时候发生
/// </summary>
/// <param name="ex">异常信息</param>
protected internal virtual void OnSetValueError(Exception ex) {
this.Control.SetErrorMessage(ex.Message);
}
/// <summary>
/// 当绘制属性在属性编辑器上的值所在区域时候发生
/// </summary>
/// <param name="dt">绘制工具</param>
protected internal virtual void OnDrawValueRectangle(DrawingTools dt) {
Graphics g = dt.Graphics;
SolidBrush brush = dt.SolidBrush;
STNodePropertyGrid ctrl = this.Control;
//STNodePropertyItem item = this._PropertyItem;
brush.Color = ctrl.ItemValueBackColor;
g.FillRectangle(brush, this.RectangleR);
Rectangle rect = this.RectangleR;
rect.Width--; rect.Height--;
brush.Color = this.Control.ForeColor;
g.DrawString(this.GetStringFromValue(), ctrl.Font, brush, this.RectangleR, m_sf);
if (this.PropertyInfo.PropertyType.IsEnum || this.PropertyInfo.PropertyType == m_t_bool) {
g.FillPolygon(Brushes.Gray, new Point[]{
new Point(rect.Right - 13, rect.Top + rect.Height / 2 - 2),
new Point(rect.Right - 4, rect.Top + rect.Height / 2 - 2),
new Point(rect.Right - 9, rect.Top + rect.Height / 2 + 3)
});
}
}
/// <summary>
/// 当鼠标进入属性值所在区域时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected internal virtual void OnMouseEnter(EventArgs e) { }
/// <summary>
/// 当鼠标在属性值所在区域点击时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected internal virtual void OnMouseDown(MouseEventArgs e) {
}
/// <summary>
/// 当鼠标在属性值所在区域移动时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected internal virtual void OnMouseMove(MouseEventArgs e) { }
/// <summary>
/// 当鼠标在属性值所在区域抬起时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected internal virtual void OnMouseUp(MouseEventArgs e) { }
/// <summary>
/// 当鼠标在属性值所在区域离开时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected internal virtual void OnMouseLeave(EventArgs e) { }
/// <summary>
/// 当鼠标在属性值所在区域点击时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected internal virtual void OnMouseClick(MouseEventArgs e) {
Type t = this.PropertyInfo.PropertyType;
if (t == m_t_bool || t.IsEnum) {
new FrmSTNodePropertySelect(this).Show(this.Control);
return;
}
Rectangle rect = this.Control.RectangleToScreen(this.RectangleR);
new FrmSTNodePropertyInput(this).Show(this.Control);
}
/// <summary>
/// 重绘选项区域
/// </summary>
public void Invalidate() {
Rectangle rect = this.Rectangle;
rect.X -= this.Control.ScrollOffset;
this.Control.Invalidate(rect);
}
}
}

View File

@ -1,860 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using System.ComponentModel;
/*
MIT License
Copyright (c) 2021 DebugST@crystal_lz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
* create: 2021-01-28
* modify: 2021-03-02
* Author: Crystal_lz
* blog: http://st233.com
* Gitee: https://gitee.com/DebugST
* Github: https://github.com/DebugST
*/
namespace ST.Library.UI.NodeEditor
{
/// <summary>
/// STNode节点属性编辑器
/// </summary>
public class STNodePropertyGrid : Control
{
#region properties ==========
private STNode _STNode;
/// <summary>
/// 当前显示的STNode
/// </summary>
[Description("当前显示的STNode"), Browsable(false)]
public STNode STNode {
get { return _STNode; }
}
private Color _ItemHoverColor = Color.FromArgb(50, 125, 125, 125);
/// <summary>
/// 获取或设置属性选项被鼠标悬停时候背景色
/// </summary>
[Description("获取或设置属性选项被鼠标悬停时候背景色")]
public Color ItemHoverColor {
get { return _ItemHoverColor; }
set { _ItemHoverColor = value; }
}
private Color _ItemSelectedColor = Color.DodgerBlue;
/// <summary>
/// 获取或设置属性选项被选中时候背景色 当AutoColor被设置时此属性不能被设置
/// </summary>
[Description("获取或设置属性选项被选中时候背景色 当AutoColor被设置时此属性不能被设置"), DefaultValue(typeof(Color), "DodgerBlue")]
public Color ItemSelectedColor {
get { return _ItemSelectedColor; }
set {
if (this._AutoColor) return;
if (value == _ItemSelectedColor) return;
_ItemSelectedColor = value;
this.Invalidate();
}
}
private Color _ItemValueBackColor = Color.FromArgb(255, 80, 80, 80);
/// <summary>
/// 获取或设置属性选项值背景色
/// </summary>
[Description("获取或设置属性选项值背景色")]
public Color ItemValueBackColor {
get { return _ItemValueBackColor; }
set {
_ItemValueBackColor = value;
this.Invalidate();
}
}
private Color _TitleColor = Color.FromArgb(127, 0, 0, 0);
/// <summary>
/// 获取或设置默认标题背景色
/// </summary>
[Description("获取或设置默认标题背景色")]
public Color TitleColor {
get { return _TitleColor; }
set {
_TitleColor = value;
if (!this._ShowTitle) return;
this.Invalidate(m_rect_title);
}
}
private Color _ErrorColor = Color.FromArgb(200, Color.Brown);
/// <summary>
/// 获取或设置属性设置错误时候提示信息背景色
/// </summary>
[Description("获取或设置属性设置错误时候提示信息背景色")]
public Color ErrorColor {
get { return _ErrorColor; }
set { _ErrorColor = value; }
}
private Color _DescriptionColor = Color.FromArgb(200, Color.DarkGoldenrod);
/// <summary>
/// 获取或设置属性描述信息背景色
/// </summary>
[Description("获取或设置属性描述信息背景色")]
public Color DescriptionColor {
get { return _DescriptionColor; }
set { _DescriptionColor = value; }
}
private bool _ShowTitle = true;
/// <summary>
/// 获取或设置是否显示节点标题
/// </summary>
[Description("获取或设置是否显示节点标题")]
public bool ShowTitle {
get { return _ShowTitle; }
set {
_ShowTitle = value;
this.SetItemRectangle();
this.Invalidate();
}
}
private bool _AutoColor = true;
/// <summary>
/// 获取或设置是否根据STNode自动设置控件高亮颜色
/// </summary>
[Description("获取或设置是否根据STNode自动设置控件高亮颜色"), DefaultValue(true)]
public bool AutoColor {
get { return _AutoColor; }
set { _AutoColor = value; }
}
private bool _InfoFirstOnDraw;
/// <summary>
/// 获取或当节点被设置时候 是否优先绘制信息面板
/// </summary>
[Description("获取或设置当节点被设置时候 是否优先绘制信息面板"), DefaultValue(false)]
public bool InfoFirstOnDraw {
get { return _InfoFirstOnDraw; }
set { _InfoFirstOnDraw = value; }
}
private bool _ReadOnlyModel;
/// <summary>
/// 获取或设置当前属性编辑器是否处于只读模式
/// </summary>
[Description("获取或设置当前属性编辑器是否处于只读模式"), DefaultValue(false)]
public bool ReadOnlyModel {
get { return _ReadOnlyModel; }
set {
if (value == _ReadOnlyModel) return;
_ReadOnlyModel = value;
this.Invalidate(m_rect_title);
}
}
/// <summary>
/// 获取当前滚动条高度
/// </summary>
[Description("获取当前滚动条高度")]
public int ScrollOffset { get { return m_nOffsetY; } }
#endregion
#region protected fields ==========
/// <summary>
/// 作者链接地址区域
/// </summary>
protected Rectangle m_rect_link;
/// <summary>
/// 查看帮助按钮区域
/// </summary>
protected Rectangle m_rect_help;
/// <summary>
/// 编辑器标题区域
/// </summary>
protected Rectangle m_rect_title;
/// <summary>
/// 面板切换按钮区域
/// </summary>
protected Rectangle m_rect_switch;
/// <summary>
/// 控件在绘制过程中使用的垂直滚动偏移
/// </summary>
protected int m_nOffsetY;
/// <summary>
/// 保存的信息面板垂直滚动偏移
/// </summary>
protected int m_nInfoOffsetY;
/// <summary>
/// 保存的属性面板垂直滚动偏移
/// </summary>
protected int m_nPropertyOffsetY;
/// <summary>
/// 控件在绘制过程中使用的绘图区域总高度
/// </summary>
protected int m_nVHeight;
/// <summary>
/// 保存的信息面板需要的总高度
/// </summary>
protected int m_nInfoVHeight;
/// <summary>
/// 保存的属性面板需要的总高度
/// </summary>
protected int m_nPropertyVHeight;
/// <summary>
/// 信息面板中Key显示需要的水平宽度
/// </summary>
protected int m_nInfoLeft;
#endregion
private Type m_type;
private string[] m_KeysString = new string[] { "作者", "邮箱", "链接", "查看帮助" };
private int m_nTitleHeight = 20;
private int m_item_height = 30;
private Color m_clr_item_1 = Color.FromArgb(10, 0, 0, 0);
private Color m_clr_item_2 = Color.FromArgb(10, 255, 255, 255);
//所有属性列表保存在此List中
private List<STNodePropertyDescriptor> m_lst_item = new List<STNodePropertyDescriptor>();
private STNodePropertyDescriptor m_item_hover; //当前被鼠标悬停的选项
private STNodePropertyDescriptor m_item_hover_value; //当前值区域被鼠标悬停的选项
private STNodePropertyDescriptor m_item_down_value; //当前值区域被鼠标点击的选项
private STNodePropertyDescriptor m_item_selected; //当前选中的选项
private STNodeAttribute m_node_attribute; //节点参数信息
private bool m_b_hover_switch; //是否鼠标悬停在面板切换按钮上
private bool m_b_current_draw_info; //当前绘制的时候是信息面板
private Point m_pt_move; //鼠标在控件上的实时坐标
private Point m_pt_down; //上次鼠标在控件上点下的坐标
private string m_str_err; //当被设置时 绘制错误信息
private string m_str_desc; //当被设置时 绘制描述信息
private Pen m_pen;
private SolidBrush m_brush;
private StringFormat m_sf;
private DrawingTools m_dt;
/// <summary>
/// 构造一个节点属性编辑器
/// </summary>
public STNodePropertyGrid() {
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
m_pen = new Pen(Color.Black, 1);
m_brush = new SolidBrush(Color.Black);
m_sf = new StringFormat();
m_sf.LineAlignment = StringAlignment.Center;
m_sf.FormatFlags = StringFormatFlags.NoWrap;
m_dt.Pen = m_pen;
m_dt.SolidBrush = m_brush;
this.ForeColor = Color.White;
this.BackColor = Color.FromArgb(255, 35, 35, 35);
this.MinimumSize = new Size(120, 50);
this.Size = new Size(200, 150);
}
#region private method ==========
private List<STNodePropertyDescriptor> GetProperties(STNode node) {
List<STNodePropertyDescriptor> lst = new List<STNodePropertyDescriptor>();
if (node == null) return lst;
Type t = node.GetType();
foreach (var p in t.GetProperties()) {
var attrs = p.GetCustomAttributes(true);
foreach (var a in attrs) {
if (!(a is STNodePropertyAttribute)) continue;
var attr = a as STNodePropertyAttribute;
object obj = Activator.CreateInstance(attr.DescriptorType);
if (!(obj is STNodePropertyDescriptor))
throw new ArgumentException("[STNodePropertyAttribute.DescriptorType]参数值必须为[STNodePropertyDescriptor]或者其子类的类型");
var desc = (STNodePropertyDescriptor)Activator.CreateInstance(attr.DescriptorType);
desc.Node = node;
desc.Name = attr.Name;
desc.Description = attr.Description;
desc.PropertyInfo = p;
desc.Control = this;
lst.Add(desc);
}
}
return lst;
}
private STNodeAttribute GetNodeAttribute(STNode node) {
if (node == null) return null;
Type t = node.GetType();
foreach (var v in t.GetCustomAttributes(true)) {
if (!(v is STNodeAttribute)) continue;
return (STNodeAttribute)v;
}
return null;
}
private void SetItemRectangle() {
int nw_p = 0, nw_h = 0;
using (Graphics g = this.CreateGraphics()) {
foreach (var v in m_lst_item) {
SizeF szf = g.MeasureString(v.Name, this.Font);
if (szf.Width > nw_p) nw_p = (int)Math.Ceiling(szf.Width);
}
for (int i = 0; i < m_KeysString.Length - 1; i++) {
SizeF szf = g.MeasureString(m_KeysString[i], this.Font);
if (szf.Width > nw_h) nw_h = (int)Math.Ceiling(szf.Width);
}
nw_p += 5; nw_h += 5;
nw_p = Math.Min(nw_p, this.Width >> 1);
m_nInfoLeft = Math.Min(nw_h, this.Width >> 1);
int nTitleHeight = this._ShowTitle ? m_nTitleHeight : 0;
for (int i = 0; i < m_lst_item.Count; i++) {
STNodePropertyDescriptor item = m_lst_item[i];
Rectangle rect = new Rectangle(0, i * m_item_height + nTitleHeight, this.Width, m_item_height);
item.Rectangle = rect;
rect.Width = nw_p;
item.RectangleL = rect;
rect.X = rect.Right;
rect.Width = this.Width - rect.Left - 1;
rect.Inflate(-4, -4);
item.RectangleR = rect;
item.OnSetItemLocation();
}
m_nPropertyVHeight = m_lst_item.Count * m_item_height;
if (this._ShowTitle) m_nPropertyVHeight += m_nTitleHeight;
}
}
#endregion
#region override ==========
/// <summary>
/// 当控件重绘时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
m_dt.Graphics = g;
m_nOffsetY = m_b_current_draw_info ? m_nInfoOffsetY : m_nPropertyOffsetY;
g.TranslateTransform(0, m_nOffsetY);
if (m_b_current_draw_info) {
m_nVHeight = m_nInfoVHeight;
this.OnDrawInfo(m_dt);
} else {
m_nVHeight = m_nPropertyVHeight;
for (int i = 0; i < m_lst_item.Count; i++) {
this.OnDrawPropertyItem(m_dt, m_lst_item[i], i);
}
}
g.ResetTransform();
if (this._ShowTitle) this.OnDrawTitle(m_dt);
m_sf.FormatFlags = 0;
if (!string.IsNullOrEmpty(m_str_err)) this.OnDrawErrorInfo(m_dt);
if (!string.IsNullOrEmpty(m_str_desc)) this.OnDrawDescription(m_dt);
}
/// <summary>
/// 当鼠标在控件上移动时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected override void OnMouseMove(MouseEventArgs e) {
base.OnMouseMove(e);
m_pt_move = e.Location;
bool bHover = this._ShowTitle && m_rect_switch.Contains(e.Location);
if (bHover != m_b_hover_switch) {
m_b_hover_switch = bHover;
this.Invalidate(m_rect_switch);
}
Point pt = new Point(e.X, e.Y - (int)m_nOffsetY);
MouseEventArgs mea = new MouseEventArgs(e.Button, e.Clicks, pt.X, pt.Y, e.Delta);
if (m_b_current_draw_info)
this.OnProcessHelpMouseMove(mea);
else
this.OnProcessPropertyMouseMove(mea);
}
/// <summary>
/// 当鼠标在控件上点下时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected override void OnMouseDown(MouseEventArgs e) {
base.OnMouseDown(e);
m_pt_down = e.Location;
this.Focus();
bool bRedraw = false;
if (m_str_err != null) {
bRedraw = true;
m_str_err = null;
}
if (this._ShowTitle) {
if (m_rect_switch.Contains(e.Location)) {
if (m_node_attribute == null || m_lst_item.Count == 0) return;
m_b_current_draw_info = !m_b_current_draw_info;
this.Invalidate();
return;
} else if (m_rect_title.Contains(e.Location)) {
return;
}
}
if (this._ShowTitle && m_rect_switch.Contains(e.Location)) {
if (m_node_attribute == null || m_lst_item.Count == 0) return;
m_b_current_draw_info = !m_b_current_draw_info;
this.Invalidate();
return;
}
Point pt = new Point(e.X, e.Y - (int)m_nOffsetY);
MouseEventArgs mea = new MouseEventArgs(e.Button, e.Clicks, pt.X, pt.Y, e.Delta);
if (m_b_current_draw_info)
this.OnProcessInfoMouseDown(mea);
else
this.OnProcessPropertyMouseDown(mea);
if (bRedraw) this.Invalidate();
}
/// <summary>
/// 当鼠标在控件上抬起时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected override void OnMouseUp(MouseEventArgs e) {
base.OnMouseUp(e);
m_str_desc = null;
if (m_item_down_value != null && !this._ReadOnlyModel) {
Point pt = new Point(e.X, e.Y - (int)m_nOffsetY);
MouseEventArgs mea = new MouseEventArgs(e.Button, e.Clicks, pt.X, pt.Y, e.Delta);
m_item_down_value.OnMouseUp(mea);
if (m_pt_down == e.Location && !this._ReadOnlyModel) {
m_item_down_value.OnMouseClick(mea);
}
}
m_item_down_value = null;
this.Invalidate();
}
/// <summary>
/// 当鼠标离开控件时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave(e);
m_b_hover_switch = false;
if (m_item_hover_value != null && !this._ReadOnlyModel) m_item_hover_value.OnMouseLeave(e);
m_item_hover = null;
this.Invalidate();
}
/// <summary>
/// 当鼠标在控件上滚动滚轮时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected override void OnMouseWheel(MouseEventArgs e) {
base.OnMouseWheel(e);
if (e.Delta > 0) {
if (m_nOffsetY == 0) return;
m_nOffsetY += m_item_height;
if (m_nOffsetY > 0) m_nOffsetY = 0;
} else {
if (this.Height - m_nOffsetY >= m_nVHeight) return;
m_nOffsetY -= m_item_height;
}
if (m_b_current_draw_info)
m_nInfoOffsetY = m_nOffsetY;
else
m_nPropertyOffsetY = m_nOffsetY;
this.Invalidate();
}
/// <summary>
/// 当设置控件矩形区域时候发生
/// </summary>
/// <param name="x">x坐标</param>
/// <param name="y">y坐标</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="specified">指定需要设置的标识</param>
//protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
// if (width < 120) width = 120;
// if (height < 50) height = 50;
// base.SetBoundsCore(x, y, width, height, specified);
//}
/// <summary>
/// 当控件尺寸发生改变时候发生
/// </summary>
/// <param name="e">事件参数</param>
protected override void OnResize(EventArgs e) {
base.OnResize(e);
m_rect_title.Width = this.Width;
m_rect_title.Height = m_nTitleHeight;
if (this._ShowTitle)
m_rect_switch = new Rectangle(this.Width - m_nTitleHeight + 2, 2, m_nTitleHeight - 4, m_nTitleHeight - 4);
if (this._STNode != null) this.SetItemRectangle();
}
#endregion
#region virtual method ==========
/// <summary>
/// 当绘制属性选项时候发生
/// </summary>
/// <param name="dt">绘制工具</param>
/// <param name="item">目标属性选项描述器</param>
/// <param name="nIndex">选项所在索引</param>
protected virtual void OnDrawPropertyItem(DrawingTools dt, STNodePropertyDescriptor item, int nIndex) {
Graphics g = dt.Graphics;
m_brush.Color = (nIndex % 2 == 0) ? m_clr_item_1 : m_clr_item_2;
g.FillRectangle(m_brush, item.Rectangle);
if (item == m_item_hover || item == m_item_selected) {
m_brush.Color = this._ItemHoverColor;
g.FillRectangle(m_brush, item.Rectangle);
}
if (m_item_selected == item) {
g.FillRectangle(m_brush, item.Rectangle.X, item.Rectangle.Y, 5, item.Rectangle.Height);
if (this._AutoColor && this._STNode != null)
m_brush.Color = this._STNode.TitleColor;
else
m_brush.Color = this._ItemSelectedColor;
g.FillRectangle(m_brush, item.Rectangle.X, item.Rectangle.Y + 4, 5, item.Rectangle.Height - 8);
}
m_sf.Alignment = StringAlignment.Far;
m_brush.Color = this.ForeColor;
g.DrawString(item.Name, this.Font, m_brush, item.RectangleL, m_sf);
item.OnDrawValueRectangle(m_dt);
if (this._ReadOnlyModel) {
m_brush.Color = Color.FromArgb(125, 125, 125, 125);
g.FillRectangle(m_brush, item.RectangleR);
m_pen.Color = this.ForeColor;
//g.DrawLine(m_pen,
// item.RectangleR.Left - 2, item.RectangleR.Top + item.RectangleR.Height / 2,
// item.RectangleR.Right + 1, item.RectangleR.Top + item.RectangleR.Height / 2);
}
}
/// <summary>
/// 绘制属性窗口标题
/// </summary>
/// <param name="dt">绘制工具</param>
protected virtual void OnDrawTitle(DrawingTools dt) {
Graphics g = dt.Graphics;
if (this._AutoColor)
m_brush.Color = this._STNode == null ? this._TitleColor : this._STNode.TitleColor;
else
m_brush.Color = this._TitleColor;
g.FillRectangle(m_brush, m_rect_title);
m_brush.Color = this._STNode == null ? this.ForeColor : this._STNode.ForeColor;
m_sf.Alignment = StringAlignment.Center;
g.DrawString(this._STNode == null ? this.Text : this._STNode.Title, this.Font, m_brush, m_rect_title, m_sf);
if (this._ReadOnlyModel) {
m_brush.Color = this.ForeColor;
g.FillRectangle(dt.SolidBrush, 4, 5, 2, 4);
g.FillRectangle(dt.SolidBrush, 6, 5, 2, 2);
g.FillRectangle(dt.SolidBrush, 8, 5, 2, 4);
g.FillRectangle(dt.SolidBrush, 3, 9, 8, 6);
}
//是否绘制面板切换按钮
if (m_node_attribute == null || m_lst_item.Count == 0) return;
if (m_b_hover_switch) {
m_brush.Color = this.BackColor;
g.FillRectangle(m_brush, m_rect_switch);
}
m_pen.Color = this._STNode == null ? this.ForeColor : this._STNode.ForeColor;
m_brush.Color = m_pen.Color;
int nT1 = m_rect_switch.Top + m_rect_switch.Height / 2 - 2;
int nT2 = m_rect_switch.Top + m_rect_switch.Height / 2 + 1;
g.DrawRectangle(m_pen, m_rect_switch.Left, m_rect_switch.Top, m_rect_switch.Width - 1, m_rect_switch.Height - 1);
g.DrawLines(m_pen, new Point[]{
new Point(m_rect_switch.Left + 2, nT1), new Point(m_rect_switch.Right - 3, nT1),
new Point(m_rect_switch.Left + 3, nT1 - 1), new Point(m_rect_switch.Right - 3, nT1 - 1)
});
g.DrawLines(m_pen, new Point[]{
new Point(m_rect_switch.Left + 2, nT2), new Point(m_rect_switch.Right - 3, nT2),
new Point(m_rect_switch.Left + 2, nT2 + 1), new Point(m_rect_switch.Right - 4, nT2 + 1),
});
g.FillPolygon(m_brush, new Point[]{
new Point(m_rect_switch.Left + 2, nT1),
new Point(m_rect_switch.Left + 7, nT1),
new Point(m_rect_switch.Left + 7, m_rect_switch.Top ),
});
g.FillPolygon(m_brush, new Point[]{
new Point(m_rect_switch.Right - 2, nT2),
new Point(m_rect_switch.Right - 7, nT2),
new Point(m_rect_switch.Right - 7, m_rect_switch.Bottom - 2 ),
});
}
/// <summary>
/// 当需要绘制属性描述信息时发生
/// </summary>
/// <param name="dt">绘制工具</param>
protected virtual void OnDrawDescription(DrawingTools dt) {
if (string.IsNullOrEmpty(m_str_desc)) return;
Graphics g = dt.Graphics;
SizeF szf = g.MeasureString(m_str_desc, this.Font, this.Width - 4);
Rectangle rect_desc = new Rectangle(0, this.Height - (int)szf.Height - 4, this.Width, (int)szf.Height + 4);
m_brush.Color = this._DescriptionColor;
g.FillRectangle(m_brush, rect_desc);
m_pen.Color = this._DescriptionColor;
g.DrawRectangle(m_pen, 0, rect_desc.Top, rect_desc.Width - 1, rect_desc.Height - 1);
rect_desc.Inflate(-4, 0);
m_brush.Color = this.ForeColor;
m_sf.Alignment = StringAlignment.Near;
g.DrawString(m_str_desc, this.Font, m_brush, rect_desc, m_sf);
}
/// <summary>
/// 当需要绘制错误信息时发生
/// </summary>
/// <param name="dt">绘制工具</param>
protected virtual void OnDrawErrorInfo(DrawingTools dt) {
if (string.IsNullOrEmpty(m_str_err)) return;
Graphics g = dt.Graphics;
SizeF szf = g.MeasureString(m_str_err, this.Font, this.Width - 4);
Rectangle rect_desc = new Rectangle(0, 0, this.Width, (int)szf.Height + 4);
m_brush.Color = this._ErrorColor;
g.FillRectangle(m_brush, rect_desc);
m_pen.Color = this._ErrorColor;
g.DrawRectangle(m_pen, 0, rect_desc.Top, rect_desc.Width - 1, rect_desc.Height - 1);
rect_desc.Inflate(-4, 0);
m_brush.Color = this.ForeColor;
m_sf.Alignment = StringAlignment.Near;
g.DrawString(m_str_err, this.Font, m_brush, rect_desc, m_sf);
}
/// <summary>
/// 当绘制节点信息时候发生
/// </summary>
/// <param name="dt">绘制工具</param>
protected virtual void OnDrawInfo(DrawingTools dt) {
if (m_node_attribute == null) return;
var attr = m_node_attribute;
Graphics g = dt.Graphics;
Color clr_r = Color.FromArgb(this.ForeColor.A / 2, this.ForeColor);
m_sf.Alignment = StringAlignment.Near;
Rectangle rect = new Rectangle(0, this._ShowTitle ? m_nTitleHeight : 0, this.Width, m_item_height);
Rectangle rect_l = new Rectangle(2, rect.Top, m_nInfoLeft - 2, m_item_height);
Rectangle rect_r = new Rectangle(m_nInfoLeft, rect.Top, this.Width - m_nInfoLeft, m_item_height);
m_brush.Color = m_clr_item_2;
g.FillRectangle(m_brush, rect);
m_brush.Color = this.ForeColor;
m_sf.FormatFlags = StringFormatFlags.NoWrap;
m_sf.Alignment = StringAlignment.Near;
g.DrawString(m_KeysString[0], this.Font, m_brush, rect_l, m_sf); //author
m_brush.Color = clr_r;
g.DrawString(attr.Author, this.Font, m_brush, rect_r, m_sf);
rect.Y += m_item_height; rect_l.Y += m_item_height; rect_r.Y += m_item_height;
m_brush.Color = m_clr_item_1;
g.FillRectangle(m_brush, rect);
m_brush.Color = this.ForeColor;
g.DrawString(m_KeysString[1], this.Font, m_brush, rect_l, m_sf); //mail
m_brush.Color = clr_r;
g.DrawString(attr.Mail, this.Font, m_brush, rect_r, m_sf);
rect.Y += m_item_height; rect_l.Y += m_item_height; rect_r.Y += m_item_height;
m_brush.Color = m_clr_item_2;
g.FillRectangle(m_brush, rect);
m_brush.Color = this.ForeColor;
g.DrawString(m_KeysString[2], this.Font, m_brush, rect_l, m_sf); //link_key
m_brush.Color = clr_r;
g.DrawString(attr.Link, this.Font, Brushes.CornflowerBlue, rect_r, m_sf); //link
if (!string.IsNullOrEmpty(attr.Link)) m_rect_link = rect_r;
//fill left
m_brush.Color = Color.FromArgb(40, 125, 125, 125);
g.FillRectangle(m_brush, 0, this._ShowTitle ? m_nTitleHeight : 0, m_nInfoLeft - 1, m_item_height * 3);
rect.X = 5; rect.Y += m_item_height;
rect.Width = this.Width - 10;
if (!string.IsNullOrEmpty(m_node_attribute.Description)) {
float h = g.MeasureString(m_node_attribute.Description, this.Font, rect.Width).Height;
rect.Height = (int)Math.Ceiling(h / m_item_height) * m_item_height;
m_brush.Color = clr_r;
m_sf.FormatFlags = 0;
g.DrawString(m_node_attribute.Description, this.Font, m_brush, rect, m_sf);
}
m_nInfoVHeight = rect.Bottom;
bool bHasHelp = STNodeAttribute.GetHelpMethod(m_type) != null;
rect.X = 5; rect.Y += rect.Height;
rect.Height = m_item_height;
m_sf.Alignment = StringAlignment.Center;
m_brush.Color = Color.FromArgb(125, 125, 125, 125);
g.FillRectangle(m_brush, rect);
if (bHasHelp) m_brush.Color = Color.CornflowerBlue;
g.DrawString(m_KeysString[3], this.Font, m_brush, rect, m_sf);
if (bHasHelp) m_rect_help = rect;
else {
int w = (int)g.MeasureString(m_KeysString[3], this.Font).Width + 1;
int x = rect.X + (rect.Width - w) / 2, y = rect.Y + rect.Height / 2;
m_pen.Color = m_brush.Color;
g.DrawLine(m_pen, x, y, x + w, y);
}
m_nInfoVHeight = rect.Bottom;
}
/// <summary>
/// 当在属性面板鼠标点下时候发生
/// </summary>
/// <param name="e">鼠标事件参数</param>
protected virtual void OnProcessPropertyMouseDown(MouseEventArgs e) {
bool bRedraw = false;
if (m_item_selected != m_item_hover) {
m_item_selected = m_item_hover;
bRedraw = true;
}
m_item_down_value = null;
if (m_item_selected == null) {
if (bRedraw) this.Invalidate();
return;
}
if (m_item_selected.RectangleR.Contains(e.Location)) {
m_item_down_value = m_item_selected;
if (!this._ReadOnlyModel)
m_item_selected.OnMouseDown(e);
else {
return;
}
} else if (m_item_selected.RectangleL.Contains(e.Location)) {
m_str_desc = m_item_selected.Description;
bRedraw = true;
}
if (bRedraw) this.Invalidate();
}
/// <summary>
/// 当在信息面板鼠标点下时候发生
/// </summary>
/// <param name="e">鼠标事件参数</param>
protected virtual void OnProcessInfoMouseDown(MouseEventArgs e) {
try {
if (m_rect_link.Contains(e.Location)) {
System.Diagnostics.Process.Start(m_node_attribute.Link);
} else if (m_rect_help.Contains(e.Location)) {
STNodeAttribute.ShowHelp(m_type);
}
} catch (Exception ex) {
this.SetErrorMessage(ex.Message);
}
}
/// <summary>
/// 当在属性面板鼠标移动时候发生
/// </summary>
/// <param name="e">鼠标事件参数</param>
protected virtual void OnProcessPropertyMouseMove(MouseEventArgs e) {
if (m_item_down_value != null) {
m_item_down_value.OnMouseMove(e);
return;
}
STNodePropertyDescriptor item = null;
foreach (var v in m_lst_item) {
if (v.Rectangle.Contains(e.Location)) {
item = v;
break;
}
}
if (item != null) {
if (item.RectangleR.Contains(e.Location)) {
if (m_item_hover_value != item) {
if (m_item_hover_value != null) m_item_hover_value.OnMouseLeave(e);
m_item_hover_value = item;
m_item_hover_value.OnMouseEnter(e);
}
m_item_hover_value.OnMouseMove(e);
} else {
if (m_item_hover_value != null) m_item_hover_value.OnMouseLeave(e);
}
}
if (m_item_hover != item) {
m_item_hover = item;
this.Invalidate();
}
}
/// <summary>
/// 当在信息面板鼠标移动时候发生
/// </summary>
/// <param name="e">鼠标事件参数</param>
protected virtual void OnProcessHelpMouseMove(MouseEventArgs e) {
if (m_rect_link.Contains(e.Location) || m_rect_help.Contains(e.Location)) {
this.Cursor = Cursors.Hand;
} else this.Cursor = Cursors.Arrow;
}
#endregion
#region public ==========
/// <summary>
/// 设置需要显示的STNode节点
/// </summary>
/// <param name="node">目标节点</param>
public void SetNode(STNode node) {
if (node == this._STNode) return;
m_nInfoOffsetY = m_nPropertyOffsetY = 0;
m_nInfoVHeight = m_nPropertyVHeight = 0;
m_rect_link = m_rect_help = Rectangle.Empty;
m_str_desc = m_str_err = null;
this._STNode = node;
if (node != null) {
m_type = node.GetType();
m_lst_item = this.GetProperties(node);
m_node_attribute = this.GetNodeAttribute(node);
this.SetItemRectangle();
m_b_current_draw_info = m_lst_item.Count == 0 || this._InfoFirstOnDraw;
if (this._AutoColor) this._ItemSelectedColor = this._STNode.TitleColor;
} else {
m_type = null;
m_lst_item.Clear();
m_node_attribute = null;
}
this.Invalidate();
}
/// <summary>
/// 设置信息页面Key的显示文本
/// </summary>
/// <param name="strAuthor">作者</param>
/// <param name="strMail">邮箱</param>
/// <param name="strLink">连接</param>
/// <param name="strHelp">查看帮助</param>
public void SetInfoKey(string strAuthor, string strMail, string strLink, string strHelp) {
m_KeysString = new string[] { strAuthor, strMail, strLink, strHelp };
}
/// <summary>
/// 设置要显示的错误信息
/// </summary>
/// <param name="strText">错误信息</param>
public void SetErrorMessage(string strText) {
m_str_err = strText;
this.Invalidate();
}
#endregion
}
}

View File

@ -1,907 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections;
/*
MIT License
Copyright (c) 2021 DebugST@crystal_lz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
* create: 2021-02-23
* modify: 2021-03-02
* Author: Crystal_lz
* blog: http://st233.com
* Gitee: https://gitee.com/DebugST
* Github: https://github.com/DebugST
*/
namespace ST.Library.UI.NodeEditor
{
public class STNodeTreeView : Control
{
private Color _ItemBackColor = Color.FromArgb(255, 45, 45, 45);
/// <summary>
/// 获取或设置每行属性选项背景色
/// </summary>
[Description("获取或设置每行属性选项背景色")]
public Color ItemBackColor {
get { return _ItemBackColor; }
set {
_ItemBackColor = value;
}
}
private Color _ItemHoverColor = Color.FromArgb(50, 125, 125, 125);
/// <summary>
/// 获取或设置属性选项被鼠标悬停时候背景色
/// </summary>
[Description("获取或设置属性选项被鼠标悬停时候背景色")]
public Color ItemHoverColor {
get { return _ItemHoverColor; }
set { _ItemHoverColor = value; }
}
private Color _TitleColor = Color.FromArgb(255, 60, 60, 60);
/// <summary>
/// 获取或设置顶部检索区域背景色
/// </summary>
[Description("获取或设置顶部检索区域背景颜色")]
public Color TitleColor {
get { return _TitleColor; }
set {
_TitleColor = value;
this.Invalidate(new Rectangle(0, 0, this.Width, m_nItemHeight));
}
}
/// <summary>
/// 获取或设置检索文本框的背景色
/// </summary>
[Description("获取或设置检索文本框的背景色")]
public Color TextBoxColor {
get { return m_tbx.BackColor; }
set {
m_tbx.BackColor = value;
this.Invalidate(new Rectangle(0, 0, this.Width, m_nItemHeight));
}
}
private Color _HightLightTextColor = Color.Lime;
/// <summary>
/// 获取或设置检索时候高亮文本颜色
/// </summary>
[Description("获取或设置检索时候高亮文本颜色"), DefaultValue(typeof(Color), "Lime")]
public Color HightLightTextColor {
get { return _HightLightTextColor; }
set { _HightLightTextColor = value; }
}
private Color _InfoButtonColor = Color.Gray;
/// <summary>
/// 获取或设置信息显示按钮颜色 若设置AutoColor无法设置此属性值
/// </summary>
[Description("获取或设置信息显示按钮颜色 若设置AutoColor无法设置此属性值"), DefaultValue(typeof(Color), "Gray")]
public Color InfoButtonColor {
get { return _InfoButtonColor; }
set { _InfoButtonColor = value; }
}
private Color _FolderCountColor = Color.FromArgb(40, 255, 255, 255);
/// <summary>
/// 获取或设置统计个数的文本颜色
/// </summary>
[Description("获取或设置统计个数的文本颜色")]
public Color FolderCountColor {
get { return _FolderCountColor; }
set { _FolderCountColor = value; }
}
private Color _SwitchColor = Color.LightGray;
private bool _ShowFolderCount = true;
/// <summary>
/// 获取或设置是否统计STNode的个数
/// </summary>
[Description("获取或设置是否统计STNode的个数"), DefaultValue(typeof(Color), "LightGray")]
public bool ShowFolderCount {
get { return _ShowFolderCount; }
set { _ShowFolderCount = value; }
}
private bool _ShowInfoButton = true;
/// <summary>
/// 获取或设置是否显示信息按钮
/// </summary>
[Description("获取或设置是否显示信息按钮"), DefaultValue(true)]
public bool ShowInfoButton {
get { return _ShowInfoButton; }
set { _ShowInfoButton = value; }
}
private bool _InfoPanelIsLeftLayout = true;
/// <summary>
/// 获取或设置预览窗口是否是向左布局
/// </summary>
[Description("获取或设置预览窗口是否是向左布局"), DefaultValue(true)]
public bool InfoPanelIsLeftLayout {
get { return _InfoPanelIsLeftLayout; }
set { _InfoPanelIsLeftLayout = value; }
}
private bool _AutoColor = true;
/// <summary>
/// 获取或设置控件中部分颜色来之对应的STNode的标题颜色
/// </summary>
[Description("获取或设置控件中部分颜色来之对应的STNode的标题颜色"), DefaultValue(true)]
public bool AutoColor {
get { return _AutoColor; }
set {
_AutoColor = value;
this.Invalidate();
}
}
private STNodeEditor _Editor;
/// <summary>
/// 获取节点预览时候使用的STNodeEditor
/// </summary>
[Description("获取节点预览时候使用的STNodeEditor"), Browsable(false)]
public STNodeEditor Editor {
get { return _Editor; }
}
private STNodePropertyGrid _PropertyGrid;
/// <summary>
/// 获取节点预览时候使用的STNodePropertyGrid
/// </summary>
[Description("获取节点预览时候使用的STNodePropertyGrid"), Browsable(false)]
public STNodePropertyGrid PropertyGrid {
get { return _PropertyGrid; }
}
private int m_nItemHeight = 29;
private static Type m_type_node_base = typeof(STNode);
private static char[] m_chr_splitter = new char[] { '/', '\\' };
private STNodeTreeCollection m_items_draw;
private STNodeTreeCollection m_items_source = new STNodeTreeCollection("ROOT");
private Dictionary<Type, string> m_dic_all_type = new Dictionary<Type, string>();
private Pen m_pen;
private SolidBrush m_brush;
private StringFormat m_sf;
private DrawingTools m_dt;
private Color m_clr_item_1 = Color.FromArgb(10, 0, 0, 0);// Color.FromArgb(255, 40, 40, 40);
private Color m_clr_item_2 = Color.FromArgb(10, 255, 255, 255);// Color.FromArgb(255, 50, 50, 50);
private int m_nOffsetY; //控件绘制时候需要偏移的垂直高度
private int m_nSourceOffsetY; //绘制源数据时候需要偏移的垂直高度
private int m_nSearchOffsetY; //绘制检索数据时候需要偏移的垂直高度
private int m_nVHeight; //控件中内容需要的总高度
private bool m_bHoverInfo; //当前鼠标是否悬停在信息显示按钮上
private STNodeTreeCollection m_item_hover; //当前鼠标悬停的树节点
private Point m_pt_control; //鼠标在控件上的坐标
private Point m_pt_offsety; //鼠标在控件上锤子偏移后的坐标
private Rectangle m_rect_clear; //清空检索按钮区域
private string m_str_search; //检索的文本
private TextBox m_tbx = new TextBox(); //检索文本框
/// <summary>
/// 构造一个STNode树控件
/// </summary>
public STNodeTreeView() {
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.MinimumSize = new System.Drawing.Size(100, 60);
this.Size = new System.Drawing.Size(200, 150);
m_items_draw = m_items_source;
m_pen = new Pen(Color.Black);
m_brush = new SolidBrush(Color.White);
m_sf = new StringFormat();
m_sf.LineAlignment = StringAlignment.Center;
m_dt.Pen = m_pen;
m_dt.SolidBrush = m_brush;
this.ForeColor = Color.FromArgb(255, 220, 220, 220);
this.BackColor = Color.FromArgb(255, 35, 35, 35);
m_tbx.Left = 6;
m_tbx.BackColor = Color.FromArgb(255, 30, 30, 30);
m_tbx.ForeColor = this.ForeColor;
m_tbx.BorderStyle = BorderStyle.None;
m_tbx.MaxLength = 20;
m_tbx.TextChanged += new EventHandler(m_tbx_TextChanged);
this.Controls.Add(m_tbx);
this.AllowDrop = true;
this._Editor = new STNodeEditor();
this._PropertyGrid = new STNodePropertyGrid();
}
#region private method ==========
private void m_tbx_TextChanged(object sender, EventArgs e) {
m_str_search = m_tbx.Text.Trim().ToLower();
m_nSearchOffsetY = 0;
if (m_str_search == string.Empty) {
m_items_draw = m_items_source;
this.Invalidate();
return;
}
m_items_draw = m_items_source.Copy();
this.Search(m_items_draw, new Stack<string>(), m_str_search);
this.Invalidate();
}
private bool Search(STNodeTreeCollection items, Stack<string> stack, string strText) {
bool bFound = false;
string[] strName = new string[items.Count];
int nCounter = 0;
foreach (STNodeTreeCollection v in items) {
if (v.NameLower.IndexOf(strText) != -1) {
v.IsOpen = bFound = true;
} else {
if (!this.Search(v, stack, strText)) {
stack.Push(v.Name);
nCounter++;
} else {
v.IsOpen = bFound = true;
}
}
}
for (int i = 0; i < nCounter; i++) items.Remove(stack.Pop(), false);
return bFound;
}
private bool AddSTNode(Type stNodeType, STNodeTreeCollection items, string strLibName, bool bShowException) {
if (m_dic_all_type.ContainsKey(stNodeType)) return false;
if (stNodeType == null) return false;
if (!stNodeType.IsSubclassOf(m_type_node_base)) {
if (bShowException)
throw new ArgumentException("不支持的类型[" + stNodeType.FullName + "] [stNodeType]参数值必须为[STNode]子类的类型");
else return false;
}
var attr = this.GetNodeAttribute(stNodeType);
if (attr == null) {
if (bShowException)
throw new InvalidOperationException("类型[" + stNodeType.FullName + "]未被[STNodeAttribute]所标记");
else return false;
}
string strPath = string.Empty;
items.STNodeCount++;
if (!string.IsNullOrEmpty(attr.Path)) {
var strKeys = attr.Path.Split(m_chr_splitter);
for (int i = 0; i < strKeys.Length; i++) {
items = items.Add(strKeys[i]);
items.STNodeCount++;
strPath += "/" + strKeys[i];
}
}
try {
STNode node = (STNode)Activator.CreateInstance(stNodeType);
STNodeTreeCollection stt = new STNodeTreeCollection(node.Title);
stt.Path = (strLibName + "/" + attr.Path).Trim('/');
stt.STNodeType = stNodeType;
items[stt.Name] = stt;
stt.STNodeTypeColor = node.TitleColor;
m_dic_all_type.Add(stNodeType, stt.Path);
this.Invalidate();
} catch (Exception ex) {
if (bShowException) throw ex;
return false;
}
return true;
}
private STNodeTreeCollection AddAssemblyPrivate(string strFile) {
strFile = System.IO.Path.GetFullPath(strFile);
var asm = Assembly.LoadFrom(strFile);
STNodeTreeCollection items = new STNodeTreeCollection(System.IO.Path.GetFileNameWithoutExtension(strFile));
foreach (var v in asm.GetTypes()) {
if (v.IsAbstract) continue;
if (v.IsSubclassOf(m_type_node_base)) this.AddSTNode(v, items, items.Name, false);
}
return items;
}
private STNodeAttribute GetNodeAttribute(Type stNodeType) {
if (stNodeType == null) return null;
foreach (var v in stNodeType.GetCustomAttributes(true)) {
if (!(v is STNodeAttribute)) continue;
return (STNodeAttribute)v;
}
return null;
}
private STNodeTreeCollection FindItemByPoint(STNodeTreeCollection items, Point pt) {
foreach (STNodeTreeCollection t in items) {
if (t.DisplayRectangle.Contains(pt)) return t;
if (t.IsOpen) {
var n = FindItemByPoint(t, pt);
if (n != null) return n;
}
}
return null;
}
#endregion
#region overide method ==========
protected override void OnCreateControl() {
base.OnCreateControl();
m_tbx.Top = (m_nItemHeight - m_tbx.Height) / 2;
}
protected override void OnResize(EventArgs e) {
base.OnResize(e);
m_tbx.Width = this.Width - 29;
m_rect_clear = new Rectangle(this.Width - 20, 9, 12, 12);
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
m_nOffsetY = string.IsNullOrEmpty(m_str_search) ? m_nSourceOffsetY : m_nSearchOffsetY;
Graphics g = e.Graphics;
m_dt.Graphics = g;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
g.TranslateTransform(0, m_nOffsetY);
int nCounter = 0;
foreach (STNodeTreeCollection v in m_items_draw)
nCounter = this.OnStartDrawItem(m_dt, v, nCounter, 0);
m_nVHeight = (nCounter + 1) * m_nItemHeight;
foreach (STNodeTreeCollection v in m_items_draw)
this.OnDrawSwitch(m_dt, v);
g.ResetTransform();
this.OnDrawSearch(m_dt);
}
protected override void OnMouseMove(MouseEventArgs e) {
base.OnMouseMove(e);
bool bRedraw = false;
m_pt_offsety = m_pt_control = e.Location;
m_pt_offsety.Y -= m_nOffsetY;
if (!string.IsNullOrEmpty(m_str_search) && m_rect_clear.Contains(e.Location))
this.Cursor = Cursors.Hand;
else
this.Cursor = Cursors.Arrow;
var node = this.FindItemByPoint(m_items_draw, m_pt_offsety);
if (m_item_hover != node) {
m_item_hover = node;
bRedraw = true;
}
if (node != null) {
bool bHoverInfo = node.InfoRectangle.Contains(m_pt_offsety);
if (bHoverInfo != m_bHoverInfo) {
m_bHoverInfo = bHoverInfo;
bRedraw = true;
}
}
if (bRedraw) this.Invalidate();
}
protected override void OnMouseDown(MouseEventArgs e) {
base.OnMouseDown(e);
this.Focus();
if (!string.IsNullOrEmpty(m_str_search) && m_rect_clear.Contains(e.Location)) {
m_tbx.Text = string.Empty;
return;
}
m_pt_offsety = m_pt_control = e.Location;
m_pt_offsety.Y -= m_nOffsetY;
if (m_item_hover == null) return;
if (m_item_hover.SwitchRectangle.Contains(m_pt_offsety)) {
m_item_hover.IsOpen = !m_item_hover.IsOpen;
this.Invalidate();
} else if (m_item_hover.InfoRectangle.Contains(m_pt_offsety)) {
Rectangle rect = this.RectangleToScreen(m_item_hover.DisplayRectangle);
FrmNodePreviewPanel frm = new FrmNodePreviewPanel(m_item_hover.STNodeType,
new Point(rect.Right - m_nItemHeight, rect.Top + m_nOffsetY),
m_nItemHeight,
this._InfoPanelIsLeftLayout,
this._Editor, this._PropertyGrid);
frm.BackColor = this.BackColor;
frm.Show(this);
} else if (m_item_hover.STNodeType != null) {
DataObject d = new DataObject("STNodeType", m_item_hover.STNodeType);
this.DoDragDrop(d, DragDropEffects.Copy);
}
}
protected override void OnMouseDoubleClick(MouseEventArgs e) {
base.OnMouseDoubleClick(e);
m_pt_offsety = m_pt_control = e.Location;
m_pt_offsety.Y -= m_nOffsetY;
STNodeTreeCollection item = this.FindItemByPoint(m_items_draw, m_pt_offsety);
if (item == null || item.STNodeType != null) return;
item.IsOpen = !item.IsOpen;
this.Invalidate();
}
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave(e);
if (m_item_hover != null) {
m_item_hover = null;
this.Invalidate();
}
}
protected override void OnMouseWheel(MouseEventArgs e) {
base.OnMouseWheel(e);
if (e.Delta > 0) {
if (m_nOffsetY == 0) return;
m_nOffsetY += m_nItemHeight;
if (m_nOffsetY > 0) m_nOffsetY = 0;
} else {
if (this.Height - m_nOffsetY >= m_nVHeight) return;
m_nOffsetY -= m_nItemHeight;
}
if (string.IsNullOrEmpty(m_str_search))
m_nSourceOffsetY = m_nOffsetY;
else
m_nSearchOffsetY = m_nOffsetY;
this.Invalidate();
}
#endregion
#region protected method ==========
/// <summary>
/// 当绘制检索文本区域时候发生
/// </summary>
/// <param name="dt">绘制工具</param>
protected virtual void OnDrawSearch(DrawingTools dt) {
Graphics g = dt.Graphics;
m_brush.Color = this._TitleColor;
g.FillRectangle(m_brush, 0, 0, this.Width, m_nItemHeight);
m_brush.Color = m_tbx.BackColor;
g.FillRectangle(m_brush, 5, 5, this.Width - 10, m_nItemHeight - 10);
m_pen.Color = this.ForeColor;
if (string.IsNullOrEmpty(m_str_search)) {
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.DrawEllipse(m_pen, this.Width - 17, 8, 8, 8);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
g.DrawLine(m_pen, this.Width - 13, 17, this.Width - 13, m_nItemHeight - 9);
} else {
m_pen.Color = this.ForeColor;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.DrawEllipse(m_pen, this.Width - 20, 9, 10, 10);
g.DrawLine(m_pen, this.Width - 18, 11, this.Width - 12, 17);
g.DrawLine(m_pen, this.Width - 12, 11, this.Width - 18, 17);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
}
}
/// <summary>
/// 当开始绘制树节点的每一个节点时候发生
/// </summary>
/// <param name="dt">绘制工具</param>
/// <param name="Items">当前需要绘制的集合</param>
/// <param name="nCounter">已经绘制个数的计数器</param>
/// <param name="nLevel">当前位于第几级子集合</param>
/// <returns>已经绘制个数</returns>
protected virtual int OnStartDrawItem(DrawingTools dt, STNodeTreeCollection Items, int nCounter, int nLevel) {
Graphics g = dt.Graphics;
Items.DisplayRectangle = new Rectangle(0, m_nItemHeight * (nCounter + 1), this.Width, m_nItemHeight);
Items.SwitchRectangle = new Rectangle(5 + nLevel * 10, (nCounter + 1) * m_nItemHeight, 10, m_nItemHeight);
if (this._ShowInfoButton && Items.STNodeType != null)
Items.InfoRectangle = new Rectangle(this.Width - 18, Items.DisplayRectangle.Top + (m_nItemHeight - 14) / 2, 14, 14);
else Items.InfoRectangle = Rectangle.Empty;
this.OnDrawItem(dt, Items, nCounter++, nLevel);
if (!Items.IsOpen) return nCounter;
foreach (STNodeTreeCollection n in Items) {
if (n.STNodeType == null)
nCounter = this.OnStartDrawItem(dt, n, nCounter++, nLevel + 1);
}
foreach (STNodeTreeCollection n in Items) {
if (n.STNodeType != null)
nCounter = this.OnStartDrawItem(dt, n, nCounter++, nLevel + 1);
}
foreach (STNodeTreeCollection v in Items) this.OnDrawSwitch(dt, v);
return nCounter;
}
/// <summary>
/// 当绘制树节点每一个节点时候发生
/// </summary>
/// <param name="dt">绘制工具</param>
/// <param name="items">当前需要绘制的集合</param>
/// <param name="nCounter">已经绘制个数的计数器</param>
/// <param name="nLevel">当前位于第几级子集合</param>
protected virtual void OnDrawItem(DrawingTools dt, STNodeTreeCollection items, int nCounter, int nLevel) {
Graphics g = dt.Graphics;
m_brush.Color = nCounter % 2 == 0 ? m_clr_item_1 : m_clr_item_2;
g.FillRectangle(m_brush, items.DisplayRectangle);
if (items == m_item_hover) {
m_brush.Color = this._ItemHoverColor;
g.FillRectangle(m_brush, items.DisplayRectangle);
}
Rectangle rect = new Rectangle(45 + nLevel * 10, items.SwitchRectangle.Top, this.Width - 45 - nLevel * 10, m_nItemHeight);
m_pen.Color = Color.FromArgb(100, 125, 125, 125);
g.DrawLine(m_pen, 9, items.SwitchRectangle.Top + m_nItemHeight / 2, items.SwitchRectangle.Left + 19, items.SwitchRectangle.Top + m_nItemHeight / 2);
if (nCounter != 0) {
for (int i = 0; i <= nLevel; i++) {
g.DrawLine(m_pen, 9 + i * 10, items.SwitchRectangle.Top - m_nItemHeight / 2, 9 + i * 10, items.SwitchRectangle.Top + m_nItemHeight / 2 - 1);
}
}
this.OnDrawItemText(dt, items, rect);
this.OnDrawItemIcon(dt, items, rect);
}
/// <summary>
/// 当绘制树节点展开与关闭开关时候发生
/// </summary>
/// <param name="dt">绘制工具</param>
/// <param name="items">当前需要绘制的集合</param>
protected virtual void OnDrawSwitch(DrawingTools dt, STNodeTreeCollection items) {
Graphics g = dt.Graphics;
if (items.Count != 0) {
m_pen.Color = this._SwitchColor;
m_brush.Color = m_pen.Color;
int nT = items.SwitchRectangle.Y + m_nItemHeight / 2 - 4;
g.DrawRectangle(m_pen, items.SwitchRectangle.Left, nT, 8, 8);
g.DrawLine(m_pen, items.SwitchRectangle.Left + 1, nT + 4, items.SwitchRectangle.Right - 3, nT + 4);
if (items.IsOpen) return;
g.DrawLine(m_pen, items.SwitchRectangle.Left + 4, nT + 1, items.SwitchRectangle.Left + 4, nT + 7);
//if (items.IsOpen) {
// //g.FillPolygon(m_brush, new Point[]{
// // new Point(items.DotRectangle.Left + 0, items.DotRectangle.Top + m_nItemHeight / 2 - 2),
// // new Point(items.DotRectangle.Left + 9, items.DotRectangle.Top + m_nItemHeight / 2 - 2),
// // new Point(items.DotRectangle.Left + 4, items.DotRectangle.Top + m_nItemHeight / 2 + 3)
// //});
// g.DrawRectangle(m_pen, items.SwitchRectangle.Left, nT, 8, 8);
// g.DrawLine(m_pen, items.SwitchRectangle.Left + 1, nT + 4, items.SwitchRectangle.Right - 3, nT + 4);
//} else {
// //g.FillPolygon(m_brush, new Point[]{
// // new Point(items.DotRectangle.Left + 2, items.DotRectangle.Top + m_nItemHeight / 2 - 5),
// // new Point(items.DotRectangle.Left + 2, items.DotRectangle.Top + m_nItemHeight / 2 + 5),
// // new Point(items.DotRectangle.Left + 7, items.DotRectangle.Top + m_nItemHeight / 2)
// //});
// g.DrawRectangle(m_pen, items.SwitchRectangle.Left, nT, 8, 8);
// g.DrawLine(m_pen, items.SwitchRectangle.Left + 1, nT + 4, items.SwitchRectangle.Right - 3, nT + 4);
// g.DrawLine(m_pen, items.SwitchRectangle.Left + 4, nT + 1, items.SwitchRectangle.Left + 4, nT + 7);
//}
}
}
/// <summary>
/// 当绘制树节点的文本时候发生
/// </summary>
/// <param name="dt">绘制工具</param>
/// <param name="items">当前需要绘制的集合</param>
/// <param name="rect">文本域所在矩形区域</param>
protected virtual void OnDrawItemText(DrawingTools dt, STNodeTreeCollection items, Rectangle rect) {
Graphics g = dt.Graphics;
rect.Width -= 20;
m_sf.FormatFlags = StringFormatFlags.NoWrap;
if (!string.IsNullOrEmpty(m_str_search)) {
int nIndex = items.NameLower.IndexOf(m_str_search);
if (nIndex != -1) {
CharacterRange[] chrs = { new CharacterRange(nIndex, m_str_search.Length) };//global
m_sf.SetMeasurableCharacterRanges(chrs);
Region[] regions = g.MeasureCharacterRanges(items.Name, this.Font, rect, m_sf);
g.SetClip(regions[0], System.Drawing.Drawing2D.CombineMode.Intersect);
m_brush.Color = this._HightLightTextColor;
g.DrawString(items.Name, this.Font, m_brush, rect, m_sf);
g.ResetClip();
g.SetClip(regions[0], System.Drawing.Drawing2D.CombineMode.Exclude);
m_brush.Color = items.STNodeType == null ? Color.FromArgb(this.ForeColor.A * 1 / 2, this.ForeColor) : this.ForeColor;
g.DrawString(items.Name, this.Font, m_brush, rect, m_sf);
g.ResetClip();
return;
}
}
m_brush.Color = items.STNodeType == null ? Color.FromArgb(this.ForeColor.A * 2 / 3, this.ForeColor) : this.ForeColor;
g.DrawString(items.Name, this.Font, m_brush, rect, m_sf);
}
/// <summary>
/// 当绘制树节点图标时候发生
/// </summary>
/// <param name="dt">绘制工具</param>
/// <param name="items">当前需要绘制的集合</param>
/// <param name="rect">文本域所在矩形区域</param>
protected virtual void OnDrawItemIcon(DrawingTools dt, STNodeTreeCollection items, Rectangle rect) {
Graphics g = dt.Graphics;
if (items.STNodeType != null) {
m_pen.Color = this._AutoColor ? items.STNodeTypeColor : Color.DarkCyan;
m_brush.Color = Color.LightGray;
g.DrawRectangle(m_pen, rect.Left - 15, rect.Top + m_nItemHeight / 2 - 5, 11, 10);
g.FillRectangle(m_brush, rect.Left - 17, rect.Top + m_nItemHeight / 2 - 2, 5, 5);
g.FillRectangle(m_brush, rect.Left - 6, rect.Top + m_nItemHeight / 2 - 2, 5, 5);
if (m_item_hover == items && m_bHoverInfo) {
m_brush.Color = this.BackColor;
g.FillRectangle(m_brush, items.InfoRectangle);
}
m_pen.Color = this._AutoColor ? items.STNodeTypeColor : this._InfoButtonColor;
m_pen.Width = 2;
g.DrawLine(m_pen, items.InfoRectangle.X + 4, items.InfoRectangle.Y + 3, items.InfoRectangle.X + 10, items.InfoRectangle.Y + 3);
g.DrawLine(m_pen, items.InfoRectangle.X + 4, items.InfoRectangle.Y + 6, items.InfoRectangle.X + 10, items.InfoRectangle.Y + 6);
g.DrawLine(m_pen, items.InfoRectangle.X + 4, items.InfoRectangle.Y + 11, items.InfoRectangle.X + 10, items.InfoRectangle.Y + 11);
g.DrawLine(m_pen, items.InfoRectangle.X + 7, items.InfoRectangle.Y + 7, items.InfoRectangle.X + 7, items.InfoRectangle.Y + 10);
m_pen.Width = 1;
g.DrawRectangle(m_pen, items.InfoRectangle.X, items.InfoRectangle.Y, items.InfoRectangle.Width - 1, items.InfoRectangle.Height - 1);
} else {
if (items.IsLibraryRoot) {
Rectangle rect_box = new Rectangle(rect.Left - 15, rect.Top + m_nItemHeight / 2 - 5, 11, 10);
g.DrawRectangle(Pens.Gray, rect_box);
g.DrawLine(Pens.Cyan, rect_box.X - 2, rect_box.Top, rect_box.X + 2, rect_box.Top);
g.DrawLine(Pens.Cyan, rect_box.X, rect_box.Y - 2, rect_box.X, rect_box.Y + 2);
g.DrawLine(Pens.Cyan, rect_box.Right - 2, rect_box.Bottom, rect_box.Right + 2, rect_box.Bottom);
g.DrawLine(Pens.Cyan, rect_box.Right, rect_box.Bottom - 2, rect_box.Right, rect_box.Bottom + 2);
} else {
g.DrawRectangle(Pens.Goldenrod, new Rectangle(rect.Left - 16, rect.Top + m_nItemHeight / 2 - 6, 8, 3));
g.DrawRectangle(Pens.Goldenrod, new Rectangle(rect.Left - 16, rect.Top + m_nItemHeight / 2 - 3, 13, 9));
}
if (!this._ShowFolderCount) return;
m_sf.Alignment = StringAlignment.Far;
m_brush.Color = this._FolderCountColor;
rect.X -= 4;
g.DrawString("[" + items.STNodeCount.ToString() + "]", this.Font, m_brush, rect, m_sf);
m_sf.Alignment = StringAlignment.Near;
}
}
#endregion
#region public method ==========
/// <summary>
/// 在控件中检索STNode
/// </summary>
/// <param name="strText">需要检索的文本</param>
public void Search(string strText) {
if (strText == null) return;
if (strText.Trim() == string.Empty) return;
m_tbx.Text = strText.Trim();
}
/// <summary>
/// 向控件中添加一个STNode类型
/// </summary>
/// <param name="stNodeType">STNode类型</param>
/// <returns>是否添加成功</returns>
public bool AddNode(Type stNodeType) { return this.AddSTNode(stNodeType, m_items_source, null, true); }
/// <summary>
/// 从文件中向控件添加STNode类型
/// </summary>
/// <param name="strFile">指定文件路径</param>
/// <returns>添加成功个数</returns>
public int LoadAssembly(string strFile) {
strFile = System.IO.Path.GetFullPath(strFile);
var items = this.AddAssemblyPrivate(strFile);
if (items.STNodeCount == 0) return 0;
items.IsLibraryRoot = true;
m_items_source[items.Name] = items;
return items.STNodeCount;
}
/// <summary>
/// 清空控件中所有STNode类型
/// </summary>
public void Clear() {
m_items_source.Clear();
m_items_draw.Clear();
m_dic_all_type.Clear();
this.Invalidate();
}
/// <summary>
/// 向控件中移除一个STNode类型
/// </summary>
/// <param name="stNodeType">STNode类型</param>
/// <returns>是否移除成功</returns>
public bool RemoveNode(Type stNodeType) {
if (!m_dic_all_type.ContainsKey(stNodeType)) return false;
string strPath = m_dic_all_type[stNodeType];
STNodeTreeCollection items = m_items_source;
if (!string.IsNullOrEmpty(strPath)) {
string[] strKeys = strPath.Split(m_chr_splitter);
for (int i = 0; i < strKeys.Length; i++) {
items = items[strKeys[i]];
if (items == null) return false;
}
}
try {
STNode node = (STNode)Activator.CreateInstance(stNodeType);
if (items[node.Title] == null) return false;
items.Remove(node.Title, true);
m_dic_all_type.Remove(stNodeType);
} catch { return false; }
this.Invalidate();
return true;
}
#endregion
//=================================================================================================
/// <summary>
/// STNodeTreeView控件中每一项的集合
/// </summary>
protected class STNodeTreeCollection : IEnumerable
{
private string _Name;
/// <summary>
/// 获取当前树节点显示名称
/// </summary>
public string Name {
get {
return _Name;
}
}
/// <summary>
/// 获取当前树节点显示名称的小写字符串
/// </summary>
public string NameLower { get; private set; }
/// <summary>
/// 获取当前树节点对应的STNode类型
/// </summary>
public Type STNodeType { get; internal set; }
/// <summary>
/// 获取当前树节点的父级树节点
/// </summary>
public STNodeTreeCollection Parent { get; internal set; }
/// <summary>
/// 获取当前树节点下拥有的STNode类型个数
/// </summary>
public int STNodeCount { get; internal set; }
/// <summary>
/// 获取当前树节点对应STNode类型在树控件中对应路径
/// </summary>
public string Path { get; internal set; }
/// <summary>
/// 获取当前或设置树节点是否为打开状态
/// </summary>
public bool IsOpen { get; set; }
/// <summary>
/// 获取当前树节点是否为加载模块的根路劲节点
/// </summary>
public bool IsLibraryRoot { get; internal set; }
/// <summary>
/// 获取当前树节点在控件中的显示区域
/// </summary>
public Rectangle DisplayRectangle { get; internal set; }
/// <summary>
/// 获取当前树节点在控件中的开关按钮区域
/// </summary>
public Rectangle SwitchRectangle { get; internal set; }
/// <summary>
/// 获取当前树节点在控件中的信息按钮区域
/// </summary>
public Rectangle InfoRectangle { get; internal set; }
/// <summary>
/// 获取当前树节点对应STNode类型的标题颜色
/// </summary>
public Color STNodeTypeColor { get; internal set; }
/// <summary>
/// 获取当前树节点所包含子节点个数
/// </summary>
public int Count { get { return m_dic.Count; } }
/// <summary>
/// 获取或设置指定名称的集合
/// </summary>
/// <param name="strKey">指定名称</param>
/// <returns>集合</returns>
public STNodeTreeCollection this[string strKey] {
get {
if (string.IsNullOrEmpty(strKey)) return null;
if (m_dic.ContainsKey(strKey)) return m_dic[strKey];
return null;
}
set {
if (string.IsNullOrEmpty(strKey)) return;
if (value == null) return;
if (m_dic.ContainsKey(strKey)) {
m_dic[strKey] = value;
} else {
m_dic.Add(strKey, value);
}
value.Parent = this;
}
}
private SortedDictionary<string, STNodeTreeCollection> m_dic = new SortedDictionary<string, STNodeTreeCollection>();
/// <summary>
/// 构造一颗树节点集合
/// </summary>
/// <param name="strName">当前树节点在控件中的显示名称</param>
public STNodeTreeCollection(string strName) {
if (strName == null || strName.Trim() == string.Empty)
throw new ArgumentNullException("显示名称不能为空");
this._Name = strName.Trim();
this.NameLower = this._Name.ToLower();
}
/// <summary>
/// 向当前树节点中添加一个子节点
/// </summary>
/// <param name="strName">节点显示名称</param>
/// <returns>添加后的子节点集合</returns>
public STNodeTreeCollection Add(string strName) {
if (!m_dic.ContainsKey(strName))
m_dic.Add(strName, new STNodeTreeCollection(strName) { Parent = this });
return m_dic[strName];
}
/// <summary>
/// 向当前树节点中删除一个子集合
/// </summary>
/// <param name="strName">子集合名称</param>
/// <param name="isAutoDelFolder">是否递归向上自动清空无用节点</param>
/// <returns>是否删除成功</returns>
public bool Remove(string strName, bool isAutoDelFolder) {
if (!m_dic.ContainsKey(strName)) return false;
bool b = m_dic.Remove(strName);
var temp = this;
while (temp != null) {
temp.STNodeCount--;
temp = temp.Parent;
}
if (isAutoDelFolder && m_dic.Count == 0 && this.Parent != null)
return b && this.Parent.Remove(this.Name, isAutoDelFolder);
return b;
}
/// <summary>
/// 清空当前树节点中所有子节点
/// </summary>
public void Clear() { this.Clear(this); }
private void Clear(STNodeTreeCollection items) {
foreach (STNodeTreeCollection v in items) v.Clear(v);
m_dic.Clear();
}
/// <summary>
/// 获取当前树节点中所有的名称数组
/// </summary>
/// <returns></returns>
public string[] GetKeys() { return m_dic.Keys.ToArray(); }
/// <summary>
/// 拷贝当前树节点集合中所有数据
/// </summary>
/// <returns>拷贝的副本</returns>
public STNodeTreeCollection Copy() {
STNodeTreeCollection items = new STNodeTreeCollection("COPY");
this.Copy(this, items);
return items;
}
private void Copy(STNodeTreeCollection items_src, STNodeTreeCollection items_dst) {
foreach (STNodeTreeCollection v in items_src) {
this.Copy(v, items_dst.Add(v.Name));
}
items_dst.Path = items_src.Path;
items_dst.STNodeType = items_src.STNodeType;
items_dst.IsLibraryRoot = items_src.IsLibraryRoot;
items_dst.STNodeCount = items_src.STNodeCount;
items_dst.STNodeTypeColor = items_src.STNodeTypeColor;
}
/// <summary>
/// 返回 System.Collections.IEnumerator 的 Array
/// </summary>
/// <returns></returns>
public IEnumerator GetEnumerator() {
foreach (var v in m_dic.Values) yield return v;
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
}
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@ -10,9 +10,8 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ST.Library.UI</RootNamespace>
<AssemblyName>ST.Library.UI</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -22,8 +21,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -34,50 +31,28 @@
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\ST.Library.UI.XML</DocumentationFile>
<DebugSymbols>true</DebugSymbols>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="NodeEditor\FrmNodePreviewPanel.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="NodeEditor\FrmSTNodePropertySelect.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="NodeEditor\STNodeEditorPannel.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="NodeEditor\STNodeTreeView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="NodeEditor\FrmSTNodePropertyInput.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="NodeEditor\STNode.cs" />
<Compile Include="NodeEditor\STNodeAttribute.cs" />
<Compile Include="NodeEditor\STNodeCollection.cs" />
<Compile Include="NodeEditor\STNodeControl.cs" />
<Compile Include="NodeEditor\STNodeControlCollection.cs" />
<Compile Include="NodeEditor\STNodeEditor.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="NodeEditor\STNodeEditorDataType.cs" />
<Compile Include="NodeEditor\STNodeHub.cs" />
<Compile Include="NodeEditor\STNodeOption.cs" />
<Compile Include="NodeEditor\STNodeOptionCollection.cs" />
<Compile Include="NodeEditor\STNodePropertyAttribute.cs" />
<Compile Include="NodeEditor\STNodePropertyGrid.cs">
<Compile Include="STNodeEditor\STNode.cs" />
<Compile Include="STNodeEditor\STNodeCollection.cs" />
<Compile Include="STNodeEditor\STNodeControl.cs" />
<Compile Include="STNodeEditor\STNodeControlCollection.cs" />
<Compile Include="STNodeEditor\STNodeEditor.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="STNodeEditor\STNodeEditorDataType.cs" />
<Compile Include="STNodeEditor\STNodeHub.cs" />
<Compile Include="STNodeEditor\STNodeOption.cs" />
<Compile Include="STNodeEditor\STNodeOptionCollection.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@ -1,253 +1,236 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Drawing;
namespace ST.Library.UI.NodeEditor
{
public class STNodeCollection : IList, ICollection, IEnumerable
{
private int _Count;
public int Count { get { return _Count; } }
private STNode[] m_nodes;
private STNodeEditor m_owner;
internal STNodeCollection(STNodeEditor owner) {
if (owner == null) throw new ArgumentNullException("所有者不能为空");
m_owner = owner;
m_nodes = new STNode[4];
}
public void MoveToEnd(STNode node) {
if (this._Count < 1) return;
if (m_nodes[this._Count - 1] == node) return;
bool bFound = false;
for (int i = 0; i < _Count - 1; i++) {
if (m_nodes[i] == node) {
bFound = true;
}
if (bFound) m_nodes[i] = m_nodes[i + 1];
}
m_nodes[this._Count - 1] = node;
}
public int Add(STNode node) {
if (node == null) throw new ArgumentNullException("添加对象不能为空");
this.EnsureSpace(1);
int nIndex = this.IndexOf(node);
if (-1 == nIndex) {
nIndex = this._Count;
node.Owner = m_owner;
//node.BuildSize(true, true, false);
m_nodes[this._Count++] = node;
m_owner.BuildBounds();
m_owner.OnNodeAdded(new STNodeEditorEventArgs(node));
m_owner.Invalidate();
//m_owner.Invalidate(m_owner.CanvasToControl(new Rectangle(node.Left - 5, node.Top - 5, node.Width + 10, node.Height + 10)));
//Console.WriteLine(node.Rectangle);
}
return nIndex;
}
public void AddRange(STNode[] nodes) {
if (nodes == null) throw new ArgumentNullException("添加对象不能为空");
this.EnsureSpace(nodes.Length);
foreach (var n in nodes) {
if (n == null) throw new ArgumentNullException("添加对象不能为空");
if (-1 == this.IndexOf(n)) {
n.Owner = m_owner;
m_nodes[this._Count++] = n;
}
m_owner.OnNodeAdded(new STNodeEditorEventArgs(n));
}
m_owner.Invalidate();
m_owner.BuildBounds();
}
public void Clear() {
for (int i = 0; i < this._Count; i++) {
m_nodes[i].Owner = null;
foreach (STNodeOption op in m_nodes[i].InputOptions) op.DisConnectionAll();
foreach (STNodeOption op in m_nodes[i].OutputOptions) op.DisConnectionAll();
m_owner.OnNodeRemoved(new STNodeEditorEventArgs(m_nodes[i]));
m_owner.InternalRemoveSelectedNode(m_nodes[i]);
}
this._Count = 0;
m_nodes = new STNode[4];
m_owner.SetActiveNode(null);
m_owner.BuildBounds();
m_owner.ScaleCanvas(1, 0, 0); //当不存在节点时候 坐标系回归
m_owner.MoveCanvas(10, 10, true, CanvasMoveArgs.All);
m_owner.Invalidate(); //如果画布位置和缩放处于初始状态 上面两行代码并不会造成控件重绘
}
public bool Contains(STNode node) {
return this.IndexOf(node) != -1;
}
public int IndexOf(STNode node) {
return Array.IndexOf<STNode>(m_nodes, node);
}
public void Insert(int nIndex, STNode node) {
if (nIndex < 0 || nIndex >= this._Count)
throw new IndexOutOfRangeException("索引越界");
if (node == null)
throw new ArgumentNullException("插入对象不能为空");
this.EnsureSpace(1);
for (int i = this._Count; i > nIndex; i--)
m_nodes[i] = m_nodes[i - 1];
node.Owner = m_owner;
m_nodes[nIndex] = node;
this._Count++;
//node.BuildSize(true, true,false);
m_owner.Invalidate();
m_owner.BuildBounds();
}
public bool IsFixedSize {
get { return false; }
}
public bool IsReadOnly {
get { return false; }
}
public void Remove(STNode node) {
int nIndex = this.IndexOf(node);
if (nIndex != -1) this.RemoveAt(nIndex);
}
public void RemoveAt(int nIndex) {
if (nIndex < 0 || nIndex >= this._Count)
throw new IndexOutOfRangeException("索引越界");
m_nodes[nIndex].Owner = null;
m_owner.InternalRemoveSelectedNode(m_nodes[nIndex]);
if (m_owner.ActiveNode == m_nodes[nIndex]) m_owner.SetActiveNode(null);
m_owner.OnNodeRemoved(new STNodeEditorEventArgs(m_nodes[nIndex]));
this._Count--;
for (int i = nIndex, Len = this._Count; i < Len; i++)
m_nodes[i] = m_nodes[i + 1];
if (this._Count == 0) { //当不存在节点时候 坐标系回归
m_owner.ScaleCanvas(1, 0, 0);
m_owner.MoveCanvas(10, 10, true, CanvasMoveArgs.All);
} else {
m_owner.Invalidate();
m_owner.BuildBounds();
}
}
public STNode this[int nIndex] {
get {
if (nIndex < 0 || nIndex >= this._Count)
throw new IndexOutOfRangeException("索引越界");
return m_nodes[nIndex];
}
set { throw new InvalidOperationException("禁止重新赋值元素"); }
}
public void CopyTo(Array array, int index) {
if (array == null)
throw new ArgumentNullException("数组不能为空");
m_nodes.CopyTo(array, index);
}
public bool IsSynchronized {
get { return true; }
}
public object SyncRoot {
get { return this; }
}
public IEnumerator GetEnumerator() {
for (int i = 0, Len = this._Count; i < Len; i++)
yield return m_nodes[i];
}
/// <summary>
/// 确认空间是否足够 空间不足扩大容量
/// </summary>
/// <param name="elements">需要增加的个数</param>
private void EnsureSpace(int elements) {
if (elements + this._Count > m_nodes.Length) {
STNode[] arrTemp = new STNode[Math.Max(m_nodes.Length * 2, elements + this._Count)];
m_nodes.CopyTo(arrTemp, 0);
m_nodes = arrTemp;
}
}
//============================================================================
int IList.Add(object value) {
return this.Add((STNode)value);
}
void IList.Clear() {
this.Clear();
}
bool IList.Contains(object value) {
return this.Contains((STNode)value);
}
int IList.IndexOf(object value) {
return this.IndexOf((STNode)value);
}
void IList.Insert(int index, object value) {
this.Insert(index, (STNode)value);
}
bool IList.IsFixedSize {
get { return this.IsFixedSize; }
}
bool IList.IsReadOnly {
get { return this.IsReadOnly; }
}
void IList.Remove(object value) {
this.Remove((STNode)value);
}
void IList.RemoveAt(int index) {
this.RemoveAt(index);
}
object IList.this[int index] {
get {
return this[index];
}
set {
this[index] = (STNode)value;
}
}
void ICollection.CopyTo(Array array, int index) {
this.CopyTo(array, index);
}
int ICollection.Count {
get { return this._Count; }
}
bool ICollection.IsSynchronized {
get { return this.IsSynchronized; }
}
object ICollection.SyncRoot {
get { return this.SyncRoot; }
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
public STNode[] ToArray() {
STNode[] nodes = new STNode[this._Count];
for (int i = 0; i < nodes.Length; i++)
nodes[i] = m_nodes[i];
return nodes;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Drawing;
namespace ST.Library.UI
{
public class STNodeCollection : IList, ICollection, IEnumerable
{
private int _Count;
public int Count { get { return _Count; } }
private STNode[] m_nodes;
private STNodeEditor m_owner;
internal STNodeCollection(STNodeEditor owner) {
if (owner == null) throw new ArgumentNullException("所有者不能为空");
m_owner = owner;
m_nodes = new STNode[4];
}
public int Add(STNode node) {
if (node == null) throw new ArgumentNullException("添加对象不能为空");
this.EnsureSpace(1);
int nIndex = this.IndexOf(node);
if (-1 == nIndex) {
nIndex = this._Count;
node.Owner = m_owner;
//node.BuildSize(true, true, false);
m_nodes[this._Count++] = node;
m_owner.BuildBounds();
m_owner.OnNodeAdded(new STNodeEditorEventArgs(node));
m_owner.Invalidate();
//m_owner.Invalidate(m_owner.CanvasToControl(new Rectangle(node.Left - 5, node.Top - 5, node.Width + 10, node.Height + 10)));
//Console.WriteLine(node.Rectangle);
}
return nIndex;
}
public void AddRange(STNode[] nodes) {
if (nodes == null) throw new ArgumentNullException("添加对象不能为空");
this.EnsureSpace(nodes.Length);
foreach (var n in nodes) {
if (n == null) throw new ArgumentNullException("添加对象不能为空");
if (-1 == this.IndexOf(n)) {
n.Owner = m_owner;
m_nodes[this._Count++] = n;
}
m_owner.OnNodeAdded(new STNodeEditorEventArgs(n));
}
m_owner.Invalidate();
m_owner.BuildBounds();
}
public void Clear() {
for (int i = 0; i < this._Count; i++) {
m_nodes[i].Owner = null;
foreach (STNodeOption op in m_nodes[i].InputOptions) op.DisConnectionAll();
foreach (STNodeOption op in m_nodes[i].OutputOptions) op.DisConnectionAll();
m_owner.OnNodeRemoved(new STNodeEditorEventArgs(m_nodes[i]));
}
this._Count = 0;
m_nodes = new STNode[4];
m_owner.BuildBounds();
m_owner.ScaleCanvas(1, 0, 0); //当不存在节点时候 坐标系回归
m_owner.MoveCanvas(10, 10, true, CanvasMoveArgs.All);
m_owner.Invalidate(); //如果画布位置和缩放处于初始状态 上面两行代码并不会造成控件重绘
}
public bool Contains(STNode node) {
return this.IndexOf(node) != -1;
}
public int IndexOf(STNode node) {
return Array.IndexOf<STNode>(m_nodes, node);
}
public void Insert(int nIndex, STNode node) {
if (nIndex < 0 || nIndex >= this._Count)
throw new IndexOutOfRangeException("索引越界");
if (node == null)
throw new ArgumentNullException("插入对象不能为空");
this.EnsureSpace(1);
for (int i = this._Count; i > nIndex; i--)
m_nodes[i] = m_nodes[i - 1];
node.Owner = m_owner;
m_nodes[nIndex] = node;
this._Count++;
//node.BuildSize(true, true,false);
m_owner.Invalidate();
m_owner.BuildBounds();
}
public bool IsFixedSize {
get { return false; }
}
public bool IsReadOnly {
get { return false; }
}
public void Remove(STNode node) {
int nIndex = this.IndexOf(node);
if (nIndex != -1) this.RemoveAt(nIndex);
}
public void RemoveAt(int nIndex) {
if (nIndex < 0 || nIndex >= this._Count)
throw new IndexOutOfRangeException("索引越界");
m_nodes[nIndex].Owner = null;
m_owner.OnNodeRemoved(new STNodeEditorEventArgs(m_nodes[nIndex]));
this._Count--;
for (int i = nIndex, Len = this._Count; i < Len; i++)
m_nodes[i] = m_nodes[i + 1];
if (this._Count == 0) { //当不存在节点时候 坐标系回归
m_owner.ScaleCanvas(1, 0, 0);
m_owner.MoveCanvas(10, 10, true, CanvasMoveArgs.All);
} else {
m_owner.Invalidate();
m_owner.BuildBounds();
}
}
public STNode this[int nIndex] {
get {
if (nIndex < 0 || nIndex >= this._Count)
throw new IndexOutOfRangeException("索引越界");
return m_nodes[nIndex];
}
set { throw new InvalidOperationException("禁止重新赋值元素"); }
}
public void CopyTo(Array array, int index) {
if (array == null)
throw new ArgumentNullException("数组不能为空");
m_nodes.CopyTo(array, index);
}
public bool IsSynchronized {
get { return true; }
}
public object SyncRoot {
get { return this; }
}
public IEnumerator GetEnumerator() {
for (int i = 0, Len = this._Count; i < Len; i++)
yield return m_nodes[i];
}
/// <summary>
/// 确认空间是否足够 空间不足扩大容量
/// </summary>
/// <param name="elements">需要增加的个数</param>
private void EnsureSpace(int elements) {
if (elements + this._Count > m_nodes.Length) {
STNode[] arrTemp = new STNode[Math.Max(m_nodes.Length * 2, elements + this._Count)];
m_nodes.CopyTo(arrTemp, 0);
m_nodes = arrTemp;
}
}
//============================================================================
int IList.Add(object value) {
return this.Add((STNode)value);
}
void IList.Clear() {
this.Clear();
}
bool IList.Contains(object value) {
return this.Contains((STNode)value);
}
int IList.IndexOf(object value) {
return this.IndexOf((STNode)value);
}
void IList.Insert(int index, object value) {
this.Insert(index, (STNode)value);
}
bool IList.IsFixedSize {
get { return this.IsFixedSize; }
}
bool IList.IsReadOnly {
get { return this.IsReadOnly; }
}
void IList.Remove(object value) {
this.Remove((STNode)value);
}
void IList.RemoveAt(int index) {
this.RemoveAt(index);
}
object IList.this[int index] {
get {
return this[index];
}
set {
this[index] = (STNode)value;
}
}
void ICollection.CopyTo(Array array, int index) {
this.CopyTo(array, index);
}
int ICollection.Count {
get { return this._Count; }
}
bool ICollection.IsSynchronized {
get { return this.IsSynchronized; }
}
object ICollection.SyncRoot {
get { return this.SyncRoot; }
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
public STNode[] ToArray() {
STNode[] nodes = new STNode[this._Count];
for (int i = 0; i < nodes.Length; i++)
nodes[i] = m_nodes[i];
return nodes;
}
}
}

View File

@ -1,319 +1,263 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
/*
MIT License
Copyright (c) 2021 DebugST@crystal_lz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
* time: 2021-01-06
* Author: Crystal_lz
* blog: st233.com
* Github: DebugST.github.io
*/
namespace ST.Library.UI.NodeEditor
{
public class STNodeControl
{
private STNode _Owner;
public STNode Owner {
get { return _Owner; }
internal set { _Owner = value; }
}
private int _Left;
public int Left {
get { return _Left; }
set {
_Left = value;
this.OnMove(EventArgs.Empty);
this.Invalidate();
}
}
private int _Top;
public int Top {
get { return _Top; }
set {
_Top = value;
this.OnMove(EventArgs.Empty);
this.Invalidate();
}
}
private int _Width;
public int Width {
get { return _Width; }
set {
_Width = value;
this.OnResize(EventArgs.Empty);
this.Invalidate();
}
}
private int _Height;
public int Height {
get { return _Height; }
set {
_Height = value;
this.OnResize(EventArgs.Empty);
this.Invalidate();
}
}
public int Right { get { return this._Left + this._Width; } }
public int Bottom { get { return this._Top + this._Height; } }
public Point Location {
get { return new Point(this._Left, this._Top); }
set {
this.Left = value.X;
this.Top = value.Y;
}
}
public Size Size {
get { return new Size(this._Width, this._Height); }
set {
this.Width = value.Width;
this.Height = value.Height;
}
}
public Rectangle DisplayRectangle {
get { return new Rectangle(this._Left, this._Top, this._Width, this._Height); }
set {
this.Left = value.X;
this.Top = value.Y;
this.Width = value.Width;
this.Height = value.Height;
}
}
public Rectangle ClientRectangle {
get { return new Rectangle(0, 0, this._Width, this._Height); }
}
private Color _BackColor = Color.FromArgb(127, 0, 0, 0);
public Color BackColor {
get { return _BackColor; }
set {
_BackColor = value;
this.Invalidate();
}
}
private Color _ForeColor = Color.White;
public Color ForeColor {
get { return _ForeColor; }
set {
_ForeColor = value;
this.Invalidate();
}
}
private string _Text = "STNCTRL";
public string Text {
get { return _Text; }
set {
_Text = value;
this.Invalidate();
}
}
private Font _Font;
public Font Font {
get { return _Font; }
set {
if (value == _Font) return;
if (value == null) throw new ArgumentNullException("值不能为空");
_Font = value;
this.Invalidate();
}
}
private bool _Enabled = true;
public bool Enabled {
get { return _Enabled; }
set {
if (value == _Enabled) return;
_Enabled = value;
this.Invalidate();
}
}
private bool _Visable = true;
public bool Visable {
get { return _Visable; }
set {
if (value == _Visable) return;
_Visable = value;
this.Invalidate();
}
}
protected StringFormat m_sf;
public STNodeControl() {
m_sf = new StringFormat();
m_sf.Alignment = StringAlignment.Center;
m_sf.LineAlignment = StringAlignment.Center;
this._Font = new Font("courier new", 8.25f);
this.Width = 75;
this.Height = 23;
}
protected internal virtual void OnPaint(DrawingTools dt) {
Graphics g = dt.Graphics;
SolidBrush brush = dt.SolidBrush;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
brush.Color = this._BackColor;
g.FillRectangle(brush, 0, 0, this.Width, this.Height);
if (!string.IsNullOrEmpty(this._Text)) {
brush.Color = this._ForeColor;
g.DrawString(this._Text, this._Font, brush, this.ClientRectangle, m_sf);
}
if (this.Paint != null) this.Paint(this, new STNodeControlPaintEventArgs(dt));
}
public void Invalidate() {
if (this._Owner == null) return;
this._Owner.Invalidate(new Rectangle(this._Left, this._Top + this._Owner.TitleHeight, this.Width, this.Height));
}
public void Invalidate(Rectangle rect) {
if (this._Owner == null) return;
this._Owner.Invalidate(this.RectangleToParent(rect));
}
public Rectangle RectangleToParent(Rectangle rect) {
return new Rectangle(this._Left, this._Top + this._Owner.TitleHeight, this.Width, this.Height);
}
public event EventHandler GotFocus;
public event EventHandler LostFocus;
public event EventHandler MouseEnter;
public event EventHandler MouseLeave;
public event MouseEventHandler MouseDown;
public event MouseEventHandler MouseMove;
public event MouseEventHandler MouseUp;
public event MouseEventHandler MouseClick;
public event MouseEventHandler MouseWheel;
public event EventHandler MouseHWheel;
public event KeyEventHandler KeyDown;
public event KeyEventHandler KeyUp;
public event KeyPressEventHandler KeyPress;
public event EventHandler Move;
public event EventHandler Resize;
public event STNodeControlPaintEventHandler Paint;
protected internal virtual void OnGotFocus(EventArgs e) {
if (this.GotFocus != null) this.GotFocus(this, e);
}
protected internal virtual void OnLostFocus(EventArgs e) {
if (this.LostFocus != null) this.LostFocus(this, e);
}
protected internal virtual void OnMouseEnter(EventArgs e) {
if (this.MouseEnter != null) this.MouseEnter(this, e);
}
protected internal virtual void OnMouseLeave(EventArgs e) {
if (this.MouseLeave != null) this.MouseLeave(this, e);
}
protected internal virtual void OnMouseDown(MouseEventArgs e) {
if (this.MouseDown != null) this.MouseDown(this, e);
}
protected internal virtual void OnMouseMove(MouseEventArgs e) {
if (this.MouseMove != null) this.MouseMove(this, e);
}
protected internal virtual void OnMouseUp(MouseEventArgs e) {
if (this.MouseUp != null) this.MouseUp(this, e);
}
protected internal virtual void OnMouseClick(MouseEventArgs e) {
if (this.MouseClick != null) this.MouseClick(this, e);
}
protected internal virtual void OnMouseWheel(MouseEventArgs e) {
if (this.MouseWheel != null) this.MouseWheel(this, e);
}
protected internal virtual void OnMouseHWheel(MouseEventArgs e) {
if (this.MouseHWheel != null) this.MouseHWheel(this, e);
}
protected internal virtual void OnKeyDown(KeyEventArgs e) {
if (this.KeyDown != null) this.KeyDown(this, e);
}
protected internal virtual void OnKeyUp(KeyEventArgs e) {
if (this.KeyUp != null) this.KeyUp(this, e);
}
protected internal virtual void OnKeyPress(KeyPressEventArgs e) {
if (this.KeyPress != null) this.KeyPress(this, e);
}
protected internal virtual void OnMove(EventArgs e) {
if (this.Move != null) this.Move(this, e);
}
protected internal virtual void OnResize(EventArgs e) {
if (this.Resize != null) this.Resize(this, e);
}
public IAsyncResult BeginInvoke(Delegate method) { return this.BeginInvoke(method, null); }
public IAsyncResult BeginInvoke(Delegate method, params object[] args) {
if (this._Owner == null) return null;
return this._Owner.BeginInvoke(method, args);
}
public object Invoke(Delegate method) { return this.Invoke(method, null); }
public object Invoke(Delegate method, params object[] args) {
if (this._Owner == null) return null;
return this._Owner.Invoke(method, args);
}
}
public delegate void STNodeControlPaintEventHandler(object sender, STNodeControlPaintEventArgs e);
public class STNodeControlPaintEventArgs : EventArgs
{
/// <summary>
/// 绘制工具
/// </summary>
public DrawingTools DrawingTools { get; private set; }
public STNodeControlPaintEventArgs(DrawingTools dt) {
this.DrawingTools = dt;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
/*
MIT License
Copyright (c) 2021 DebugST@crystal_lz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
* time: 2021-01-06
* Author: Crystal_lz
* blog: st233.com
* Github: DebugST.github.io
*/
namespace ST.Library.UI
{
public class STNodeControl
{
private STNode _Owner;
public STNode Owner {
get { return _Owner; }
internal set { _Owner = value; }
}
private int _Left;
public int Left {
get { return _Left; }
set {
_Left = value;
this.OnMove(new EventArgs());
this.Invalidate();
}
}
private int _Top;
public int Top {
get { return _Top; }
set {
_Top = value;
this.OnMove(new EventArgs());
this.Invalidate();
}
}
private int _Width;
public int Width {
get { return _Width; }
set {
_Width = value;
this.OnResize(new EventArgs());
this.Invalidate();
}
}
private int _Height;
public int Height {
get { return _Height; }
set {
_Height = value;
this.OnResize(new EventArgs());
this.Invalidate();
}
}
public Point Location {
get { return new Point(this._Left, this._Top); }
}
public Size Size {
get { return new Size(this._Width, this._Height); }
}
public Rectangle DisplayRectangle {
get { return new Rectangle(this._Left, this._Top, this._Width, this._Height); }
}
public Rectangle ClientRectangle {
get { return new Rectangle(0, 0, this._Width, this._Height); }
}
private Color _BackColor = Color.FromArgb(127, 0, 0, 0);
public Color BackColor {
get { return _BackColor; }
set {
_BackColor = value;
this.Invalidate();
}
}
private Color _ForeColor = Color.White;
public Color ForeColor {
get { return _ForeColor; }
set {
_ForeColor = value;
this.Invalidate();
}
}
private string _Text = "STNCTRL";
public string Text {
get { return _Text; }
set {
_Text = value;
this.Invalidate();
}
}
private Font _Font;
public Font Font {
get { return _Font; }
set {
if (value == _Font) return;
if (value == null) throw new ArgumentNullException("值不能为空");
_Font = value;
this.Invalidate();
}
}
protected StringFormat m_sf;
public STNodeControl() {
m_sf = new StringFormat();
m_sf.Alignment = StringAlignment.Center;
m_sf.LineAlignment = StringAlignment.Center;
this._Font = new Font("courier new", 8.25f);
this.Width = 75;
this.Height = 23;
}
protected internal virtual void OnPaint(DrawingTools dt) {
Graphics g = dt.Graphics;
SolidBrush brush = dt.SolidBrush;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
brush.Color = this._BackColor;
g.FillRectangle(brush, 0, 0, this.Width, this.Height);
if (!string.IsNullOrEmpty(this._Text)) {
brush.Color = this._ForeColor;
g.DrawString(this._Text, this._Font, brush, this.ClientRectangle, m_sf);
}
}
public void Invalidate() {
if (this._Owner == null) return;
this._Owner.Invalidate(new Rectangle(this._Left, this._Top + this._Owner.TitleHeight, this.Width, this.Height));
}
public void Invalidate(Rectangle rect) {
if (this._Owner == null) return;
this._Owner.Invalidate(this.RectangleToParent(rect));
}
public Rectangle RectangleToParent(Rectangle rect) {
return new Rectangle(this._Left, this._Top + this._Owner.TitleHeight, this.Width, this.Height);
}
public event EventHandler GotFocus;
public event EventHandler LostFocus;
public event EventHandler MouseEnter;
public event EventHandler MouseLeave;
public event MouseEventHandler MouseDown;
public event MouseEventHandler MouseMove;
public event MouseEventHandler MouseUp;
public event MouseEventHandler MouseClick;
public event MouseEventHandler MouseWheel;
public event EventHandler MouseHWheel;
public event KeyEventHandler KeyDown;
public event KeyEventHandler KeyUp;
public event KeyPressEventHandler KeyPress;
public event EventHandler Move;
public event EventHandler Resize;
protected internal virtual void OnGotFocus(EventArgs e) {
if (this.GotFocus != null) this.GotFocus(this, e);
}
protected internal virtual void OnLostFocus(EventArgs e) {
if (this.LostFocus != null) this.LostFocus(this, e);
}
protected internal virtual void OnMouseEnter(EventArgs e) {
if (this.MouseEnter != null) this.MouseEnter(this, e);
}
protected internal virtual void OnMouseLeave(EventArgs e) {
if (this.MouseLeave != null) this.MouseLeave(this, e);
}
protected internal virtual void OnMouseDown(MouseEventArgs e) {
if (this.MouseDown != null) this.MouseDown(this, e);
}
protected internal virtual void OnMouseMove(MouseEventArgs e) {
if (this.MouseMove != null) this.MouseMove(this, e);
}
protected internal virtual void OnMouseUp(MouseEventArgs e) {
if (this.MouseUp != null) this.MouseUp(this, e);
}
protected internal virtual void OnMouseClick(MouseEventArgs e) {
if (this.MouseClick != null) this.MouseClick(this, e);
}
protected internal virtual void OnMouseWheel(MouseEventArgs e) {
if (this.MouseWheel != null) this.MouseWheel(this, e);
}
protected internal virtual void OnMouseHWheel(MouseEventArgs e) {
if (this.MouseHWheel != null) this.MouseHWheel(this, e);
}
protected internal virtual void OnKeyDown(KeyEventArgs e) {
if (this.KeyDown != null) this.KeyDown(this, e);
}
protected internal virtual void OnKeyUp(KeyEventArgs e) {
if (this.KeyUp != null) this.KeyUp(this, e);
}
protected internal virtual void OnKeyPress(KeyPressEventArgs e) {
if (this.KeyPress != null) this.KeyPress(this, e);
}
protected internal virtual void OnMove(EventArgs e) {
if (this.Move != null) this.Move(this, e);
}
protected internal virtual void OnResize(EventArgs e) {
if (this.Resize != null) this.Resize(this, e);
}
public IAsyncResult BeginInvoke(Delegate method) { return this.BeginInvoke(method, null); }
public IAsyncResult BeginInvoke(Delegate method, params object[] args) {
if (this._Owner == null) return null;
return this._Owner.BeginInvoke(method, args);
}
public object Invoke(Delegate method) { return this.Invoke(method, null); }
public object Invoke(Delegate method, params object[] args) {
if (this._Owner == null) return null;
return this._Owner.Invoke(method, args);
}
}
}

View File

@ -1,214 +1,214 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace ST.Library.UI.NodeEditor
{
public class STNodeControlCollection: IList, ICollection, IEnumerable
{
/*
* STNode中 访
*/
private int _Count;
public int Count { get { return _Count; } }
private STNodeControl[] m_controls;
private STNode m_owner;
internal STNodeControlCollection(STNode owner) {
if (owner == null) throw new ArgumentNullException("所有者不能为空");
m_owner = owner;
m_controls = new STNodeControl[4];
}
public int Add(STNodeControl control) {
if (control == null) throw new ArgumentNullException("添加对象不能为空");
this.EnsureSpace(1);
int nIndex = this.IndexOf(control);
if (-1 == nIndex) {
nIndex = this._Count;
control.Owner = m_owner;
m_controls[this._Count++] = control;
this.Redraw();
}
return nIndex;
}
public void AddRange(STNodeControl[] controls) {
if (controls == null) throw new ArgumentNullException("添加对象不能为空");
this.EnsureSpace(controls.Length);
foreach (var op in controls) {
if (op == null) throw new ArgumentNullException("添加对象不能为空");
if (-1 == this.IndexOf(op)) {
op.Owner = m_owner;
m_controls[this._Count++] = op;
}
}
this.Redraw();
}
public void Clear() {
for (int i = 0; i < this._Count; i++) m_controls[i].Owner = null;
this._Count = 0;
m_controls = new STNodeControl[4];
this.Redraw();
}
public bool Contains(STNodeControl option) {
return this.IndexOf(option) != -1;
}
public int IndexOf(STNodeControl option) {
return Array.IndexOf<STNodeControl>(m_controls, option);
}
public void Insert(int index, STNodeControl control) {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("索引越界");
if (control == null)
throw new ArgumentNullException("插入对象不能为空");
this.EnsureSpace(1);
for (int i = this._Count; i > index; i--)
m_controls[i] = m_controls[i - 1];
control.Owner = m_owner;
m_controls[index] = control;
this._Count++;
this.Redraw();
}
public bool IsFixedSize {
get { return false; }
}
public bool IsReadOnly {
get { return false; }
}
public void Remove(STNodeControl control) {
int nIndex = this.IndexOf(control);
if (nIndex != -1) this.RemoveAt(nIndex);
}
public void RemoveAt(int index) {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("索引越界");
this._Count--;
m_controls[index].Owner = null;
for (int i = index, Len = this._Count; i < Len; i++)
m_controls[i] = m_controls[i + 1];
this.Redraw();
}
public STNodeControl this[int index] {
get {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("索引越界");
return m_controls[index];
}
set { throw new InvalidOperationException("禁止重新赋值元素"); }
}
public void CopyTo(Array array, int index) {
if (array == null)
throw new ArgumentNullException("数组不能为空");
m_controls.CopyTo(array, index);
}
public bool IsSynchronized {
get { return true; }
}
public object SyncRoot {
get { return this; }
}
public IEnumerator GetEnumerator() {
for (int i = 0, Len = this._Count; i < Len; i++)
yield return m_controls[i];
}
/// <summary>
/// 确认空间是否足够 空间不足扩大容量
/// </summary>
/// <param name="elements">需要增加的个数</param>
private void EnsureSpace(int elements) {
if (elements + this._Count > m_controls.Length) {
STNodeControl[] arrTemp = new STNodeControl[Math.Max(m_controls.Length * 2, elements + this._Count)];
m_controls.CopyTo(arrTemp, 0);
m_controls = arrTemp;
}
}
protected void Redraw() {
if (m_owner != null && m_owner.Owner != null) {
//m_owner.BuildSize();
m_owner.Owner.Invalidate(m_owner.Owner.CanvasToControl(m_owner.Rectangle));
}
}
//===================================================================================
int IList.Add(object value) {
return this.Add((STNodeControl)value);
}
void IList.Clear() {
this.Clear();
}
bool IList.Contains(object value) {
return this.Contains((STNodeControl)value);
}
int IList.IndexOf(object value) {
return this.IndexOf((STNodeControl)value);
}
void IList.Insert(int index, object value) {
this.Insert(index, (STNodeControl)value);
}
bool IList.IsFixedSize {
get { return this.IsFixedSize; }
}
bool IList.IsReadOnly {
get { return this.IsReadOnly; }
}
void IList.Remove(object value) {
this.Remove((STNodeControl)value);
}
void IList.RemoveAt(int index) {
this.RemoveAt(index);
}
object IList.this[int index] {
get {
return this[index];
}
set {
this[index] = (STNodeControl)value;
}
}
void ICollection.CopyTo(Array array, int index) {
this.CopyTo(array, index);
}
int ICollection.Count {
get { return this._Count; }
}
bool ICollection.IsSynchronized {
get { return this.IsSynchronized; }
}
object ICollection.SyncRoot {
get { return this.SyncRoot; }
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace ST.Library.UI
{
public class STNodeControlCollection: IList, ICollection, IEnumerable
{
/*
* STNode中 访
*/
private int _Count;
public int Count { get { return _Count; } }
private STNodeControl[] m_controls;
private STNode m_owner;
internal STNodeControlCollection(STNode owner) {
if (owner == null) throw new ArgumentNullException("所有者不能为空");
m_owner = owner;
m_controls = new STNodeControl[4];
}
public int Add(STNodeControl control) {
if (control == null) throw new ArgumentNullException("添加对象不能为空");
this.EnsureSpace(1);
int nIndex = this.IndexOf(control);
if (-1 == nIndex) {
nIndex = this._Count;
control.Owner = m_owner;
m_controls[this._Count++] = control;
this.Redraw();
}
return nIndex;
}
public void AddRange(STNodeControl[] controls) {
if (controls == null) throw new ArgumentNullException("添加对象不能为空");
this.EnsureSpace(controls.Length);
foreach (var op in controls) {
if (op == null) throw new ArgumentNullException("添加对象不能为空");
if (-1 == this.IndexOf(op)) {
op.Owner = m_owner;
m_controls[this._Count++] = op;
}
}
this.Redraw();
}
public void Clear() {
for (int i = 0; i < this._Count; i++) m_controls[i].Owner = null;
this._Count = 0;
m_controls = new STNodeControl[4];
this.Redraw();
}
public bool Contains(STNodeControl option) {
return this.IndexOf(option) != -1;
}
public int IndexOf(STNodeControl option) {
return Array.IndexOf<STNodeControl>(m_controls, option);
}
public void Insert(int index, STNodeControl control) {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("索引越界");
if (control == null)
throw new ArgumentNullException("插入对象不能为空");
this.EnsureSpace(1);
for (int i = this._Count; i > index; i--)
m_controls[i] = m_controls[i - 1];
control.Owner = m_owner;
m_controls[index] = control;
this._Count++;
this.Redraw();
}
public bool IsFixedSize {
get { return false; }
}
public bool IsReadOnly {
get { return false; }
}
public void Remove(STNodeControl control) {
int nIndex = this.IndexOf(control);
if (nIndex != -1) this.RemoveAt(nIndex);
}
public void RemoveAt(int index) {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("索引越界");
this._Count--;
m_controls[index].Owner = null;
for (int i = index, Len = this._Count; i < Len; i++)
m_controls[i] = m_controls[i + 1];
this.Redraw();
}
public STNodeControl this[int index] {
get {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("索引越界");
return m_controls[index];
}
set { throw new InvalidOperationException("禁止重新赋值元素"); }
}
public void CopyTo(Array array, int index) {
if (array == null)
throw new ArgumentNullException("数组不能为空");
m_controls.CopyTo(array, index);
}
public bool IsSynchronized {
get { return true; }
}
public object SyncRoot {
get { return this; }
}
public IEnumerator GetEnumerator() {
for (int i = 0, Len = this._Count; i < Len; i++)
yield return m_controls[i];
}
/// <summary>
/// 确认空间是否足够 空间不足扩大容量
/// </summary>
/// <param name="elements">需要增加的个数</param>
private void EnsureSpace(int elements) {
if (elements + this._Count > m_controls.Length) {
STNodeControl[] arrTemp = new STNodeControl[Math.Max(m_controls.Length * 2, elements + this._Count)];
m_controls.CopyTo(arrTemp, 0);
m_controls = arrTemp;
}
}
protected void Redraw() {
if (m_owner != null && m_owner.Owner != null) {
//m_owner.BuildSize();
m_owner.Owner.Invalidate(m_owner.Owner.CanvasToControl(m_owner.Rectangle));
}
}
//===================================================================================
int IList.Add(object value) {
return this.Add((STNodeControl)value);
}
void IList.Clear() {
this.Clear();
}
bool IList.Contains(object value) {
return this.Contains((STNodeControl)value);
}
int IList.IndexOf(object value) {
return this.IndexOf((STNodeControl)value);
}
void IList.Insert(int index, object value) {
this.Insert(index, (STNodeControl)value);
}
bool IList.IsFixedSize {
get { return this.IsFixedSize; }
}
bool IList.IsReadOnly {
get { return this.IsReadOnly; }
}
void IList.Remove(object value) {
this.Remove((STNodeControl)value);
}
void IList.RemoveAt(int index) {
this.RemoveAt(index);
}
object IList.this[int index] {
get {
return this[index];
}
set {
this[index] = (STNodeControl)value;
}
}
void ICollection.CopyTo(Array array, int index) {
this.CopyTo(array, index);
}
int ICollection.Count {
get { return this._Count; }
}
bool ICollection.IsSynchronized {
get { return this.IsSynchronized; }
}
object ICollection.SyncRoot {
get { return this.SyncRoot; }
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
}
}

View File

@ -1,203 +1,198 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Drawing;
namespace ST.Library.UI.NodeEditor
{
public enum ConnectionStatus
{
/// <summary>
/// 不存在所有者
/// </summary>
[Description("不存在所有者")]
NoOwner,
/// <summary>
/// 相同的所有者
/// </summary>
[Description("相同的所有者")]
SameOwner,
/// <summary>
/// 均为输入或者输出选项
/// </summary>
[Description("均为输入或者输出选项")]
SameInputOrOutput,
/// <summary>
/// 不同的数据类型
/// </summary>
[Description("不同的数据类型")]
ErrorType,
/// <summary>
/// 单连接节点
/// </summary>
[Description("单连接节点")]
SingleOption,
/// <summary>
/// 出现环形路径
/// </summary>
[Description("出现环形路径")]
Loop,
/// <summary>
/// 已存在的连接
/// </summary>
[Description("已存在的连接")]
Exists,
/// <summary>
/// 空白选项
/// </summary>
[Description("空白选项")]
EmptyOption,
/// <summary>
/// 已经连接
/// </summary>
[Description("已经连接")]
Connected,
/// <summary>
/// 连接被断开
/// </summary>
[Description("连接被断开")]
DisConnected,
/// <summary>
/// 节点被锁定
/// </summary>
[Description("节点被锁定")]
Locked,
/// <summary>
/// 操作被拒绝
/// </summary>
[Description("操作被拒绝")]
Reject,
/// <summary>
/// 正在被连接
/// </summary>
[Description("正在被连接")]
Connecting,
/// <summary>
/// 正在断开连接
/// </summary>
[Description("正在断开连接")]
DisConnecting
}
public enum AlertLocation
{
Left,
Top,
Right,
Bottom,
Center,
LeftTop,
RightTop,
RightBottom,
LeftBottom,
}
public struct DrawingTools
{
public Graphics Graphics;
public Pen Pen;
public SolidBrush SolidBrush;
}
public enum CanvasMoveArgs //移动画布时需要的参数 查看->MoveCanvas()
{
Left = 1, //表示 仅移动 X 坐标
Top = 2, //表示 仅移动 Y 坐标
All = 4 //表示 X Y 同时移动
}
public struct NodeFindInfo
{
public STNode Node;
public STNodeOption NodeOption;
public string Mark;
public string[] MarkLines;
}
public struct ConnectionInfo
{
public STNodeOption Input;
public STNodeOption Output;
}
public delegate void STNodeOptionEventHandler(object sender, STNodeOptionEventArgs e);
public class STNodeOptionEventArgs : EventArgs
{
private STNodeOption _TargetOption;
/// <summary>
/// 触发此事件的对应Option
/// </summary>
public STNodeOption TargetOption {
get { return _TargetOption; }
}
private ConnectionStatus _Status;
/// <summary>
/// Option之间的连线状态
/// </summary>
public ConnectionStatus Status {
get { return _Status; }
internal set { _Status = value; }
}
private bool _IsSponsor;
/// <summary>
/// 是否为此次行为的发起者
/// </summary>
public bool IsSponsor {
get { return _IsSponsor; }
}
public STNodeOptionEventArgs(bool isSponsor, STNodeOption opTarget, ConnectionStatus cr) {
this._IsSponsor = isSponsor;
this._TargetOption = opTarget;
this._Status = cr;
}
}
public delegate void STNodeEditorEventHandler(object sender, STNodeEditorEventArgs e);
public delegate void STNodeEditorOptionEventHandler(object sender, STNodeEditorOptionEventArgs e);
public class STNodeEditorEventArgs : EventArgs
{
private STNode _Node;
public STNode Node {
get { return _Node; }
}
public STNodeEditorEventArgs(STNode node) {
this._Node = node;
}
}
public class STNodeEditorOptionEventArgs : STNodeOptionEventArgs
{
private STNodeOption _CurrentOption;
/// <summary>
/// 主动触发事件的Option
/// </summary>
public STNodeOption CurrentOption {
get { return _CurrentOption; }
}
private bool _Continue = true;
/// <summary>
/// 是否继续向下操作 用于Begin(Connecting/DisConnecting)是否继续向后操作
/// </summary>
public bool Continue {
get { return _Continue; }
set { _Continue = value; }
}
public STNodeEditorOptionEventArgs(STNodeOption opTarget, STNodeOption opCurrent, ConnectionStatus cr)
: base(false, opTarget, cr) {
this._CurrentOption = opCurrent;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Drawing;
namespace ST.Library.UI
{
public enum ConnectionStatus
{
/// <summary>
/// 不存在所有者
/// </summary>
[Description("不存在所有者")]
NoOwner,
/// <summary>
/// 相同的所有者
/// </summary>
[Description("相同的所有者")]
SameOwner,
/// <summary>
/// 均为输入或者输出选项
/// </summary>
[Description("均为输入或者输出选项")]
SameInputOrOutput,
/// <summary>
/// 不同的数据类型
/// </summary>
[Description("不同的数据类型")]
ErrorType,
/// <summary>
/// 单连接节点
/// </summary>
[Description("单连接节点")]
SingleOption,
/// <summary>
/// 出现环形路径
/// </summary>
[Description("出现环形路径")]
Loop,
/// <summary>
/// 已存在的连接
/// </summary>
[Description("已存在的连接")]
Exists,
/// <summary>
/// 已经连接
/// </summary>
[Description("已经连接")]
Connected,
/// <summary>
/// 连接被断开
/// </summary>
[Description("连接被断开")]
DisConnected,
/// <summary>
/// 节点被锁定
/// </summary>
[Description("节点被锁定")]
Locked,
/// <summary>
/// 操作被拒绝
/// </summary>
[Description("操作被拒绝")]
Reject,
/// <summary>
/// 正在被连接
/// </summary>
[Description("正在被连接")]
Connecting,
/// <summary>
/// 正在断开连接
/// </summary>
[Description("正在断开连接")]
DisConnecting
}
public enum AlertLocation
{
Left,
Top,
Right,
Bottom,
Center,
LeftTop,
RightTop,
RightBottom,
LeftBottom,
}
public struct DrawingTools
{
public Graphics Graphics;
public Pen Pen;
public SolidBrush SolidBrush;
}
public enum CanvasMoveArgs //移动画布时需要的参数 查看->MoveCanvas()
{
Left = 1, //表示 仅移动 X 坐标
Top = 2, //表示 仅移动 Y 坐标
All = 4 //表示 X Y 同时移动
}
public struct NodeFindInfo
{
public STNode Node;
public STNodeOption NodeOption;
public string Mark;
public string[] MarkLines;
}
public struct ConnectionInfo
{
public STNodeOption Input;
public STNodeOption Output;
}
public delegate void STNodeOptionEventHandler(object sender, STNodeOptionEventArgs e);
public class STNodeOptionEventArgs : EventArgs
{
private STNodeOption _TargetOption;
/// <summary>
/// 触发此事件的对应Option
/// </summary>
public STNodeOption TargetOption {
get { return _TargetOption; }
}
private ConnectionStatus _Status;
/// <summary>
/// Option之间的连线状态
/// </summary>
public ConnectionStatus Status {
get { return _Status; }
internal set { _Status = value; }
}
private bool _IsSponsor;
/// <summary>
/// 是否为此次行为的发起者
/// </summary>
public bool IsSponsor {
get { return _IsSponsor; }
}
public STNodeOptionEventArgs(bool isSponsor, STNodeOption opTarget, ConnectionStatus cr) {
this._IsSponsor = isSponsor;
this._TargetOption = opTarget;
this._Status = cr;
}
}
public delegate void STNodeEditorEventHandler(object sender, STNodeEditorEventArgs e);
public delegate void STNodeEditorOptionEventHandler(object sender, STNodeEditorOptionEventArgs e);
public class STNodeEditorEventArgs : EventArgs
{
private STNode _Node;
public STNode Node {
get { return _Node; }
}
public STNodeEditorEventArgs(STNode node) {
this._Node = node;
}
}
public class STNodeEditorOptionEventArgs : STNodeOptionEventArgs
{
private STNodeOption _CurrentOption;
/// <summary>
/// 主动触发事件的Option
/// </summary>
public STNodeOption CurrentOption {
get { return _CurrentOption; }
}
private bool _Continue = true;
/// <summary>
/// 是否继续向下操作 用于Begin(Connecting/DisConnecting)是否继续向后操作
/// </summary>
public bool Continue {
get { return _Continue; }
set { _Continue = value; }
}
public STNodeEditorOptionEventArgs(STNodeOption opTarget, STNodeOption opCurrent, ConnectionStatus cr)
: base(false, opTarget, cr) {
this._CurrentOption = opCurrent;
}
}
}

View File

@ -1,192 +1,175 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
/*
MIT License
Copyright (c) 2021 DebugST@crystal_lz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
* create: 2021-12-08
* modify: 2021-03-02
* Author: Crystal_lz
* blog: http://st233.com
* Gitee: https://gitee.com/DebugST
* Github: https://github.com/DebugST
*/
namespace ST.Library.UI.NodeEditor
{
public class STNodeHub : STNode
{
private bool m_bSingle;
private string m_strIn;
private string m_strOut;
public STNodeHub() : this(false, "IN", "OUT") { }
public STNodeHub(bool bSingle) : this(bSingle, "IN", "OUT") { }
public STNodeHub(bool bSingle, string strTextIn, string strTextOut) {
m_bSingle = bSingle;
m_strIn = strTextIn;
m_strOut = strTextOut;
this.Addhub();
this.Title = "HUB";
this.AutoSize = false;
this.TitleColor = System.Drawing.Color.FromArgb(200, System.Drawing.Color.DarkOrange);
}
protected override void OnOwnerChanged() {
base.OnOwnerChanged();
if (this.Owner == null) return;
using (Graphics g = this.Owner.CreateGraphics()) {
this.Width = base.GetDefaultNodeSize(g).Width;
}
}
private void Addhub() {
var input = new STNodeHubOption(m_strIn, typeof(object), m_bSingle);
var output = new STNodeHubOption(m_strOut, typeof(object), false);
this.InputOptions.Add(input);
this.OutputOptions.Add(output);
input.Connected += new STNodeOptionEventHandler(input_Connected);
input.DataTransfer += new STNodeOptionEventHandler(input_DataTransfer);
input.DisConnected += new STNodeOptionEventHandler(input_DisConnected);
output.Connected += new STNodeOptionEventHandler(output_Connected);
output.DisConnected += new STNodeOptionEventHandler(output_DisConnected);
this.Height = this.TitleHeight + this.InputOptions.Count * 20;
}
void output_DisConnected(object sender, STNodeOptionEventArgs e) {
STNodeOption op = sender as STNodeOption;
if (op.ConnectionCount != 0) return;
int nIndex = this.OutputOptions.IndexOf(op);
if (this.InputOptions[nIndex].ConnectionCount != 0) return;
this.InputOptions.RemoveAt(nIndex);
this.OutputOptions.RemoveAt(nIndex);
if (this.Owner != null) this.Owner.BuildLinePath();
this.Height -= 20;
}
void output_Connected(object sender, STNodeOptionEventArgs e) {
STNodeOption op = sender as STNodeOption;
int nIndex = this.OutputOptions.IndexOf(op);
var t = typeof(object);
if (this.InputOptions[nIndex].DataType == t) {
op.DataType = e.TargetOption.DataType;
this.InputOptions[nIndex].DataType = op.DataType;
foreach (STNodeOption v in this.InputOptions) {
if (v.DataType == t) return;
}
this.Addhub();
}
}
void input_DisConnected(object sender, STNodeOptionEventArgs e) {
STNodeOption op = sender as STNodeOption;
if (op.ConnectionCount != 0) return;
int nIndex = this.InputOptions.IndexOf(op);
if (this.OutputOptions[nIndex].ConnectionCount != 0) return;
this.InputOptions.RemoveAt(nIndex);
this.OutputOptions.RemoveAt(nIndex);
if (this.Owner != null) this.Owner.BuildLinePath();
this.Height -= 20;
}
void input_DataTransfer(object sender, STNodeOptionEventArgs e) {
STNodeOption op = sender as STNodeOption;
int nIndex = this.InputOptions.IndexOf(op);
if (e.Status != ConnectionStatus.Connected)
this.OutputOptions[nIndex].Data = null;
else
this.OutputOptions[nIndex].Data = e.TargetOption.Data;
this.OutputOptions[nIndex].TransferData();
}
void input_Connected(object sender, STNodeOptionEventArgs e) {
STNodeOption op = sender as STNodeOption;
int nIndex = this.InputOptions.IndexOf(op);
var t = typeof(object);
if (op.DataType == t) {
op.DataType = e.TargetOption.DataType;
this.OutputOptions[nIndex].DataType = op.DataType;
foreach (STNodeOption v in this.InputOptions) {
if (v.DataType == t) return;
}
this.Addhub();
} else {
//this.OutputOptions[nIndex].Data = e.TargetOption.Data;
this.OutputOptions[nIndex].TransferData(e.TargetOption.Data);
}
}
protected override void OnSaveNode(Dictionary<string, byte[]> dic) {
dic.Add("count", BitConverter.GetBytes(this.InputOptionsCount));
//dic.Add("single", new byte[] { (byte)(m_bSingle ? 1 : 0) });
//dic.Add("strin", Encoding.UTF8.GetBytes(m_strIn));
//dic.Add("strout", Encoding.UTF8.GetBytes(m_strOut));
}
protected internal override void OnLoadNode(Dictionary<string, byte[]> dic) {
base.OnLoadNode(dic);
int nCount = BitConverter.ToInt32(dic["count"], 0);
while (this.InputOptionsCount < nCount && this.InputOptionsCount != nCount) this.Addhub();
//m_bSingle = dic["single"][0] == 1;
//m_strIn = Encoding.UTF8.GetString(dic["strin"]);
//m_strOut = Encoding.UTF8.GetString(dic["strout"]);
}
public class STNodeHubOption : STNodeOption
{
public STNodeHubOption(string strText, Type dataType, bool bSingle) : base(strText, dataType, bSingle) { }
public override ConnectionStatus ConnectOption(STNodeOption op) {
var t = typeof(object);
if (this.DataType != t) return base.ConnectOption(op);
this.DataType = op.DataType;
var ret = base.ConnectOption(op);
if (ret != ConnectionStatus.Connected) this.DataType = t;
return ret;
}
public override ConnectionStatus CanConnect(STNodeOption op) {
if (op == STNodeOption.Empty) return ConnectionStatus.EmptyOption;
if (this.DataType != typeof(object)) return base.CanConnect(op);
if (this.IsInput == op.IsInput) return ConnectionStatus.SameInputOrOutput;
if (op.Owner == null || this.Owner == null) return ConnectionStatus.NoOwner;
if (op.Owner == this.Owner) return ConnectionStatus.SameOwner;
if (this.Owner.LockOption || op.Owner.LockOption) return ConnectionStatus.Locked;
if (this.IsSingle && m_hs_connected.Count == 1) return ConnectionStatus.SingleOption;
if (op.IsInput && STNodeEditor.CanFindNodePath(op.Owner, this.Owner)) return ConnectionStatus.Loop;
if (m_hs_connected.Contains(op)) return ConnectionStatus.Exists;
if (op.DataType == typeof(object)) return ConnectionStatus.ErrorType;
if (!this.IsInput) return ConnectionStatus.Connected;
foreach (STNodeOption owner_input in this.Owner.InputOptions) {
foreach (STNodeOption o in owner_input.ConnectedOption) {
if (o == op) return ConnectionStatus.Exists;
}
}
return ConnectionStatus.Connected; ;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/*
MIT License
Copyright (c) 2021 DebugST@crystal_lz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
* time: 2021-01-06
* Author: Crystal_lz
* blog: st233.com
* Github: DebugST.github.io
*/
namespace ST.Library.UI
{
public class STNodeHub : STNode
{
private bool m_bSingle;
private string m_strIn;
private string m_strOut;
public STNodeHub() : this(false, "IN", "OUT") { }
public STNodeHub(bool bSingle) : this(bSingle, "IN", "OUT") { }
public STNodeHub(bool bSingle, string strTextIn, string strTextOut) {
m_bSingle = bSingle;
m_strIn = strTextIn;
m_strOut = strTextOut;
this.Addhub();
this.Title = "HUB";
this.TitleColor = System.Drawing.Color.FromArgb(200, System.Drawing.Color.DarkOrange);
}
private void Addhub() {
var input = new STNodeHubOption(m_strIn, typeof(object), m_bSingle);
var output = new STNodeHubOption(m_strOut, typeof(object), false);
this.InputOptions.Add(input);
this.OutputOptions.Add(output);
input.Connected += new STNodeOptionEventHandler(input_Connected);
input.DataTransfer += new STNodeOptionEventHandler(input_DataTransfer);
input.DisConnected += new STNodeOptionEventHandler(input_DisConnected);
output.Connected += new STNodeOptionEventHandler(output_Connected);
output.DisConnected += new STNodeOptionEventHandler(output_DisConnected);
}
void output_DisConnected(object sender, STNodeOptionEventArgs e) {
STNodeOption op = sender as STNodeOption;
if (op.ConnectionCount != 0) return;
int nIndex = this.OutputOptions.IndexOf(op);
if (this.InputOptions[nIndex].ConnectionCount != 0) return;
this.InputOptions.RemoveAt(nIndex);
this.OutputOptions.RemoveAt(nIndex);
if (this.Owner != null) this.Owner.BuildLinePath();
}
void output_Connected(object sender, STNodeOptionEventArgs e) {
STNodeOption op = sender as STNodeOption;
int nIndex = this.OutputOptions.IndexOf(op);
var t = typeof(object);
if (this.InputOptions[nIndex].DataType == t) {
op.DataType = e.TargetOption.DataType;
this.InputOptions[nIndex].DataType = op.DataType;
foreach (STNodeOption v in this.InputOptions) {
if (v.DataType == t) return;
}
this.Addhub();
}
}
void input_DisConnected(object sender, STNodeOptionEventArgs e) {
STNodeOption op = sender as STNodeOption;
if (op.ConnectionCount != 0) return;
int nIndex = this.InputOptions.IndexOf(op);
if (this.OutputOptions[nIndex].ConnectionCount != 0) return;
this.InputOptions.RemoveAt(nIndex);
this.OutputOptions.RemoveAt(nIndex);
if (this.Owner != null) this.Owner.BuildLinePath();
}
void input_DataTransfer(object sender, STNodeOptionEventArgs e) {
STNodeOption op = sender as STNodeOption;
int nIndex = this.InputOptions.IndexOf(op);
if (e.Status != ConnectionStatus.Connected)
this.OutputOptions[nIndex].Data = null;
else
this.OutputOptions[nIndex].Data = e.TargetOption.Data;
this.OutputOptions[nIndex].TransferData();
}
void input_Connected(object sender, STNodeOptionEventArgs e) {
STNodeOption op = sender as STNodeOption;
int nIndex = this.InputOptions.IndexOf(op);
var t = typeof(object);
if (op.DataType == t) {
op.DataType = e.TargetOption.DataType;
this.OutputOptions[nIndex].DataType = op.DataType;
foreach (STNodeOption v in this.InputOptions) {
if (v.DataType == t) return;
}
this.Addhub();
} else {
//this.OutputOptions[nIndex].Data = e.TargetOption.Data;
this.OutputOptions[nIndex].TransferData(e.TargetOption.Data);
}
}
protected override void OnSaveNode(Dictionary<string,byte[]> dic) {
dic.Add("count", BitConverter.GetBytes(this.InputOptionsCount));
dic.Add("single", new byte[] { (byte)(m_bSingle ? 1 : 0) });
dic.Add("strin", Encoding.UTF8.GetBytes(m_strIn));
dic.Add("strout", Encoding.UTF8.GetBytes(m_strOut));
}
protected internal override void OnLoadNode(Dictionary<string, byte[]> dic) {
base.OnLoadNode(dic);
int nCount = BitConverter.ToInt32(dic["count"], 0);
while (this.InputOptionsCount < nCount && this.InputOptionsCount != nCount) this.Addhub();
m_bSingle = dic["single"][0] == 1;
m_strIn = Encoding.UTF8.GetString(dic["strin"]);
m_strOut = Encoding.UTF8.GetString(dic["strout"]);
}
public class STNodeHubOption : STNodeOption
{
public STNodeHubOption(string strText, Type dataType, bool bSingle) : base(strText, dataType, bSingle) { }
public override ConnectionStatus ConnectOption(STNodeOption op) {
var t = typeof(object);
if (this.DataType != t) return base.ConnectOption(op);
this.DataType = op.DataType;
var ret = base.ConnectOption(op);
if (ret != ConnectionStatus.Connected) this.DataType = t;
return ret;
}
public override ConnectionStatus CanConnect(STNodeOption op) {
if (this.DataType != typeof(object)) return base.CanConnect(op);
if (this.IsInput == op.IsInput) return ConnectionStatus.SameInputOrOutput;
if (op.Owner == this.Owner) return ConnectionStatus.SameOwner;
if (op.Owner == null || this.Owner == null) return ConnectionStatus.NoOwner;
if (this.IsSingle && m_hs_connected.Count == 1) return ConnectionStatus.SingleOption;
if (op.IsInput && STNodeEditor.CanFindNodePath(op.Owner, this.Owner)) return ConnectionStatus.Loop;
if (m_hs_connected.Contains(op)) return ConnectionStatus.Exists;
if (op.DataType == typeof(object)) return ConnectionStatus.ErrorType;
if (!this.IsInput) return ConnectionStatus.Connected;
foreach (STNodeOption owner_input in this.Owner.InputOptions) {
foreach (STNodeOption o in owner_input.ConnectedOption) {
if (o == op) return ConnectionStatus.Exists;
}
}
return ConnectionStatus.Connected; ;
}
}
}
}

View File

@ -1,456 +1,430 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace ST.Library.UI.NodeEditor
{
public class STNodeOption
{
#region Properties
public static readonly STNodeOption Empty = new STNodeOption();
private STNode _Owner;
/// <summary>
/// 获取当前 Option 所属的 Node
/// </summary>
public STNode Owner {
get { return _Owner; }
internal set {
if (value == _Owner) return;
if (_Owner != null) this.DisConnectionAll(); //当所有者变更时 断开当前所有连接
_Owner = value;
}
}
private bool _IsSingle;
/// <summary>
/// 获取当前 Option 是否仅能被连接一次
/// </summary>
public bool IsSingle {
get { return _IsSingle; }
}
private bool _IsInput;
/// <summary>
/// 获取当前 Option 是否是输入选项
/// </summary>
public bool IsInput {
get { return _IsInput; }
internal set { _IsInput = value; }
}
private Color _TextColor = Color.White;
/// <summary>
/// 获取或设置当前 Option 文本颜色
/// </summary>
public Color TextColor {
get { return _TextColor; }
internal set {
if (value == _TextColor) return;
_TextColor = value;
this.Invalidate();
}
}
private Color _DotColor = Color.Transparent;
/// <summary>
/// 获取或设置当前 Option 连接点的颜色
/// </summary>
public Color DotColor {
get { return _DotColor; }
internal set {
if (value == _DotColor) return;
_DotColor = value;
this.Invalidate();
}
}
private string _Text;
/// <summary>
/// 获取或设置当前 Option 显示文本
/// 当AutoSize被设置时 无法修改此属性
/// </summary>
public string Text {
get { return _Text; }
internal set {
if (value == _Text) return;
_Text = value;
if (this._Owner == null) return;
this._Owner.BuildSize(true, true, true);
}
}
private int _DotLeft;
/// <summary>
/// 获取当前 Option 连接点的左边坐标
/// </summary>
public int DotLeft {
get { return _DotLeft; }
internal set { _DotLeft = value; }
}
private int _DotTop;
/// <summary>
/// 获取当前 Option 连接点的上边坐标
/// </summary>
public int DotTop {
get { return _DotTop; }
internal set { _DotTop = value; }
}
private int _DotSize;
/// <summary>
/// 获取当前 Option 连接点的宽度
/// </summary>
public int DotSize {
get { return _DotSize; }
protected set { _DotSize = value; }
}
private Rectangle _TextRectangle;
/// <summary>
/// 获取当前 Option 文本区域
/// </summary>
public Rectangle TextRectangle {
get { return _TextRectangle; }
internal set { _TextRectangle = value; }
}
private object _Data;
/// <summary>
/// 获取或者设置当前 Option 所包含的数据
/// </summary>
public object Data {
get { return _Data; }
set {
if (value != null) {
if (this._DataType == null) return;
var t = value.GetType();
if (t != this._DataType && !t.IsSubclassOf(this._DataType)) {
throw new ArgumentException("无效数据类型 数据类型必须为指定的数据类型或其子类");
}
}
_Data = value;
}
}
private Type _DataType;
/// <summary>
/// 获取当前 Option 数据类型
/// </summary>
public Type DataType {
get { return _DataType; }
internal set { _DataType = value; }
}
//private Rectangle _DotRectangle;
/// <summary>
/// 获取当前 Option 连接点的区域
/// </summary>
public Rectangle DotRectangle {
get {
return new Rectangle(this._DotLeft, this._DotTop, this._DotSize, this._DotSize);
}
}
/// <summary>
/// 获取当前 Option 被连接的个数
/// </summary>
public int ConnectionCount {
get { return m_hs_connected.Count; }
}
/// <summary>
/// 获取当前 Option 所连接的 Option 集合
/// </summary>
internal HashSet<STNodeOption> ConnectedOption {
get { return m_hs_connected; }
}
#endregion Properties
/// <summary>
/// 保存已经被连接的点
/// </summary>
protected HashSet<STNodeOption> m_hs_connected;
#region Constructor
private STNodeOption() { }
/// <summary>
/// 构造一个 Option
/// </summary>
/// <param name="strText">显示文本</param>
/// <param name="dataType">数据类型</param>
/// <param name="bSingle">是否为单连接</param>
public STNodeOption(string strText, Type dataType, bool bSingle) {
if (dataType == null) throw new ArgumentNullException("指定的数据类型不能为空");
this._DotSize = 10;
m_hs_connected = new HashSet<STNodeOption>();
this._DataType = dataType;
this._Text = strText;
this._IsSingle = bSingle;
}
#endregion Constructor
#region Event
/// <summary>
/// 当被连接时候发生
/// </summary>
public event STNodeOptionEventHandler Connected;
/// <summary>
/// 当连接开始发生时发生
/// </summary>
public event STNodeOptionEventHandler Connecting;
/// <summary>
/// 当连接断开时候发生
/// </summary>
public event STNodeOptionEventHandler DisConnected;
/// <summary>
/// 当连接开始断开时发生
/// </summary>
public event STNodeOptionEventHandler DisConnecting;
/// <summary>
/// 当有数据传递时候发生
/// </summary>
public event STNodeOptionEventHandler DataTransfer;
#endregion Event
#region protected
/// <summary>
/// 重绘整个控件
/// </summary>
protected void Invalidate() {
if (this._Owner == null) return;
this._Owner.Invalidate();
}
/*
*
* STNodeHub中输出节点就用到了事件
*
*/
protected internal virtual void OnConnected(STNodeOptionEventArgs e) {
if (this.Connected != null/* && this._IsInput*/) this.Connected(this, e);
}
protected internal virtual void OnConnecting(STNodeOptionEventArgs e) {
if (this.Connecting != null) this.Connecting(this, e);
}
protected internal virtual void OnDisConnected(STNodeOptionEventArgs e) {
if (this.DisConnected != null/* && this._IsInput*/) this.DisConnected(this, e);
}
protected internal virtual void OnDisConnecting(STNodeOptionEventArgs e) {
if (this.DisConnecting != null) this.DisConnecting(this, e);
}
protected internal virtual void OnDataTransfer(STNodeOptionEventArgs e) {
if (this.DataTransfer != null/* && this._IsInput*/) this.DataTransfer(this, e);
}
protected void STNodeEidtorConnected(STNodeEditorOptionEventArgs e) {
if (this._Owner == null) return;
if (this._Owner.Owner == null) return;
this._Owner.Owner.OnOptionConnected(e);
}
protected void STNodeEidtorDisConnected(STNodeEditorOptionEventArgs e) {
if (this._Owner == null) return;
if (this._Owner.Owner == null) return;
this._Owner.Owner.OnOptionDisConnected(e);
}
/// <summary>
/// 当前 Option 开始连接目标 Option
/// </summary>
/// <param name="op">需要连接的 Option</param>
/// <returns>是否允许继续操作</returns>
protected virtual bool ConnectingOption(STNodeOption op) {
if (this._Owner == null) return false;
if (this._Owner.Owner == null) return false;
STNodeEditorOptionEventArgs e = new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Connecting);
this._Owner.Owner.OnOptionConnecting(e);
this.OnConnecting(new STNodeOptionEventArgs(true, op, ConnectionStatus.Connecting));
op.OnConnecting(new STNodeOptionEventArgs(false, this, ConnectionStatus.Connecting));
return e.Continue;
}
/// <summary>
/// 当前 Option 开始断开目标 Option
/// </summary>
/// <param name="op">需要断开的 Option</param>
/// <returns>是否允许继续操作</returns>
protected virtual bool DisConnectingOption(STNodeOption op) {
if (this._Owner == null) return false;
if (this._Owner.Owner == null) return false;
STNodeEditorOptionEventArgs e = new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.DisConnecting);
this._Owner.Owner.OnOptionDisConnecting(e);
this.OnDisConnecting(new STNodeOptionEventArgs(true, op, ConnectionStatus.DisConnecting));
op.OnDisConnecting(new STNodeOptionEventArgs(false, this, ConnectionStatus.DisConnecting));
return e.Continue;
}
#endregion protected
#region public
/// <summary>
/// 当前 Option 连接目标 Option
/// </summary>
/// <param name="op">需要连接的 Option</param>
/// <returns>连接结果</returns>
public virtual ConnectionStatus ConnectOption(STNodeOption op) {
if (!this.ConnectingOption(op)) {
this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Reject));
return ConnectionStatus.Reject;
}
var v = this.CanConnect(op);
if (v != ConnectionStatus.Connected) {
this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v));
return v;
}
v = op.CanConnect(this);
if (v != ConnectionStatus.Connected) {
this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v));
return v;
}
op.AddConnection(this, false);
this.AddConnection(op, true);
this.ControlBuildLinePath();
this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v));
return v;
}
/// <summary>
/// 检测当前 Option 是否可以连接目标 Option
/// </summary>
/// <param name="op">需要连接的 Option</param>
/// <returns>检测结果</returns>
public virtual ConnectionStatus CanConnect(STNodeOption op) {
if (this == STNodeOption.Empty || op == STNodeOption.Empty) return ConnectionStatus.EmptyOption;
if (this._IsInput == op.IsInput) return ConnectionStatus.SameInputOrOutput;
if (op.Owner == null || this._Owner == null) return ConnectionStatus.NoOwner;
if (op.Owner == this._Owner) return ConnectionStatus.SameOwner;
if (this._Owner.LockOption || op._Owner.LockOption) return ConnectionStatus.Locked;
if (this._IsSingle && m_hs_connected.Count == 1) return ConnectionStatus.SingleOption;
if (op.IsInput && STNodeEditor.CanFindNodePath(op.Owner, this._Owner)) return ConnectionStatus.Loop;
if (m_hs_connected.Contains(op)) return ConnectionStatus.Exists;
if (this._IsInput && op._DataType != this._DataType && !op._DataType.IsSubclassOf(this._DataType)) return ConnectionStatus.ErrorType;
return ConnectionStatus.Connected;
}
/// <summary>
/// 当前 Option 断开目标 Option
/// </summary>
/// <param name="op">需要断开的 Option</param>
/// <returns></returns>
public virtual ConnectionStatus DisConnectOption(STNodeOption op) {
if (!this.DisConnectingOption(op)) {
this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Reject));
return ConnectionStatus.Reject;
}
if (op.Owner == null) return ConnectionStatus.NoOwner;
if (this._Owner == null) return ConnectionStatus.NoOwner;
if (op.Owner.LockOption && this._Owner.LockOption) {
this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Locked));
return ConnectionStatus.Locked;
}
op.RemoveConnection(this, false);
this.RemoveConnection(op, true);
this.ControlBuildLinePath();
this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.DisConnected));
return ConnectionStatus.DisConnected;
}
/// <summary>
/// 断开当前 Option 的所有连接
/// </summary>
public void DisConnectionAll() {
if (this._DataType == null) return;
var arr = m_hs_connected.ToArray();
foreach (var v in arr) {
this.DisConnectOption(v);
}
}
/// <summary>
/// 获取当前 Option 所连接的 Option 集合
/// </summary>
/// <returns>如果为null 则表示不存在所有者 否则返回集合</returns>
public List<STNodeOption> GetConnectedOption() {
if (this._DataType == null) return null;
if (!this._IsInput)
return m_hs_connected.ToList();
List<STNodeOption> lst = new List<STNodeOption>();
if (this._Owner == null) return null;
if (this._Owner.Owner == null) return null;
foreach (var v in this._Owner.Owner.GetConnectionInfo()) {
if (v.Output == this) lst.Add(v.Input);
}
return lst;
}
/// <summary>
/// 向当前 Option 所连接的所有 Option 投递数据
/// </summary>
public void TransferData() {
if (this._DataType == null) return;
foreach (var v in m_hs_connected) {
v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected));
}
}
/// <summary>
/// 向当前 Option 所连接的所有 Option 投递数据
/// </summary>
/// <param name="data">需要投递的数据</param>
public void TransferData(object data) {
if (this._DataType == null) return;
this.Data = data; //不是this._Data
foreach (var v in m_hs_connected) {
v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected));
}
}
/// <summary>
/// 向当前 Option 所连接的所有 Option 投递数据
/// </summary>
/// <param name="data">需要投递的数据</param>
/// <param name="bDisposeOld">是否释放旧数据</param>
public void TransferData(object data, bool bDisposeOld) {
if (bDisposeOld && this._Data != null) {
if (this._Data is IDisposable) ((IDisposable)this._Data).Dispose();
this._Data = null;
}
this.TransferData(data);
}
#endregion public
#region internal
private bool AddConnection(STNodeOption op, bool bSponsor) {
if (this._DataType == null) return false;
bool b = m_hs_connected.Add(op);
this.OnConnected(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected));
if (this._IsInput) this.OnDataTransfer(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected));
return b;
}
private bool RemoveConnection(STNodeOption op, bool bSponsor) {
if (this._DataType == null) return false;
bool b = false;
if (m_hs_connected.Contains(op)) {
b = m_hs_connected.Remove(op);
if (this._IsInput) this.OnDataTransfer(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.DisConnected));
this.OnDisConnected(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected));
}
return b;
}
#endregion internal
#region private
private void ControlBuildLinePath() {
if (this.Owner == null) return;
if (this.Owner.Owner == null) return;
this.Owner.Owner.BuildLinePath();
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace ST.Library.UI
{
public class STNodeOption
{
#region Properties
private STNode _Owner;
/// <summary>
/// 获取当前 Option 所属的 Node
/// </summary>
public STNode Owner {
get { return _Owner; }
internal set {
if (value == _Owner) return;
if (_Owner != null) this.DisConnectionAll(); //当所有者变更时 断开当前所有连接
_Owner = value;
}
}
private bool _IsSingle;
/// <summary>
/// 获取当前 Option 是否仅能被连接一次
/// </summary>
public bool IsSingle {
get { return _IsSingle; }
}
private bool _IsInput;
/// <summary>
/// 获取当前 Option 是否是输入选项
/// </summary>
public bool IsInput {
get { return _IsInput; }
internal set { _IsInput = value; }
}
private Color _TextColor = Color.White;
/// <summary>
/// 获取或设置当前 Option 文本颜色
/// </summary>
public Color TextColor {
get { return _TextColor; }
protected set {
if (value == _TextColor) return;
_TextColor = value;
this.Invalidate();
}
}
private Color _DotColor = Color.Transparent;
/// <summary>
/// 获取或设置当前 Option 连接点的颜色
/// </summary>
public Color DotColor {
get { return _DotColor; }
protected set {
if (value == _DotColor) return;
_DotColor = value;
this.Invalidate();
}
}
private string _Text;
/// <summary>
/// 获取或设置当前 Option 显示文本
/// </summary>
public string Text {
get { return _Text; }
protected set {
if (value == _Text) return;
_Text = value;
this.Invalidate();
}
}
private int _DotLeft;
/// <summary>
/// 获取当前 Option 连接点的左边坐标
/// </summary>
public int DotLeft {
get { return _DotLeft; }
internal set { _DotLeft = value; }
}
private int _DotTop;
/// <summary>
/// 获取当前 Option 连接点的上边坐标
/// </summary>
public int DotTop {
get { return _DotTop; }
internal set { _DotTop = value; }
}
private int _DotSize;
/// <summary>
/// 获取当前 Option 连接点的宽度
/// </summary>
public int DotSize {
get { return _DotSize; }
protected set { _DotSize = value; }
}
private Rectangle _TextRectangle;
/// <summary>
/// 获取当前 Option 文本区域
/// </summary>
public Rectangle TextRectangle {
get { return _TextRectangle; }
internal set { _TextRectangle = value; }
}
private object _Data;
/// <summary>
/// 获取或者设置当前 Option 所包含的数据
/// </summary>
public object Data {
get { return _Data; }
set {
if (value != null) {
var t = value.GetType();
if (t != this._DataType && !t.IsSubclassOf(this._DataType)) {
throw new ArgumentException("无效数据类型 数据类型必须为指定的数据类型或其子类");
}
}
_Data = value;
}
}
private Type _DataType;
/// <summary>
/// 获取当前 Option 数据类型
/// </summary>
public Type DataType {
get { return _DataType; }
internal set { _DataType = value; }
}
//private Rectangle _DotRectangle;
/// <summary>
/// 获取当前 Option 连接点的区域
/// </summary>
public Rectangle DotRectangle {
get {
return new Rectangle(this._DotLeft, this._DotTop, this._DotSize, this._DotSize);
}
}
/// <summary>
/// 获取当前 Option 被连接的个数
/// </summary>
public int ConnectionCount {
get { return m_hs_connected.Count; }
}
/// <summary>
/// 获取当前 Option 所连接的 Option 集合
/// </summary>
internal HashSet<STNodeOption> ConnectedOption {
get { return m_hs_connected; }
}
#endregion Properties
/// <summary>
/// 保存已经被连接的点
/// </summary>
protected HashSet<STNodeOption> m_hs_connected;
#region Constructor
/// <summary>
/// 构造一个 Option
/// </summary>
/// <param name="strText">显示文本</param>
/// <param name="dataType">数据类型</param>
/// <param name="bSingle">是否为单连接</param>
public STNodeOption(string strText, Type dataType, bool bSingle) {
if (dataType == null) throw new ArgumentNullException("指定的数据类型不能为空");
this._DotSize = 10;
m_hs_connected = new HashSet<STNodeOption>();
this._DataType = dataType;
this._Text = strText;
this._IsSingle = bSingle;
}
#endregion Constructor
#region Event
/// <summary>
/// 当被连接时候发生
/// </summary>
public event STNodeOptionEventHandler Connected;
/// <summary>
/// 当连接开始发生时发生
/// </summary>
public event STNodeOptionEventHandler Connecting;
/// <summary>
/// 当连接断开时候发生
/// </summary>
public event STNodeOptionEventHandler DisConnected;
/// <summary>
/// 当连接开始断开时发生
/// </summary>
public event STNodeOptionEventHandler DisConnecting;
/// <summary>
/// 当有数据传递时候发生
/// </summary>
public event STNodeOptionEventHandler DataTransfer;
#endregion Event
#region protected
/// <summary>
/// 重绘整个控件
/// </summary>
protected void Invalidate() {
if (this._Owner == null) return;
this._Owner.Invalidate();
}
/*
*
* STNodeHub中输出节点就用到了事件
*
*/
protected internal virtual void OnConnected(STNodeOptionEventArgs e) {
if (this.Connected != null/* && this._IsInput*/) this.Connected(this, e);
}
protected internal virtual void OnConnecting(STNodeOptionEventArgs e) {
if (this.Connecting != null) this.Connecting(this, e);
}
protected internal virtual void OnDisConnected(STNodeOptionEventArgs e) {
if (this.DisConnected != null/* && this._IsInput*/) this.DisConnected(this, e);
}
protected internal virtual void OnDisConnecting(STNodeOptionEventArgs e) {
if (this.DisConnecting != null) this.DisConnecting(this, e);
}
protected internal virtual void OnDataTransfer(STNodeOptionEventArgs e) {
if (this.DataTransfer != null/* && this._IsInput*/) this.DataTransfer(this, e);
}
protected void STNodeEidtorConnected(STNodeEditorOptionEventArgs e) {
if (this._Owner == null) return;
if (this._Owner.Owner == null) return;
this._Owner.Owner.OnOptionConnected(e);
}
protected void STNodeEidtorDisConnected(STNodeEditorOptionEventArgs e) {
if (this._Owner == null) return;
if (this._Owner.Owner == null) return;
this._Owner.Owner.OnOptionDisConnected(e);
}
/// <summary>
/// 当前 Option 开始连接目标 Option
/// </summary>
/// <param name="op">需要连接的 Option</param>
/// <returns>是否允许继续操作</returns>
protected virtual bool ConnectingOption(STNodeOption op) {
if (this._Owner == null) return false;
if (this._Owner.Owner == null) return false;
STNodeEditorOptionEventArgs e = new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Connecting);
this._Owner.Owner.OnOptionConnecting(e);
this.OnConnecting(new STNodeOptionEventArgs(true, op, ConnectionStatus.Connecting));
op.OnConnecting(new STNodeOptionEventArgs(false, this, ConnectionStatus.Connecting));
return e.Continue;
}
/// <summary>
/// 当前 Option 开始断开目标 Option
/// </summary>
/// <param name="op">需要断开的 Option</param>
/// <returns>是否允许继续操作</returns>
protected virtual bool DisConnectingOption(STNodeOption op) {
if (this._Owner == null) return false;
if (this._Owner.Owner == null) return false;
STNodeEditorOptionEventArgs e = new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.DisConnecting);
this._Owner.Owner.OnOptionDisConnecting(e);
this.OnDisConnecting(new STNodeOptionEventArgs(true, op, ConnectionStatus.DisConnecting));
op.OnDisConnecting(new STNodeOptionEventArgs(false, this, ConnectionStatus.DisConnecting));
return e.Continue;
}
#endregion protected
#region public
/// <summary>
/// 当前 Option 连接目标 Option
/// </summary>
/// <param name="op">需要连接的 Option</param>
/// <returns>连接结果</returns>
public virtual ConnectionStatus ConnectOption(STNodeOption op) {
if (!this.ConnectingOption(op)) {
this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Reject));
return ConnectionStatus.Reject;
}
var v = this.CanConnect(op);
if (v != ConnectionStatus.Connected) {
this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v));
return v;
}
v = op.CanConnect(this);
if (v != ConnectionStatus.Connected) {
this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v));
return v;
}
op.AddConnection(this, false);
this.AddConnection(op, true);
this.ControlBuildLinePath();
this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v));
return v;
}
/// <summary>
/// 检测当前 Option 是否可以连接目标 Option
/// </summary>
/// <param name="op">需要连接的 Option</param>
/// <returns>检测结果</returns>
public virtual ConnectionStatus CanConnect(STNodeOption op) {
if (this._IsInput == op.IsInput) return ConnectionStatus.SameInputOrOutput;
if (op.Owner == this._Owner) return ConnectionStatus.SameOwner;
if (op.Owner == null || this._Owner == null) return ConnectionStatus.NoOwner;
if (this._IsSingle && m_hs_connected.Count == 1) return ConnectionStatus.SingleOption;
if (op.IsInput && STNodeEditor.CanFindNodePath(op.Owner, this._Owner)) return ConnectionStatus.Loop;
if (m_hs_connected.Contains(op)) return ConnectionStatus.Exists;
if (this._IsInput && op.DataType != this._DataType && !op.DataType.IsSubclassOf(this._DataType)) return ConnectionStatus.ErrorType;
if (this._Owner.LockOption) return ConnectionStatus.Locked;
return ConnectionStatus.Connected;
}
/// <summary>
/// 当前 Option 断开目标 Option
/// </summary>
/// <param name="op">需要断开的 Option</param>
/// <returns></returns>
public virtual ConnectionStatus DisConnectOption(STNodeOption op) {
if (!this.DisConnectingOption(op)) {
this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Reject));
return ConnectionStatus.Reject;
}
if (op.Owner == null) return ConnectionStatus.NoOwner;
if (this._Owner == null) return ConnectionStatus.NoOwner;
if (op.Owner.LockOption && this._Owner.LockOption) {
this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Locked));
return ConnectionStatus.Locked;
}
op.RemoveConnection(this, false);
this.RemoveConnection(op, true);
this.ControlBuildLinePath();
this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.DisConnected));
return ConnectionStatus.DisConnected;
}
/// <summary>
/// 断开当前 Option 的所有连接
/// </summary>
public void DisConnectionAll() {
var arr = m_hs_connected.ToArray();
foreach (var v in arr) {
this.DisConnectOption(v);
}
}
/// <summary>
/// 获取当前 Option 所连接的 Option 集合
/// </summary>
/// <returns>如果为null 则表示不存在所有者 否则返回集合</returns>
public List<STNodeOption> GetConnectedOption() {
if (!this._IsInput)
return m_hs_connected.ToList();
List<STNodeOption> lst = new List<STNodeOption>();
if (this._Owner == null) return null;
if (this._Owner.Owner == null) return null;
foreach (var v in this._Owner.Owner.GetConnectionInfo()) {
if (v.Output == this) lst.Add(v.Input);
}
return lst;
}
/// <summary>
/// 向当前 Option 所连接的所有 Option 投递数据
/// </summary>
public void TransferData() {
foreach (var v in m_hs_connected) {
v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected));
}
}
/// <summary>
/// 向当前 Option 所连接的所有 Option 投递数据
/// </summary>
/// <param name="data">需要投递的数据</param>
public void TransferData(object data) {
this.Data = data; //不是this._Data
foreach (var v in m_hs_connected) {
v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected));
}
}
#endregion public
#region internal
private bool AddConnection(STNodeOption op, bool bSponsor) {
bool b = m_hs_connected.Add(op);
this.OnConnected(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected));
if (this._IsInput) this.OnDataTransfer(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected));
return b;
}
private bool RemoveConnection(STNodeOption op, bool bSponsor) {
bool b = false;
if (m_hs_connected.Contains(op)) {
b = m_hs_connected.Remove(op);
if (this._IsInput) this.OnDataTransfer(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.DisConnected));
this.OnDisConnected(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected));
}
return b;
}
#endregion internal
#region private
private void ControlBuildLinePath() {
if (this.Owner == null) return;
if (this.Owner.Owner == null) return;
this.Owner.Owner.BuildLinePath();
}
#endregion
}
}

View File

@ -1,238 +1,231 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace ST.Library.UI.NodeEditor
{
public class STNodeOptionCollection : IList, ICollection, IEnumerable
{
/*
* :Add,Remove,...
* 使 :Remove,RemoveAt,Clear,this[index] = value,...
* Option的Owner是严格绑定的 Owner的变更
* 线 DisConnect事件
* STNode中 访
*/
private int _Count;
public int Count { get { return _Count; } }
private STNodeOption[] m_options;
private STNode m_owner;
private bool m_isInput; //当前集合是否是存放的是输入点
internal STNodeOptionCollection(STNode owner, bool isInput) {
if (owner == null) throw new ArgumentNullException("所有者不能为空");
m_owner = owner;
m_isInput = isInput;
m_options = new STNodeOption[4];
}
public STNodeOption Add(string strText, Type dataType, bool bSingle) {
//not do this code -> out of bounds
//return m_options[this.Add(new STNodeOption(strText, dataType, bSingle))];
int nIndex = this.Add(new STNodeOption(strText, dataType, bSingle));
return m_options[nIndex];
}
public int Add(STNodeOption option) {
if (option == null) throw new ArgumentNullException("添加对象不能为空");
this.EnsureSpace(1);
int nIndex = option == STNodeOption.Empty ? -1 : this.IndexOf(option);
if (-1 == nIndex) {
nIndex = this._Count;
option.Owner = m_owner;
option.IsInput = m_isInput;
m_options[this._Count++] = option;
this.Invalidate();
}
return nIndex;
}
public void AddRange(STNodeOption[] options) {
if (options == null) throw new ArgumentNullException("添加对象不能为空");
this.EnsureSpace(options.Length);
foreach (var op in options) {
if (op == null) throw new ArgumentNullException("添加对象不能为空");
if (-1 == this.IndexOf(op)) {
op.Owner = m_owner;
op.IsInput = m_isInput;
m_options[this._Count++] = op;
}
}
this.Invalidate();
}
public void Clear() {
for (int i = 0; i < this._Count; i++) m_options[i].Owner = null;
this._Count = 0;
m_options = new STNodeOption[4];
this.Invalidate();
}
public bool Contains(STNodeOption option) {
return this.IndexOf(option) != -1;
}
public int IndexOf(STNodeOption option) {
return Array.IndexOf<STNodeOption>(m_options, option);
}
public void Insert(int index, STNodeOption option) {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("索引越界");
if (option == null)
throw new ArgumentNullException("插入对象不能为空");
this.EnsureSpace(1);
for (int i = this._Count; i > index; i--)
m_options[i] = m_options[i - 1];
option.Owner = m_owner;
m_options[index] = option;
this._Count++;
this.Invalidate();
}
public bool IsFixedSize {
get { return false; }
}
public bool IsReadOnly {
get { return false; }
}
public void Remove(STNodeOption option) {
int nIndex = this.IndexOf(option);
if (nIndex != -1) this.RemoveAt(nIndex);
}
public void RemoveAt(int index) {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("索引越界");
this._Count--;
m_options[index].Owner = null;
for (int i = index, Len = this._Count; i < Len; i++)
m_options[i] = m_options[i + 1];
this.Invalidate();
}
public STNodeOption this[int index] {
get {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("索引越界");
return m_options[index];
}
set { throw new InvalidOperationException("禁止重新赋值元素"); }
}
public void CopyTo(Array array, int index) {
if (array == null)
throw new ArgumentNullException("数组不能为空");
m_options.CopyTo(array, index);
}
public bool IsSynchronized {
get { return true; }
}
public object SyncRoot {
get { return this; }
}
public IEnumerator GetEnumerator() {
for (int i = 0, Len = this._Count; i < Len; i++)
yield return m_options[i];
}
/// <summary>
/// 确认空间是否足够 空间不足扩大容量
/// </summary>
/// <param name="elements">需要增加的个数</param>
private void EnsureSpace(int elements) {
if (elements + this._Count > m_options.Length) {
STNodeOption[] arrTemp = new STNodeOption[Math.Max(m_options.Length * 2, elements + this._Count)];
m_options.CopyTo(arrTemp, 0);
m_options = arrTemp;
}
}
protected void Invalidate() {
if (m_owner != null && m_owner.Owner != null) {
m_owner.BuildSize(true, true, true);
//m_owner.Invalidate();//.Owner.Invalidate();
}
}
//===================================================================================
int IList.Add(object value) {
return this.Add((STNodeOption)value);
}
void IList.Clear() {
this.Clear();
}
bool IList.Contains(object value) {
return this.Contains((STNodeOption)value);
}
int IList.IndexOf(object value) {
return this.IndexOf((STNodeOption)value);
}
void IList.Insert(int index, object value) {
this.Insert(index, (STNodeOption)value);
}
bool IList.IsFixedSize {
get { return this.IsFixedSize; }
}
bool IList.IsReadOnly {
get { return this.IsReadOnly; }
}
void IList.Remove(object value) {
this.Remove((STNodeOption)value);
}
void IList.RemoveAt(int index) {
this.RemoveAt(index);
}
object IList.this[int index] {
get {
return this[index];
}
set {
this[index] = (STNodeOption)value;
}
}
void ICollection.CopyTo(Array array, int index) {
this.CopyTo(array, index);
}
int ICollection.Count {
get { return this._Count; }
}
bool ICollection.IsSynchronized {
get { return this.IsSynchronized; }
}
object ICollection.SyncRoot {
get { return this.SyncRoot; }
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
public STNodeOption[] ToArray() {
STNodeOption[] ops = new STNodeOption[this._Count];
for (int i = 0; i < ops.Length; i++)
ops[i] = m_options[i];
return ops;
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace ST.Library.UI
{
public class STNodeOptionCollection : IList, ICollection, IEnumerable
{
/*
* :Add,Remove,...
* 使 :Remove,RemoveAt,Clear,this[index] = value,...
* Option的Owner是严格绑定的 Owner的变更
* 线 DisConnect事件
* STNode中 访
*/
private int _Count;
public int Count { get { return _Count; } }
private STNodeOption[] m_options;
private STNode m_owner;
private bool m_isInput; //当前集合是否是存放的是输入点
internal STNodeOptionCollection(STNode owner, bool isInput) {
if (owner == null) throw new ArgumentNullException("所有者不能为空");
m_owner = owner;
m_isInput = isInput;
m_options = new STNodeOption[4];
}
public int Add(STNodeOption option) {
if (option == null) throw new ArgumentNullException("添加对象不能为空");
this.EnsureSpace(1);
int nIndex = this.IndexOf(option);
if (-1 == nIndex) {
nIndex = this._Count;
option.Owner = m_owner;
option.IsInput = m_isInput;
m_options[this._Count++] = option;
this.Redraw();
}
return nIndex;
}
public void AddRange(STNodeOption[] options) {
if (options == null) throw new ArgumentNullException("添加对象不能为空");
this.EnsureSpace(options.Length);
foreach (var op in options) {
if (op == null) throw new ArgumentNullException("添加对象不能为空");
if (-1 == this.IndexOf(op)) {
op.Owner = m_owner;
op.IsInput = m_isInput;
m_options[this._Count++] = op;
}
}
this.Redraw();
}
public void Clear() {
for (int i = 0; i < this._Count; i++) m_options[i].Owner = null;
this._Count = 0;
m_options = new STNodeOption[4];
this.Redraw();
}
public bool Contains(STNodeOption option) {
return this.IndexOf(option) != -1;
}
public int IndexOf(STNodeOption option) {
return Array.IndexOf<STNodeOption>(m_options, option);
}
public void Insert(int index, STNodeOption option) {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("索引越界");
if (option == null)
throw new ArgumentNullException("插入对象不能为空");
this.EnsureSpace(1);
for (int i = this._Count; i > index; i--)
m_options[i] = m_options[i - 1];
option.Owner = m_owner;
m_options[index] = option;
this._Count++;
this.Redraw();
}
public bool IsFixedSize {
get { return false; }
}
public bool IsReadOnly {
get { return false; }
}
public void Remove(STNodeOption option) {
int nIndex = this.IndexOf(option);
if (nIndex != -1) this.RemoveAt(nIndex);
}
public void RemoveAt(int index) {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("索引越界");
this._Count--;
m_options[index].Owner = null;
for (int i = index, Len = this._Count; i < Len; i++)
m_options[i] = m_options[i + 1];
this.Redraw();
}
public STNodeOption this[int index] {
get {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("索引越界");
return m_options[index];
}
set { throw new InvalidOperationException("禁止重新赋值元素"); }
}
public void CopyTo(Array array, int index) {
if (array == null)
throw new ArgumentNullException("数组不能为空");
m_options.CopyTo(array, index);
}
public bool IsSynchronized {
get { return true; }
}
public object SyncRoot {
get { return this; }
}
public IEnumerator GetEnumerator() {
for (int i = 0, Len = this._Count; i < Len; i++)
yield return m_options[i];
}
/// <summary>
/// 确认空间是否足够 空间不足扩大容量
/// </summary>
/// <param name="elements">需要增加的个数</param>
private void EnsureSpace(int elements) {
if (elements + this._Count > m_options.Length) {
STNodeOption[] arrTemp = new STNodeOption[Math.Max(m_options.Length * 2, elements + this._Count)];
m_options.CopyTo(arrTemp, 0);
m_options = arrTemp;
}
}
protected void Redraw() {
if (m_owner != null && m_owner.Owner != null) {
m_owner.BuildSize(true, true, true);
//m_owner.Invalidate();//.Owner.Invalidate();
}
}
//===================================================================================
int IList.Add(object value) {
return this.Add((STNodeOption)value);
}
void IList.Clear() {
this.Clear();
}
bool IList.Contains(object value) {
return this.Contains((STNodeOption)value);
}
int IList.IndexOf(object value) {
return this.IndexOf((STNodeOption)value);
}
void IList.Insert(int index, object value) {
this.Insert(index, (STNodeOption)value);
}
bool IList.IsFixedSize {
get { return this.IsFixedSize; }
}
bool IList.IsReadOnly {
get { return this.IsReadOnly; }
}
void IList.Remove(object value) {
this.Remove((STNodeOption)value);
}
void IList.RemoveAt(int index) {
this.RemoveAt(index);
}
object IList.this[int index] {
get {
return this[index];
}
set {
this[index] = (STNodeOption)value;
}
}
void ICollection.CopyTo(Array array, int index) {
this.CopyTo(array, index);
}
int ICollection.Count {
get { return this._Count; }
}
bool ICollection.IsSynchronized {
get { return this.IsSynchronized; }
}
object ICollection.SyncRoot {
get { return this.SyncRoot; }
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
public STNodeOption[] ToArray() {
STNodeOption[] ops = new STNodeOption[this._Count];
for (int i = 0; i < ops.Length; i++)
ops[i] = m_options[i];
return ops;
}
}
}

View File

@ -1,81 +0,0 @@
由于`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

View File

@ -1,81 +0,0 @@
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

42
WinNodeEditerTest.sln Normal file
View File

@ -0,0 +1,42 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinNodeEditerTest", "WinNodeEditerTest\WinNodeEditerTest.csproj", "{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ST.Library.UI", "ST.Library.UI\ST.Library.UI.csproj", "{EFFCC270-4999-4077-A543-56CCCCE92147}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|Mixed Platforms = Release|Mixed Platforms
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Debug|Any CPU.ActiveCfg = Debug|x86
{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Debug|Mixed Platforms.Build.0 = Debug|x86
{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Debug|x86.ActiveCfg = Debug|x86
{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Debug|x86.Build.0 = Debug|x86
{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Release|Any CPU.ActiveCfg = Release|x86
{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Release|Mixed Platforms.ActiveCfg = Release|x86
{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Release|Mixed Platforms.Build.0 = Release|x86
{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Release|x86.ActiveCfg = Release|x86
{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Release|x86.Build.0 = Release|x86
{EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|x86.ActiveCfg = Debug|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Release|Any CPU.Build.0 = Release|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

BIN
WinNodeEditerTest.suo Normal file

Binary file not shown.

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ST.Library.UI;
namespace ST.Library.UI
{
public class DemoNode : STNode
{
protected override void OnCreate() {
base.OnCreate();
this.InputOptions.Add(new STNodeOption("Input", typeof(string), false));
this.InputOptions.Add(new STNodeOption("SingleNode", typeof(System.Drawing.Image), true));
this.InputOptions.Add(new STNodeOption("SingleNode", typeof(object), true));
this.OutputOptions.Add(new STNodeOption("output", typeof(string), false));
this.OutputOptions.Add(new STNodeOption("Single", typeof(System.Drawing.Icon), true));
this.OutputOptions.Add(new STNodeOption("Single", typeof(object), true));
this.Title = "Demo_Node";
}
}
}

View File

@ -0,0 +1,87 @@
namespace ST.Library.UI.Demo_Image
{
partial class FrmImage
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.stNodeEditor1 = new ST.Library.UI.STNodeEditor();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(0, 0);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 1;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(0, 29);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 2;
this.button2.Text = "button2";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// stNodeEditor1
//
this.stNodeEditor1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(34)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
this.stNodeEditor1.Curvature = 0.3F;
this.stNodeEditor1.Location = new System.Drawing.Point(0, 0);
this.stNodeEditor1.LocationBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.stNodeEditor1.MarkBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.stNodeEditor1.MarkForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.stNodeEditor1.Name = "stNodeEditor1";
this.stNodeEditor1.Size = new System.Drawing.Size(200, 200);
this.stNodeEditor1.TabIndex = 0;
this.stNodeEditor1.Text = "stNodeEditor1";
//
// FrmImage
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Controls.Add(this.stNodeEditor1);
this.Name = "FrmImage";
this.Text = "FrmImage";
this.Load += new System.EventHandler(this.FrmImage_Load);
this.ResumeLayout(false);
}
#endregion
private STNodeEditor stNodeEditor1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
}
}

View File

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
namespace ST.Library.UI.Demo_Image
{
public partial class FrmImage : Form
{
public FrmImage() {
InitializeComponent();
button1.Text = "Save";
button2.Text = "Open";
}
private void FrmImage_Load(object sender, EventArgs e) {
stNodeEditor1.Dock = DockStyle.Fill;
stNodeEditor1.TypeColor.Add(typeof(Image), Color.BlueViolet);
STNode node = new STNodeImageInput();
stNodeEditor1.Nodes.Add(node);
node = new STNodeImageChannel();
stNodeEditor1.Nodes.Add(node);
node = new STNodeImageChannel();
stNodeEditor1.Nodes.Add(node);
node = new STNodeImageChannel();
stNodeEditor1.Nodes.Add(node);
node = new STNodeImageChannel();
stNodeEditor1.Nodes.Add(node);
stNodeEditor1.LoadAssembly(Application.ExecutablePath);
stNodeEditor1.LoadAssembly(Directory.GetFiles("./", "*.dll"));
}
private void button1_Click(object sender, EventArgs e) {
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "*.stn|*.stn";
if (sfd.ShowDialog() != DialogResult.OK) return;
stNodeEditor1.SaveCanvas(sfd.FileName);
}
private void button2_Click(object sender, EventArgs e) {
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "*.stn|*.stn";
if (ofd.ShowDialog() != DialogResult.OK) return;
stNodeEditor1.Nodes.Clear();
stNodeEditor1.LoadCanvas(ofd.FileName);
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace ST.Library.UI.Demo_Image
{
public class STNodeImage : STNode
{
protected STNodeOption m_input_image; //输入输出点
protected STNodeOption m_out_image;
protected override void OnCreate() {
base.OnCreate();
m_input_image = new STNodeOption("", typeof(Image), true);
this.InputOptions.Add(m_input_image);
m_out_image = new STNodeOption("", typeof(Image), false);
this.OutputOptions.Add(m_out_image);
m_input_image.DataTransfer += new STNodeOptionEventHandler(m_input_image_DataTransfer);
this.Title = "Image";
}
//监听输入点接入事件
void m_input_image_DataTransfer(object sender, STNodeOptionEventArgs e) {
if (e.Status != ConnectionStatus.Connected)
m_input_image.Data = null;
else
m_input_image.Data = e.TargetOption.Data;
m_out_image.TransferData(m_input_image.Data); //输出节点向下投递数据
this.OnDataTransfer(); //通知子类
this.Invalidate(); //重绘自己
}
protected override System.Drawing.Size OnBuildNodeSize(DrawingTools dt) {
//return base.OnBuildNodeSize();
return new System.Drawing.Size(160, 120); //设定节点大小
}
protected override void OnDrawBody(DrawingTools dt) { //重绘节点主体部分
base.OnDrawBody(dt);
Graphics g = dt.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
if (m_input_image.Data != null) {
g.DrawImage((Image)m_input_image.Data, this.Left + 15, this.Top + 30, this.Width - 40, this.Height - 40);
} else {
g.FillRectangle(Brushes.Gray, this.Left + 15, this.Top + 30, this.Width - 40, this.Height - 40);
}
}
protected virtual void OnDataTransfer() { }
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace ST.Library.UI.Demo_Image
{
public class STNodeImageChannel : STNodeImage
{
private STNodeOption m_out_r;
private STNodeOption m_out_g;
private STNodeOption m_out_b;
private Bitmap m_img_r;
private Bitmap m_img_g;
private Bitmap m_img_b;
protected override void OnCreate() {
base.OnCreate();
m_out_r = new STNodeOption("R", typeof(Image), false);
m_out_g = new STNodeOption("G", typeof(Image), false);
m_out_b = new STNodeOption("B", typeof(Image), false);
this.OutputOptions.Add(m_out_r);
this.OutputOptions.Add(m_out_g);
this.OutputOptions.Add(m_out_b);
this.Title = "Channel";
}
protected override void OnDataTransfer() {
base.OnDataTransfer();
if (m_img_r != null) {
m_img_r.Dispose();
m_img_g.Dispose();
m_img_b.Dispose();
m_img_r = m_img_g = m_img_b = null;
}
if (m_out_image.Data != null) { //分离通道 Demo 演示 Get/SetPixel() 效率极低 应当LockBitmap操作
Bitmap img = (Bitmap)base.m_input_image.Data;
m_img_r = new Bitmap(img.Width, img.Height);
m_img_g = new Bitmap(img.Width, img.Height);
m_img_b = new Bitmap(img.Width, img.Height);
for (int x = 0; x < img.Width; x++) {
for (int y = 0; y < img.Height; y++) {
Color clr = img.GetPixel(x, y);
m_img_r.SetPixel(x, y, Color.FromArgb(255, clr.R, clr.R, clr.R));
m_img_g.SetPixel(x, y, Color.FromArgb(255, clr.G, clr.G, clr.G));
m_img_b.SetPixel(x, y, Color.FromArgb(255, clr.B, clr.B, clr.B));
}
}
}
m_out_r.TransferData(m_img_r);
m_out_g.TransferData(m_img_b);
m_out_b.TransferData(m_img_b);
}
}
}

View File

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
namespace ST.Library.UI.Demo_Image
{
public class STNodeImageInput : STNode
{
private STNodeOption m_option_out;
private string m_str_file;
private Size m_sz = new Size(100, 60);
protected override void OnCreate() {
base.OnCreate();
this.Title = "ImageInput";
m_option_out = new STNodeOption("", typeof(Image), false);
this.OutputOptions.Add(m_option_out);
STNodeButton btn = new STNodeButton();
btn.Left = (m_sz.Width - btn.Width) / 2;
btn.Top = (m_sz.Height - 20 - btn.Height) / 2;
btn.Text = "OpenImage";
btn.MouseClick += new MouseEventHandler(btn_MouseClick);
this.Controls.Add(btn);
}
void btn_MouseClick(object sender, MouseEventArgs e) {
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "*.jpg|*.jpg|*.png|*.png|*.bmp|*.bmp|*.*|*.*";
if (ofd.ShowDialog() != DialogResult.OK) return;
m_option_out.TransferData(Image.FromFile(ofd.FileName));
m_str_file = ofd.FileName;
}
protected override Size OnBuildNodeSize(DrawingTools dt) {
//return base.OnBuildNodeSize();
return m_sz;
}
protected override Point OnSetOptionDotLocation(STNodeOption op, Point pt) {
return new Point(pt.X, pt.Y + 10);
//return base.OnSetOptionLocation(op);
}
//protected override void OnDrawOptionDot(DrawingTools dt, STNodeOption op) {
// //if (op == m_option_out) op.DotTop = this.Top + 35;
// base.OnDrawOptionDot(dt, op);
//}
protected override void OnSaveNode(Dictionary<string, byte[]> dic) {
if (m_str_file == null) m_str_file = string.Empty;
dic.Add("file", Encoding.UTF8.GetBytes(m_str_file));
}
protected override void OnLoadNode(Dictionary<string, byte[]> dic) {
base.OnLoadNode(dic);
m_str_file = Encoding.UTF8.GetString(dic["file"]);
if (System.IO.File.Exists(m_str_file)) { //如果文件存在加载并投递数据
m_option_out.TransferData(Image.FromFile(m_str_file));
}
}
public class STNodeButton : STNodeControl //自定义一个Button控件
{
private bool m_isHover;
protected override void OnMouseEnter(EventArgs e) {
base.OnMouseEnter(e);
m_isHover = true;
this.Invalidate();
}
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave(e);
m_isHover = false;
this.Invalidate();
}
protected override void OnPaint(DrawingTools dt) {
//base.OnPaint(dt);
Graphics g = dt.Graphics;
SolidBrush brush = dt.SolidBrush;
brush.Color = m_isHover ? Color.DodgerBlue : this.BackColor;
g.FillRectangle(brush, 0, 0, this.Width, this.Height);
g.DrawString(this.Text, this.Font, Brushes.White, this.ClientRectangle, base.m_sf);
}
}
}
}

100
WinNodeEditerTest/Form1.Designer.cs generated Normal file
View File

@ -0,0 +1,100 @@
namespace ST.Library.UI
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.button3 = new System.Windows.Forms.Button();
this.stNodeEditor1 = new ST.Library.UI.STNodeEditor();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(0, 0);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 4;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(0, 29);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 5;
this.button2.Text = "button2";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// button3
//
this.button3.Location = new System.Drawing.Point(0, 58);
this.button3.Name = "button3";
this.button3.Size = new System.Drawing.Size(75, 23);
this.button3.TabIndex = 6;
this.button3.Text = "button3";
this.button3.UseVisualStyleBackColor = true;
this.button3.Click += new System.EventHandler(this.button3_Click);
//
// stNodeEditor1
//
this.stNodeEditor1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(34)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
this.stNodeEditor1.Curvature = 0.3F;
this.stNodeEditor1.Location = new System.Drawing.Point(0, 0);
this.stNodeEditor1.LocationBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.stNodeEditor1.MarkBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.stNodeEditor1.MarkForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.stNodeEditor1.Name = "stNodeEditor1";
this.stNodeEditor1.Size = new System.Drawing.Size(200, 200);
this.stNodeEditor1.TabIndex = 3;
this.stNodeEditor1.Text = "stNodeEditor1";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.button3);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Controls.Add(this.stNodeEditor1);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
}
#endregion
private STNodeEditor stNodeEditor1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Button button3;
}
}

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ST.Library.UI;
using System.IO;
namespace ST.Library.UI
{
public partial class Form1 : Form
{
public Form1() {
InitializeComponent();
button1.Text = "Lock";
button2.Text = "Save";
button3.Text = "Open";
}
private void Form1_Load(object sender, EventArgs e) {
stNodeEditor1.Dock = DockStyle.Fill;
stNodeEditor1.TypeColor.Add(typeof(string), Color.Yellow);
stNodeEditor1.TypeColor.Add(typeof(Image), Color.Red);
stNodeEditor1.SelectedChanged += new EventHandler(stNodeEditor1_SelectedChanged);
stNodeEditor1.OptionConnected += new STNodeEditorOptionEventHandler(stNodeEditor1_OptionConnected);
stNodeEditor1.CanvasScaled += new EventHandler(stNodeEditor1_CanvasScaled);
for (int i = 0; i < 4; i++) {
STNode node = new DemoNode();
//if (i == 2)
// node.Mark = "这里是标记信息\r\n\t新的一行数据";
//else
// node.Mark = "this is mark Info\r\nthis is new line " + i;
stNodeEditor1.Nodes.Add(node);
}
stNodeEditor1.Nodes.Add(new STNodeHub());
stNodeEditor1.Nodes.Add(new STNodeHub());
stNodeEditor1.Nodes.Add(new NodeNumberAdd());
stNodeEditor1.LoadAssembly(Application.ExecutablePath);
stNodeEditor1.LoadAssembly(Directory.GetFiles("./", "*.dll"));
}
void stNodeEditor1_CanvasScaled(object sender, EventArgs e) {
stNodeEditor1.ShowAlert(stNodeEditor1.CanvasScale.ToString("F2"), Color.White, Color.Black);
}
void stNodeEditor1_OptionConnected(object sender, STNodeEditorOptionEventArgs e) {
Console.WriteLine(e.CurrentOption.Text + " - " + e.TargetOption.Text + " - " + e.Status);
}
void stNodeEditor1_SelectedChanged(object sender, EventArgs e) {
foreach (var v in stNodeEditor1.GetSelectedNode()) {
Console.WriteLine("Selected - " + v.Title);
}
}
private void button1_Click(object sender, EventArgs e) {
stNodeEditor1.Nodes[0].LockOption = !stNodeEditor1.Nodes[0].LockOption;
stNodeEditor1.Nodes[1].LockOption = !stNodeEditor1.Nodes[1].LockOption;
stNodeEditor1.Nodes[0].LockLocation = !stNodeEditor1.Nodes[0].LockLocation;
stNodeEditor1.Nodes[1].LockLocation = !stNodeEditor1.Nodes[1].LockLocation;
}
private void button2_Click(object sender, EventArgs e) {
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "*.stn|*.stn";
if (sfd.ShowDialog() != DialogResult.OK) return;
stNodeEditor1.SaveCanvas(sfd.FileName);
}
private void button3_Click(object sender, EventArgs e) {
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "*.stn|*.stn";
if (ofd.ShowDialog() != DialogResult.OK) return;
stNodeEditor1.Nodes.Clear();
stNodeEditor1.LoadCanvas(ofd.FileName);
}
}
}

View File

@ -1,123 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="contextMenuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

96
WinNodeEditerTest/Form2.Designer.cs generated Normal file
View File

@ -0,0 +1,96 @@
namespace ST.Library.UI
{
partial class Form2
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.button3 = new System.Windows.Forms.Button();
this.button4 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(0, 0);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(0, 29);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 1;
this.button2.Text = "button2";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// button3
//
this.button3.Location = new System.Drawing.Point(0, 58);
this.button3.Name = "button3";
this.button3.Size = new System.Drawing.Size(75, 23);
this.button3.TabIndex = 2;
this.button3.Text = "button3";
this.button3.UseVisualStyleBackColor = true;
this.button3.Click += new System.EventHandler(this.button3_Click);
//
// button4
//
this.button4.Location = new System.Drawing.Point(0, 87);
this.button4.Name = "button4";
this.button4.Size = new System.Drawing.Size(75, 23);
this.button4.TabIndex = 3;
this.button4.Text = "button4";
this.button4.UseVisualStyleBackColor = true;
this.button4.Click += new System.EventHandler(this.button4_Click);
//
// Form2
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.button4);
this.Controls.Add(this.button3);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "Form2";
this.Text = "Form2";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Button button3;
private System.Windows.Forms.Button button4;
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ST.Library.UI
{
public partial class Form2 : Form
{
public Form2() {
InitializeComponent();
}
Graphics m_g;
private void button1_Click(object sender, EventArgs e) {
m_g = this.CreateGraphics();
}
private void button2_Click(object sender, EventArgs e) {
m_g.DrawRectangle(Pens.Red, 10, 10, 30, 30);
}
private void button3_Click(object sender, EventArgs e) {
m_g.DrawRectangle(Pens.Yellow, 45, 45, 20, 20);
}
private void button4_Click(object sender, EventArgs e) {
this.CreateGraphics().FillRectangle(Brushes.Black, 20, 20, 20, 20);
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace ST.Library.UI
{
public class NodeNumberAdd : STNode
{
private STNodeOption m_in_num1;
private STNodeOption m_in_num2;
private STNodeOption m_out_num;
private int m_nNum1, m_nNum2;
protected override void OnCreate() {
base.OnCreate();
this.Title = "NumberAdd";
m_in_num1 = new STNodeOption("num1", typeof(int), true);//只能有一个连线
m_in_num2 = new STNodeOption("num2", typeof(int), true);//只能有一个连线
m_out_num = new STNodeOption("result", 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 && e.TargetOption.Data != null) {
if (sender == m_in_num1)
m_nNum1 = (int)e.TargetOption.Data;//TargetOption为触发此事件的Option
else
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 将被自动设置
}
protected override void OnOwnerChanged() {
base.OnOwnerChanged();//通常刚被添加到节点编辑器时触发 如是以插件方式提供的节点 应当向编辑器提交数据类型颜色
if (this.Owner == null) return; //或者通过m_in_num1.DotColor = Color.Red;进行设置
this.Owner.SetTypeColor(typeof(int), Color.Red);
}
}
}

View File

@ -1,20 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace WinNodeEditorDemo
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace ST.Library.UI
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
new Demo_Image.FrmImage().Show();
Application.Run(new Form1());
}
}
}

View File

@ -1,36 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("WinNodeEditorDemo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("WinNodeEditorDemo")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("04b761ae-c417-4bc8-807c-c009959e188c")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("WinNodeEditerTest")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("WinNodeEditerTest")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("2c0c84a0-fb66-4133-80f5-053356fa0f04")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -1,63 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace WinNodeEditorDemo.Properties {
using System;
/// <summary>
/// 一个强类型的资源类,用于查找本地化的字符串等。
/// </summary>
// 此类是由 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 {
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>
/// 返回此类使用的缓存的 ResourceManager 实例。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
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>
/// 重写当前线程的 CurrentUICulture 属性,对
/// 使用此强类型资源类的所有资源查找执行重写。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ST.Library.UI.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")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
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.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ST.Library.UI.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.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -1,117 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -1,26 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace WinNodeEditorDemo.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[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;
}
}
}
}
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ST.Library.UI.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 {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@ -1,116 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<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>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{4E1829B5-2160-48F5-ABD6-11914A6A25DD}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>WinNodeEditorDemo</RootNamespace>
<AssemblyName>WinNodeEditorDemo</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AttrTestNode.cs" />
<Compile Include="Blender\BlenderMixColorNode.cs" />
<Compile Include="Blender\FrmEnumSelect.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Blender\STNodeCheckBox.cs" />
<Compile Include="Blender\STNodeColorButton.cs" />
<Compile Include="Blender\STNodeProgress.cs" />
<Compile Include="Blender\STNodeSelectBox.cs" />
<Compile Include="CalcNode.cs" />
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="ImageNode\ImageBaseNode.cs" />
<Compile Include="ImageNode\ImageChannelNode.cs" />
<Compile Include="ImageNode\ImageInputNode.cs" />
<Compile Include="EmptyOptionTestNode.cs" />
<Compile Include="NumberNode\NumberAddNode.cs" />
<Compile Include="NumberNode\NumberInputNode.cs" />
<Compile Include="NumberNode\NumberNode.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="STNodeHub.cs" />
<Compile Include="ToolStripRendererEx.cs" />
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<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>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ST.Library.UI\ST.Library.UI.csproj">
<Project>{EFFCC270-4999-4077-A543-56CCCCE92147}</Project>
<Name>ST.Library.UI</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ST.Library.UI</RootNamespace>
<AssemblyName>WinNodeEditerTest</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DemoNode.cs" />
<Compile Include="Demo_Image\FrmImage.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Demo_Image\FrmImage.Designer.cs">
<DependentUpon>FrmImage.cs</DependentUpon>
</Compile>
<Compile Include="Demo_Image\STNodeImage.cs" />
<Compile Include="Demo_Image\STNodeImageChannel.cs" />
<Compile Include="Demo_Image\STNodeImageInput.cs" />
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Form2.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form2.Designer.cs">
<DependentUpon>Form2.cs</DependentUpon>
</Compile>
<Compile Include="NodeNumberAdd.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Demo_Image\FrmImage.resx">
<DependentUpon>FrmImage.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Form2.resx">
<DependentUpon>Form2.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<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>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ST.Library.UI\ST.Library.UI.csproj">
<Project>{EFFCC270-4999-4077-A543-56CCCCE92147}</Project>
<Name>ST.Library.UI</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

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

View File

@ -1,108 +0,0 @@
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

@ -1,130 +0,0 @@
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

@ -1,75 +0,0 @@
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

@ -1,48 +0,0 @@
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

@ -1,31 +0,0 @@
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

@ -1,66 +0,0 @@
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

@ -1,55 +0,0 @@
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

@ -1,70 +0,0 @@
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

@ -1,20 +0,0 @@
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

@ -1,173 +0,0 @@
namespace WinNodeEditorDemo
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.btn_open = new System.Windows.Forms.Button();
this.btn_save = new System.Windows.Forms.Button();
this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
this.removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.lockLocationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.lockConnectionToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.stNodePropertyGrid1 = new ST.Library.UI.NodeEditor.STNodePropertyGrid();
this.stNodeEditor1 = new ST.Library.UI.NodeEditor.STNodeEditor();
this.stNodeTreeView1 = new ST.Library.UI.NodeEditor.STNodeTreeView();
this.contextMenuStrip1.SuspendLayout();
this.SuspendLayout();
//
// btn_open
//
this.btn_open.Location = new System.Drawing.Point(12, 3);
this.btn_open.Name = "btn_open";
this.btn_open.Size = new System.Drawing.Size(75, 23);
this.btn_open.TabIndex = 3;
this.btn_open.Text = "&Open";
this.btn_open.UseVisualStyleBackColor = true;
this.btn_open.Click += new System.EventHandler(this.btn_open_Click);
//
// btn_save
//
this.btn_save.Location = new System.Drawing.Point(93, 3);
this.btn_save.Name = "btn_save";
this.btn_save.Size = new System.Drawing.Size(75, 23);
this.btn_save.TabIndex = 4;
this.btn_save.Text = "&Save";
this.btn_save.UseVisualStyleBackColor = true;
this.btn_save.Click += new System.EventHandler(this.btn_save_Click);
//
// contextMenuStrip1
//
this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.removeToolStripMenuItem,
this.lockLocationToolStripMenuItem,
this.lockConnectionToolStripMenuItem});
this.contextMenuStrip1.Name = "contextMenuStrip1";
this.contextMenuStrip1.Size = new System.Drawing.Size(164, 70);
//
// removeToolStripMenuItem
//
this.removeToolStripMenuItem.Name = "removeToolStripMenuItem";
this.removeToolStripMenuItem.Size = new System.Drawing.Size(163, 22);
this.removeToolStripMenuItem.Text = "&Remove";
this.removeToolStripMenuItem.Click += new System.EventHandler(this.removeToolStripMenuItem_Click);
//
// lockLocationToolStripMenuItem
//
this.lockLocationToolStripMenuItem.Name = "lockLocationToolStripMenuItem";
this.lockLocationToolStripMenuItem.Size = new System.Drawing.Size(163, 22);
this.lockLocationToolStripMenuItem.Text = "U/Lock &Location";
this.lockLocationToolStripMenuItem.Click += new System.EventHandler(this.lockLocationToolStripMenuItem_Click);
//
// lockConnectionToolStripMenuItem
//
this.lockConnectionToolStripMenuItem.Name = "lockConnectionToolStripMenuItem";
this.lockConnectionToolStripMenuItem.Size = new System.Drawing.Size(163, 22);
this.lockConnectionToolStripMenuItem.Text = "U/Lock &Connection";
this.lockConnectionToolStripMenuItem.Click += new System.EventHandler(this.lockConnectionToolStripMenuItem_Click);
//
// stNodePropertyGrid1
//
this.stNodePropertyGrid1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(35)))), ((int)(((byte)(35)))), ((int)(((byte)(35)))));
this.stNodePropertyGrid1.DescriptionColor = System.Drawing.Color.FromArgb(((int)(((byte)(200)))), ((int)(((byte)(184)))), ((int)(((byte)(134)))), ((int)(((byte)(11)))));
this.stNodePropertyGrid1.ErrorColor = System.Drawing.Color.FromArgb(((int)(((byte)(200)))), ((int)(((byte)(165)))), ((int)(((byte)(42)))), ((int)(((byte)(42)))));
this.stNodePropertyGrid1.ForeColor = System.Drawing.Color.White;
this.stNodePropertyGrid1.ItemHoverColor = System.Drawing.Color.FromArgb(((int)(((byte)(50)))), ((int)(((byte)(125)))), ((int)(((byte)(125)))), ((int)(((byte)(125)))));
this.stNodePropertyGrid1.ItemValueBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(80)))), ((int)(((byte)(80)))), ((int)(((byte)(80)))));
this.stNodePropertyGrid1.Location = new System.Drawing.Point(12, 273);
this.stNodePropertyGrid1.MinimumSize = new System.Drawing.Size(120, 50);
this.stNodePropertyGrid1.Name = "stNodePropertyGrid1";
this.stNodePropertyGrid1.ShowTitle = true;
this.stNodePropertyGrid1.Size = new System.Drawing.Size(200, 228);
this.stNodePropertyGrid1.TabIndex = 2;
this.stNodePropertyGrid1.Text = "stNodePropertyGrid1";
this.stNodePropertyGrid1.TitleColor = System.Drawing.Color.FromArgb(((int)(((byte)(127)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
//
// stNodeEditor1
//
this.stNodeEditor1.AllowDrop = true;
this.stNodeEditor1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(34)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
this.stNodeEditor1.Curvature = 0.3F;
this.stNodeEditor1.Location = new System.Drawing.Point(218, 32);
this.stNodeEditor1.LocationBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.stNodeEditor1.MarkBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.stNodeEditor1.MarkForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.stNodeEditor1.MinimumSize = new System.Drawing.Size(100, 100);
this.stNodeEditor1.Name = "stNodeEditor1";
this.stNodeEditor1.Size = new System.Drawing.Size(432, 469);
this.stNodeEditor1.TabIndex = 1;
this.stNodeEditor1.Text = "stNodeEditor1";
//
// stNodeTreeView1
//
this.stNodeTreeView1.AllowDrop = true;
this.stNodeTreeView1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(35)))), ((int)(((byte)(35)))), ((int)(((byte)(35)))));
this.stNodeTreeView1.FolderCountColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(255)))), ((int)(((byte)(255)))), ((int)(((byte)(255)))));
this.stNodeTreeView1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220)))));
this.stNodeTreeView1.ItemBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(45)))));
this.stNodeTreeView1.ItemHoverColor = System.Drawing.Color.FromArgb(((int)(((byte)(50)))), ((int)(((byte)(125)))), ((int)(((byte)(125)))), ((int)(((byte)(125)))));
this.stNodeTreeView1.Location = new System.Drawing.Point(12, 32);
this.stNodeTreeView1.MinimumSize = new System.Drawing.Size(100, 60);
this.stNodeTreeView1.Name = "stNodeTreeView1";
this.stNodeTreeView1.ShowFolderCount = true;
this.stNodeTreeView1.Size = new System.Drawing.Size(200, 235);
this.stNodeTreeView1.TabIndex = 0;
this.stNodeTreeView1.Text = "stNodeTreeView1";
this.stNodeTreeView1.TextBoxColor = System.Drawing.Color.FromArgb(((int)(((byte)(30)))), ((int)(((byte)(30)))), ((int)(((byte)(30)))));
this.stNodeTreeView1.TitleColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(60)))), ((int)(((byte)(60)))));
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(662, 513);
this.Controls.Add(this.btn_save);
this.Controls.Add(this.btn_open);
this.Controls.Add(this.stNodePropertyGrid1);
this.Controls.Add(this.stNodeEditor1);
this.Controls.Add(this.stNodeTreeView1);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.contextMenuStrip1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private ST.Library.UI.NodeEditor.STNodeTreeView stNodeTreeView1;
private ST.Library.UI.NodeEditor.STNodeEditor stNodeEditor1;
private ST.Library.UI.NodeEditor.STNodePropertyGrid stNodePropertyGrid1;
private System.Windows.Forms.Button btn_open;
private System.Windows.Forms.Button btn_save;
private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
private System.Windows.Forms.ToolStripMenuItem removeToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem lockLocationToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem lockConnectionToolStripMenuItem;
}
}

View File

@ -1,88 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using ST.Library.UI.NodeEditor;
namespace WinNodeEditorDemo
{
public partial class Form1 : Form
{
public Form1() {
InitializeComponent();
this.StartPosition = FormStartPosition.CenterScreen;
}
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
stNodePropertyGrid1.Text = "Node_Property";
stNodeTreeView1.LoadAssembly(Application.ExecutablePath);
stNodeEditor1.LoadAssembly(Application.ExecutablePath);
stNodeEditor1.ActiveChanged += (s, ea) => stNodePropertyGrid1.SetNode(stNodeEditor1.ActiveNode);
//stNodeEditor1.SelectedChanged += (s, ea) => stNodePropertyGrid1.SetSTNode(stNodeEditor1.ActiveNode);
stNodeEditor1.OptionConnected += (s, ea) => stNodeEditor1.ShowAlert(ea.Status.ToString(), Color.White, ea.Status == ConnectionStatus.Connected ? Color.FromArgb(125, Color.Green) : Color.FromArgb(125, Color.Red));
stNodeEditor1.CanvasScaled += (s, ea) => stNodeEditor1.ShowAlert(stNodeEditor1.CanvasScale.ToString("F2"), Color.White, Color.FromArgb(125, Color.Yellow));
stNodeEditor1.NodeAdded += (s, ea) => ea.Node.ContextMenuStrip = contextMenuStrip1;
stNodePropertyGrid1.SetInfoKey("Author", "Mail", "Link", "Show Help");
stNodeTreeView1.PropertyGrid.SetInfoKey("Author", "Mail", "Link", "Show Help");
stNodeEditor1.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom;
contextMenuStrip1.ShowImageMargin = false;
contextMenuStrip1.Renderer = new ToolStripRendererEx();
}
private void Form1_Load(object sender, EventArgs e) {
//int nLines = 0;
//foreach (var v in Directory.GetFiles("../../../", "*.cs", SearchOption.AllDirectories)) {
// nLines += File.ReadAllLines(v).Length;
//}
//MessageBox.Show(nLines.ToString());
//this.Resize += (s, ea) => this.Text = this.Size.ToString();
//this.BeginInvoke(new MethodInvoker(() => {
// //this.Size = new Size(488, 306);
// this.Size = new Size(488, 246);
// stNodeTreeView1.Visible = false;
// stNodePropertyGrid1.Top = stNodeEditor1.Top;
// stNodePropertyGrid1.Height = stNodeEditor1.Height;
// stNodeTreeView1.Height = stNodeEditor1.Height;
//}));
}
private void btn_open_Click(object sender, EventArgs e) {
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "*.stn|*.stn";
if (ofd.ShowDialog() != DialogResult.OK) return;
stNodeEditor1.Nodes.Clear();
stNodeEditor1.LoadCanvas(ofd.FileName);
}
private void btn_save_Click(object sender, EventArgs e) {
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "*.stn|*.stn";
if (sfd.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
stNodeEditor1.SaveCanvas(sfd.FileName);
}
private void lockConnectionToolStripMenuItem_Click(object sender, EventArgs e) {
stNodeEditor1.ActiveNode.LockOption = !stNodeEditor1.ActiveNode.LockOption;
}
private void lockLocationToolStripMenuItem_Click(object sender, EventArgs e) {
if (stNodeEditor1.ActiveNode == null) return;
stNodeEditor1.ActiveNode.LockLocation = !stNodeEditor1.ActiveNode.LockLocation;
}
private void removeToolStripMenuItem_Click(object sender, EventArgs e) {
if (stNodeEditor1.ActiveNode == null) return;
stNodeEditor1.Nodes.Remove(stNodeEditor1.ActiveNode);
}
}
}

View File

@ -1,41 +0,0 @@
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

@ -1,87 +0,0 @@
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

@ -1,84 +0,0 @@
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

@ -1,73 +0,0 @@
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

@ -1,52 +0,0 @@
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

@ -1,25 +0,0 @@
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

@ -1,32 +0,0 @@
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

@ -1,80 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
namespace WinNodeEditorDemo
{
public class ToolStripRendererEx : ToolStripRenderer
{
private SolidBrush m_brush = new SolidBrush(Color.FromArgb(255, 52, 86, 141));
private StringFormat m_sf = new StringFormat();
public ToolStripRendererEx() {
m_sf.LineAlignment = StringAlignment.Center;
}
protected override void InitializeItem(ToolStripItem item) {
base.InitializeItem(item);
item.AutoSize = false;
item.Size = new Size(item.Width, 30);
}
protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) {
using (SolidBrush sb = new SolidBrush(Color.FromArgb(34, 34, 34))) {
e.Graphics.FillRectangle(sb, e.AffectedBounds);
}
base.OnRenderToolStripBackground(e);
}
protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) {
e.Graphics.DrawRectangle(Pens.Black, e.AffectedBounds.X, e.AffectedBounds.Y, e.AffectedBounds.Width - 1, e.AffectedBounds.Height - 1);
base.OnRenderToolStripBorder(e);
}
//protected override void OnRenderImageMargin(ToolStripRenderEventArgs e) {
// using (SolidBrush sb = new SolidBrush(Color.FromArgb(50, 50, 50))) {
// e.Graphics.FillRectangle(sb, e.AffectedBounds);
// }
// base.OnRenderImageMargin(e);
//}
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) {
e.TextColor = e.Item.Selected ? Color.White : Color.LightGray;
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
e.TextRectangle = new Rectangle(e.TextRectangle.Left, e.TextRectangle.Top, e.TextRectangle.Width, 30);
base.OnRenderItemText(e);
}
protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) {
e.ArrowColor = e.Item.Selected ? Color.White : Color.LightGray;
base.OnRenderArrow(e);
}
protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) {
Point ptEnd = new Point(e.Item.ContentRectangle.X + e.Item.Width / 2, e.Item.ContentRectangle.Y);
using (LinearGradientBrush lgb = new LinearGradientBrush(e.Item.ContentRectangle.Location, ptEnd, Color.Transparent, Color.Gray)) {
lgb.WrapMode = WrapMode.TileFlipX;
using (Pen p = new Pen(lgb)) {
e.Graphics.DrawLine(p, e.Item.ContentRectangle.Location, new Point(e.Item.ContentRectangle.Right, ptEnd.Y));
}
}
//e.Graphics.DrawLine(Pens.Gray, e.Item.ContentRectangle.Location, new Point(e.Item.ContentRectangle.Right, ptEnd.Y));
base.OnRenderSeparator(e);
}
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) {
if (e.Item.Selected)
e.Graphics.FillRectangle(m_brush, e.Item.ContentRectangle);
else
base.OnRenderMenuItemBackground(e);
}
//protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e) {
// //base.OnRenderItemImage(e);
// e.Graphics.DrawImage(e.Image, e.ImageRectangle.X, e.ImageRectangle.Y, 17, 17);
//}
}
}

View File

@ -1,61 +0,0 @@

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
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|Mixed Platforms = Release|Mixed Platforms
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|x86.ActiveCfg = Debug|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Release|Any CPU.Build.0 = Release|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{EFFCC270-4999-4077-A543-56CCCCE92147}.Release|x86.ActiveCfg = Release|Any CPU
{4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Debug|Any CPU.ActiveCfg = Debug|x86
{4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Debug|Mixed Platforms.Build.0 = Debug|x86
{4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Debug|x86.ActiveCfg = Debug|x86
{4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Debug|x86.Build.0 = Debug|x86
{4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Release|Any CPU.ActiveCfg = Release|x86
{4E1829B5-2160-48F5-ABD6-11914A6A25DD}.Release|Mixed Platforms.ActiveCfg = Release|x86
{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

Binary file not shown.

View File

@ -1,9 +0,0 @@
<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

@ -1,14 +0,0 @@
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

@ -1,10 +0,0 @@
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

@ -1,108 +0,0 @@
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

@ -1,130 +0,0 @@
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

@ -1,75 +0,0 @@
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

@ -1,48 +0,0 @@
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

@ -1,31 +0,0 @@
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

@ -1,66 +0,0 @@
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

@ -1,55 +0,0 @@
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

@ -1,70 +0,0 @@
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

@ -1,20 +0,0 @@
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

@ -1,41 +0,0 @@
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

@ -1,87 +0,0 @@
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

@ -1,84 +0,0 @@
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

@ -1,68 +0,0 @@
<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

@ -1,262 +0,0 @@

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

@ -1,73 +0,0 @@
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

@ -1,52 +0,0 @@
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

@ -1,25 +0,0 @@
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

@ -1,514 +0,0 @@
#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

@ -1,32 +0,0 @@
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

@ -1,22 +0,0 @@
<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>

BIN
docs/STNodeEditor.zip Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

BIN
docs/back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

185
docs/css/doc.css Normal file
View File

@ -0,0 +1,185 @@
@charset "utf-8";
*{
color:rgba(255,255,255,.7);
font-family:"consolas","Menlo","DejaVu Sans Mono","monaco","MonoSpace","courier new","微软雅黑","Microsoft Yahei";
font-weight:300;
background-repeat:no-repeat;
/*text-shadow:0px 1px 1px #DDD;*/
-webkit-font-smoothing:antialiased;
}
::selection { background-color: #5B6B8545; }
::-moz-selection { background-color: #5B6B8545; }
::-webkit-selection { background-color: #5B6B8545; }
hr{
height: 1px;
border: none;
margin: 10px;
border-top: solid 1px #2b2b2b;
}
body{
margin:0px;
position:relative;
font-size:14px;
background-color:#343434;
}
h2{
margin:0px 10px;
padding-left:10px;
border-left:solid 5px #0096C9;
border-top:solid 1px #2b2b2b;
border-bottom:solid 1px #2b2b2b;
font-weight:bold;
background-color:#505050;
height:38px;
line-height:38px;
}
p{margin:10px 25px;}
a{
color:#58b4e8;
cursor:pointer;
outline:none;
text-decoration:none;
}
a:hover{
color:#58b4e8;
outline-style:none;
}
table{
margin:20px;
color: rgba(255,255,255,.7);
font-size: 12px;
border-spacing: 0px;
border-top: solid 1px #1f1f1f;
border-left: solid 1px #1f1f1f;
background-color: #2b2b2b;
}
td{
padding: 5px 20px;
border-right: solid 1px #1f1f1f;
border-bottom: solid 1px #1f1f1f;
}
pre{
background-color: #1a1a1a;
font-size: 12px;
overflow: auto;
padding:10px;
display:inline-block;
margin-top:0px;
min-width:680px;
}
.span_center{
display: inline-block;
background-color: #0096c9;
border-radius: 5px;
padding: 1px 5px;
color: white;
font-size: 12px;
}
.span_title{
margin-left:10px;
display: inline-block;
background-color: dodgerblue;
padding: 2px 10px;
/* border-radius: 5px; */
font-size: 12px;
position: relative;
height: 20pxpx;
line-height: 20px;
}
.span_title:after{
content: ' ';
position: absolute;
border-left: solid 13px #2090ff;
border-top: solid 12px transparent;
border-bottom: solid 12px transparent;
right: -13px;
top: 0px;
}
.span_property{
color:white;
font-size: 12px;
border-radius: 2px;
background-color: gray;
padding: 1px 4px;
margin:0px 4px;
}
#div_body{
}
#div_left{
overflow:auto;
height:100%;
width:220px;
position:fixed;
background-color:#343434;
border-right:solid 1px black;
}
.ul_group_root a{
color:gray;
font-weight:300px;
height:30px;
line-height:30px;
display:block;
padding:5px 15px;
transition:background-color .5s;
}
.ul_group_root a:hover{
color:white;
background-color:deepskyblue;
transition:background-color 0s;
}
.ul_group_root,.ul_group_root ul{
margin:0px;
padding:0px;
list-style:none;
font-size:12px;
background-color:#1A1A1A;
}
.a_node_root{
color:gray;
font-size:14px;
padding:5px;
display:block;
background-color:#343434;
/*border-top:1px solid #1A1A1A;
border-bottom:1px solid #1A1A1A;
background-color:rgba(255,255,255,0.05);*/
}
.anchor_btn{
transition:background-color 1s;
}
.anchor_btn.active{
color:white;
background-color:#0076A0;
transition:background-color 0s;
}
.li_node_sub{
text-align:right;
}
#div_right{
margin-left:220px;
}
#div_right a{
color: white;
margin: 2px;
padding: 1px 2px;
border-radius: 2px;
display: inline-block;
font-size:12px;
background-color: rgba(0,150,201,.2);
border:solid 1px #0096C9;
}
#div_right ul{
list-style:none;
margin:5px;
padding:0px 10px;
}
#div_img_node{
text-align:center;
margin:0px 10px;
background-color:black;
}
#div_img_node img{
width:50%;
max-width:537px;
}

View File

@ -3,337 +3,91 @@
font-family:"consolas","Menlo","DejaVu Sans Mono","monaco","MonoSpace","courier new","微软雅黑","Microsoft Yahei";
font-weight:300;
background-repeat:no-repeat;
text-shadow:0px 1px 1px #DDD;
-webkit-font-smoothing:antialiased;
}
::-webkit-scrollbar { display: none; }
h1{
font-size:2.75rem;
}
a{
cursor:pointer;
::selection { background-color: #5B6B8545; }
::-moz-selection { background-color: #5B6B8545; }
::-webkit-selection { background-color: #5B6B8545; }
a{
outline:none;
text-decoration:none;
text-decoration:none;
}
a:hover{
outline-style:none;
}
img{
vertical-align: bottom;
}
span{
display:inline-block;
p{line-height:25px;}
img{max-width:100%;}
ul{margin:0px;padding-left:20px;}
li{margin:5px;}
hr{
height: 1px;
border: none;
margin: 30px 0px;
border-top: solid 1px lightgray;
}
body{
margin:0px;
}
#h1_title{
margin:0px;
position:relative;
display:inline-block;
font-size:14px;
}
#h1_title:before{
#div_body{
max-width:1074px;
margin: 0 auto;
box-shadow: 0px 0px 20px #222;
}
#div_img_node{
text-align:center;
font-size:0px;
overflow:hidden;
background-color:cornflowerblue;
}
#div_img_node img{
width:50%;
max-width:537px;
box-shadow: 0px 0px 20px #222;
}
.span_title{
font-size:40px;
display:inline-block;
margin:20px;
text-align:center;
position:relative;
}
.span_title:before{
content: 'Copyright© DebugST@Crystal_lz';
position: absolute;
left: 50%;
top: 100%;
font-size: 0.75rem;
color: white;
width: 100%;
text-align:center;
font-size: 11px;
color: gray;
width:100%;
transform: translate(-50%, 0px);
}
#h1_title:after{
content: 'V 2.0';
font-size: 0.75rem;
.span_title:after{
content: '(V 1.0)';
font-size: 10px;
position: absolute;
background-color: hotpink;
border-radius: 3px;
padding: 2px 5px;
border-radius: 5px;
padding: 2px;
color: white;
display: block;
top: 0px;
right: 0px;
transform: translate(50%, -50%);
}
#div_top{
background-color:rgba(20,20,20,1);
background-image:url('../images/top_bg.jpeg');
background-size:cover;
text-shadow: 0px 1px 1px black;
}
#div_top_left{
padding: 30px;
display: inline-block;
left: 0px;
top: 0px;
}
#div_top_right{
text-align:center;
color: white;
display: inline-block;
top: 0px;
right: 0px;
}
#div_top_left img{
width:100%;
}
#a_btn_down{
color: white;
background-color: rgba(255,255,255,.3);
padding: 0px 40px;
display: inline-block;
text-align: center;
border-radius: 5px;
margin-bottom:30px;
height:40px;
line-height:40px;
position:relative;
}
#a_btn_down:hover{
background-color:coral;
}
#a_btn_down:before{
content:' ';
display:block;
width:40px;
height:40px;
background-image:url('../images/download.png');
background-size:cover;
position:absolute;
left:0px;
top:0px;
}
#a_btn_down:after{
content:'MIT LICENSE';
font-size:0.75rem;
height:1rem;
line-height:1rem;
display:block;
}
.a_icon{
width: 40px;
height: 40px;
display: inline-block;
background-color: rgba(255,255,255,.3);
line-height: 40px;
text-align: center;
border-radius: 20px;
background-size: contain;
.a_top_btn{
color:white;
padding: 5px 20px;
font-size: 16px;
border-radius: 15px;
background-color: cornflowerblue;
margin:0px 10px;
}
.quote_symbol{
color:gray;
position:relative;
display:inline-block;
}
.quote_symbol:before{
content: "“";
font-size: 3rem;
color: lightgray;
position: absolute;
transform: translate(-100%, -50%);
left: 0px;
top: 0px;
}
.quote_symbol:after{
content: "”";
font-size: 3rem;
color: lightgray;
position: absolute;
top: 100%;
left:100%;
}
.span_mark{
background-color: gold;
padding: 5px 10px;
.span_note{
color:cornflowerblue;
margin: 2px;
padding: 2px 5px;
border-radius: 5px;
font-size:0.8rem;
margin:10px 10px 0px 10px;
}
.span_key{
background-color: lavender;
color: cornflowerblue;
padding: 0px 5px;
margin: 0px 2px;
border-radius: 5px;
}
/*.div_content_left{
position:absolute;
width:50%;
height:500px;
text-align:right;
display:inline-block;
}
.div_content_body{
height:500px;
}*/
.div_content_img img{
max-width:100%;
}
/*.div_content_img{
line-height:500px;
text-align:center;
}
.div_content_img img{
max-width:100%;
position:absolute;
left:50%;
top:50%;
transform:translate(-50%,-50%);
}
.div_content_right{
position:absolute;
left:50%;
height:500px;
display:inline-block;
width:50%;
}
.div_content_text{
margin:0px 50px 0px 30px;
position: absolute;
top: 50%;
transform: translate(0px, -50%);
}*/
.div_content_text h1{
margin-top:1rem;
}
.question_symbol:before{
content: '?';
font-size: 5rem;
position: absolute;
bottom: 0px;
left: 100%;
transform: rotate(45deg);
color: lightgray;
transform-origin:50% 100%;
}
.question_symbol:after{
content: '?';
font-size: 5rem;
position: absolute;
bottom: 0px;
left: 100%;
transform: rotate(90deg);
color: lightgray;
transform-origin:50% 100%;
}
#span_sticker:before{
content: ' ';
display: block;
width: 100px;
height: 100%;
position: absolute;
left: -100px;
background-image: url(../images/sticker.png);
background-size: contain;
background-repeat: no-repeat;
background-position: right;
top:0px;
}
#p_mengbi:after{
content:attr(attr_text);
position: absolute;
font-size: 0.8rem;
width: 100%;
left: 0px;
top: 100%;
color: gray;
}
#div_img_pannel{
position: relative;
width: 926px;
max-width: 100%;
display: inline-block;
}
#div_img_pannel img{
max-width:100%;
}
#a_fork_me{
display: block;
width: 300px;
background-color: green;
position: absolute;
height: 26px;
line-height: 26px;
box-shadow: 0px 0px 3px black;
text-shadow: 0px 0px 3px black;
color: white;
top: 60px;
right: -80px;
transform: rotate(45deg);
}
#a_fork_me:after{
content: ' ';
display: block;
position: absolute;
width: 100%;
top: 2px;
bottom: 2px;
border-top: dashed 1px white;
border-bottom: dashed 1px white;
}
@media screen and (min-width:1080px){
#div_top_left{
margin-right: 400px;
}
#div_top_right{
width:370px;
padding: 30px 30px 0px 0px;
position: absolute;
top: 0px;
right: 0px;
}
.div_content_body{
height:500px;
}
.div_content_left{
position:absolute;
width:50%;
height:500px;
text-align:right;
display:inline-block;
}
.div_content_img{
line-height:500px;
text-align:center;
}
.div_content_img img{
max-width:100%;
position:absolute;
left:50%;
top:50%;
transform:translate(-50%,-50%);
}
.div_content_right{
position:absolute;
left:50%;
height:500px;
display:inline-block;
width:50%;
}
.div_content_text{
margin:0px 50px 0px 30px;
position: absolute;
top: 50%;
transform: translate(0px, -50%);
}
}
@media screen and (max-width:1080px){
#div_top_right{
padding: 10px;
}
.div_content_body {
padding: 10px 0px;
overflow:hidden;
}
.div_content_text{
padding:10px;
}
.div_content_img{
text-align:center;
font-size:0px;
}
.div_content_text h1{
font-size:2rem;
display:block;
text-align:center;
}
background-color: #EEE;
border:solid 1px #DDD;
}

View File

@ -1,277 +0,0 @@
@charset "utf-8";
*{
color:rgba(255,255,255,.7);
font-family:"consolas","Menlo","DejaVu Sans Mono","monaco","MonoSpace","courier new","微软雅黑","Microsoft Yahei";
font-weight:300;
background-repeat:no-repeat;
-webkit-font-smoothing:antialiased;
-webkit-text-size-adjust: none;
}
::selection { background-color: rgba(0,255,255,.2); }
::-moz-selection { background-color: rgba(0,255,255,.2); }
::-webkit-selection { background-color: rgba(0,255,255,.2); }
::-webkit-scrollbar { display: none; }
img{
display:block;
max-width:100%;
}
hr{
height: 1px;
border: none;
border-top: solid 1px #2b2b2b;
}
body{
margin:0px;
position:relative;
font-size:0.85rem;
line-height:1rem;
background-color:#343434;
}
a{
color:#58b4e8;
cursor:pointer;
outline:none;
text-decoration:none;
}
a:hover{
color:#58b4e8;
outline-style:none;
}
table{
margin:10px 0px;
color: rgba(255,255,255,.7);
font-size: 12px;
border-spacing: 0px;
border-top: solid 1px #1f1f1f;
border-left: solid 1px #1f1f1f;
background-color: #2b2b2b;
min-width:500px;
}
th{
padding: 5px 20px;
border-right: solid 1px #1f1f1f;
border-bottom: solid 1px #1f1f1f;
background-color:#505050;
}
.tr_hight{background-color:rgba(255,255,255,.04);}
td{
padding: 5px 20px;
border-right: solid 1px #1f1f1f;
border-bottom: solid 1px #1f1f1f;
}
#div_body{
}
#div_left{
overflow:auto;
height:100%;
width:250px;
position:fixed;
background-color:#343434;
padding-right:10px;
z-index:1;
}
#div_left_list{border-right:solid 1px black;}
#a_btn_left{
display:block;
}
.ul_group_root a{
color:gray;
font-weight:300px;
height:30px;
line-height:30px;
display:block;
padding:5px 15px;
transition:background-color .5s;
}
.ul_group_root a:hover{
color:white;
background-color:deepskyblue;
transition:background-color 0s;
}
.ul_group_root,.ul_group_root ul{
margin:0px;
padding:0px;
list-style:none;
font-size:12px;
background-color:#1A1A1A;
}
.a_node_root{
color:gray;
font-size:14px;
padding:5px;
display:block;
background-color:#343434;
}
.anchor_btn{
transition:background-color 1s;
}
.anchor_btn.active{
color:white;
background-color:#0076A0;
transition:background-color 0s;
}
.li_node_sub{
text-align:right;
}
#div_left:hover{
left:0px;
}
#div_right{
padding:0px 10px;
}
@media screen and (min-width:820px){
#div_right{
margin-left:250px;
}
}
@media screen and (max-width:820px){
#div_left{
left:-250px;
transition:left 1s;
}
#div_left:hover{border-right:solid 1px black;}
#div_left:before{
content: ' ';
position: fixed;
width: 10px;
left: 4px;
top: 0px;
height: 100%;
animation-name: light;
animation-duration: 2s;
animation-iteration-count: 4;
border-left:dashed 2px gray;
transition:left 1s;
}
@keyframes light{
from{opacity: 0;}
50%{opacity: 1;}
to{opacity: 0;}
}
#div_left:hover:before{
left:254px;
transition:left 1s;
}
.h_title{ font-size:1.4rem; }
}
#div_right a{
color: white;
margin: 2px;
padding: 1px 2px;
border-radius: 2px;
display: inline-block;
font-size:12px;
background-color: rgba(0,150,201,.2);
border:solid 1px #0096C9;
}
.h_title{
margin:0px 0px 10px 0px;
padding-left:10px;
border-left:solid 5px dodgerblue;
font-weight:bold;
background-color:#505050;
height:40px;
line-height:40px;
}
.h_option{
margin:0px;
display: inline-block;
background-color: dodgerblue;
padding: 2px 10px;
font-size: 12px;
position: relative;
height: 20pxpx;
line-height: 20px;
}
.h_option:after{
content: ' ';
position: absolute;
border-left: solid 13px #2090ff;
border-top: solid 12px transparent;
border-bottom: solid 12px transparent;
right: -13px;
top: 0px;
}
.span_mark{
color:white;
font-size: 12px;
border-radius: 2px;
background-color: rgba(125,125,125,.5);
padding: 1px 4px;
margin:0px 4px;
}
.p_hightlight{
color: dimgray;
background-color: goldenrod;
padding: 5px;
border-radius: 5px;
}
}
.div_table{
overflow:auto;
}
.div_code{
overflow: auto;
counter-reset: code_line_num;
border: solid 1px black;
vertical-align: bottom;
font-size: 0px;
display:inline-block;
max-width:100%;
margin-bottom:10px;
line-height:0px;
}
.span_code_title{
display: inline-block;
background-color: black;
padding: 2px 5px;
margin-top: 10px;
}
.pre_code{
background-color: #1a1a1a;
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: ' ';
background-color: gray;
width: 30px;
display: block;
position: absolute;
height: 100%;
left: 0px;
top: 0px;
background-color: rgb(43 43 43);
}
.span_code_line{
position:relative;
}
.span_code_line:before{
content: counter(code_line_num);
counter-increment: code_line_num;
position: absolute;
width: 25px;
text-align: right;
left: -35px;
height: 100%;
color:gray;
}
.code_note{ color:gray; }
.code_note_1{ color:bisque; }
.code_key{ color:cornflowerblue; }
.code_class{ color:#0de8e8; }
.code_string{ color:darkorange; }
.span_time{
display: block;
text-align: center;
padding: 2px;
font-size: 12px;
background-color: darkgoldenrod;
}

927
docs/doc.html Normal file
View File

@ -0,0 +1,927 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>STNodeEditor Document</title>
<link rel="stylesheet" type="text/css" href="./css/doc.css"/>
<script type="text/javascript" src="./js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="./js/doc.js"></script>
<script>
$(document).ready(function(){
$(".anchor_btn").click(function(){
var nTop = $(".anchor_point[anchor='" + $(this).attr('anchor') + "']").offset().top;
if(!$(this).hasClass("a_node_root")) nTop -= 5;
$('html,body').animate({scrollTop:nTop},500);
});
$(window).scroll(function(){
var nMin = 100000,strName = '';
var nHeight = document.body.clientHeight;
var nHtmlTop = $(this).scrollTop();
var es = $('.anchor_point');
for(i = 0; i < es.length; i++){
var nSub = Math.abs(es[i].offsetTop - nHtmlTop);
if(nSub < nMin){
nMin = nSub;
if(nHtmlTop + (nHeight / 2) >= es[i].offsetTop)
strName = $(es[i]).attr('anchor');
}
}
//if(nMin > 100) return;
$(".anchor_btn").removeClass('active');
$(".anchor_btn[anchor='" + strName + "']").addClass('active');
});
});
</script>
</head>
<body>
<div id="div_body">
<div id="div_left">
<ul class="ul_group_root">
<li>
<a class="a_node_root anchor_btn active" anchor="a">概述</a>
<ul>
<li class="li_node_sub"><a class="anchor_btn" anchor="b">节点编辑器</a></li>
</ul>
</li>
<li>
<a class="a_node_root anchor_btn" anchor="c">STNodeEditor</a>
<ul>
<li class="li_node_sub"><a class="anchor_btn" anchor="d">属性</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="e">受保护字段</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="f">可重载函数</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="g">公开函数</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="h">事件</a></li>
</ul>
</li>
<li>
<a class="a_node_root anchor_btn" anchor="i">STNode</a>
<ul>
<li class="li_node_sub"><a class="anchor_btn" anchor="j">属性</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="k">受保护字段</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="l">可重载函数</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="m">其他重载函数</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="n">公开函数</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="o">关于重绘</a></li>
</ul>
</li>
<li>
<a class="a_node_root anchor_btn" anchor="p">STNodeOption</a>
<ul>
<li class="li_node_sub"><a class="anchor_btn" anchor="q">属性</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="r">受保护字段</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="s">构造器</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="t">可重载函数</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="u">公开函数</a></li>
<li class="li_node_sub"><a class="anchor_btn" anchor="v">事件</a></li>
</ul>
</li>
<li>
<a class="a_node_root anchor_btn" anchor="w">STNodeOptionEventArgs</a>
<ul>
<li class="li_node_sub"><a class="anchor_btn" anchor="x">属性</a></li>
</ul>
</li>
<li>
<a class="a_node_root anchor_btn" anchor="y">STNodeControl</a>
<ul>
<li class="li_node_sub"><a class="anchor_btn" anchor="z">Demo</a></li>
</ul>
</li>
</ul>
<span style="background-color:blueviolet;font-size:12px;text-align:center;width:100%;display:inline-block;">最后编辑时间 2021-01-21</span>
</div>
<div id="div_right">
<h2 class="anchor_point" anchor="a">概述</h2>
<p>那是一个冬季 在研究无线电安全的作者接触到了<a target="_bank" href="https://www.gnuradio.org/">GNURadio</a> 那是作者第一次接触到节点编辑器<br/>&nbsp;&nbsp;"What? Excuse me... What's this?.. 这是什么鬼东西?..."</p>
<p>那是一个春季 不知道为什么 过完年整个世界都变了 大家被迫窝在家里 无聊至极的作者学起了<a target="_bank" href="https://www.blender.org/">Blender</a> 那是作者第二次接触到节点编辑器<br/>&nbsp;&nbsp;"Wo...原来这东西可以这么玩...真方便"<br/>&nbsp;&nbsp;于是一些想法在作者脑中逐渐诞生 让作者有了想做一个这样的东西的想法</p>
<p>那是一个夏季 不知道为什么 作者又玩起了<a target="_bank" href="http://www.blackmagicdesign.com/cn/products/davinciresolve/">Davinci</a> 那是作者第三次接触到节点编辑器 这一次的接触让作者对节点编辑器的好感倍增 作者瞬间觉得 只要是可以模块化流程化的功能 万物皆可节点化</p>
<hr/>
<center><span class="span_center">于是下面的这个东西就诞生了</span></center>
<div id="div_img_node"><img src="./images/node.png"/><img src="./images/node.gif"/></div>
<center><span class="span_center">或者这样的一个东西</span></center>
<div style="text-align:center;margin:0px 10px;background-color:black;"><img style="width:1077px;max-width:100%;" src="./images/channel.png"/></div>
<p>本案例中只是提供了一个节点编辑器控件 并不包含节点列表 属性编辑器等 若后期有空再做一套完整的框架</p>
<hr/>
<span class="span_title anchor_point" anchor="b">节点编辑器</span>
<hr/>
<p>或许你未曾接触过节点编辑 但节点编辑的影子越来越多 尤其是在影视相关的一些设计类软件当中 Blender,C4D,Houdini,Davinci 等</p>
<p>节点编辑最大的好处就是可视化操作 将单一的功能点封装到节点之中 让用户通过节点布线来组合自己需要的逻辑 让整个流程可视化 而不是将你程序的整个执行流程固化在你的程序之中 当然在这之前你需要定义好节点的数据类型 <span style="color:orangered">因为数据类型无法兼容是不允许连线的</span> 通常情况下同颜色的连接点表示数据类型相同</p>
<p>另一个好处 让开发者只需要注重单一的功能点开发 使得功能与功能之间的耦合度降低 开发者在节点中开发完成需要的功能 无需知道应该把数据交给谁或者怎么去传递数据 只需要将你的结果数据打包给你所继承的节点类 节点编辑器会自动完成数据的投递过程
</p>
<h2 class="anchor_point" anchor="c">STNodeEditor</h2>
<p>STNodeEditor为节点容器 其中最重要的一个属性为<span class="span_property">Nodes</span>里面包含了画布中能看到的所有节点</p>
<p>STNodeEditor有两套坐标系统 平常情况下使用最多的就直接是控件的坐标系统 需要在控件的什么位置绘图或者添加东西采用的都是相对控件左上角的坐标 但是在节点编辑器中的情况比较复杂 需要引入一个画布的概念 画布是可以移动缩放的 所以在绘图的时候依旧采用控件的坐标系统将带来很多的麻烦 所以为画布定义了一个坐标原点 在节点以及连线绘制的时候采用的是画布的坐标系统 当需要拖动画布位置的时候 改变画布原点的位置就好了 而内部节点的位置依旧不做变更 因为节点采用的坐标位置是相对画布原点的
<br/><span style="color:orangered">当画布中<span class="span_property">Nodes</span>没有元素时候 画布将处于重置状态无法缩放与移动 因为作者觉得这样的操作没有任何意义</span></p>
<hr/>
<span class="span_title anchor_point" anchor="d">属性</span>
<hr/>
<table>
<tr style="text-align:center;background-color:#505050;">
<td>属性名称</td><td>类型</td><td>描述</td>
</tr>
<tr>
<td>CanvasOffsetX</td><td>float</td><td>获取画布原点相对于控件 X 方向上的偏移位置</td>
</tr>
<tr>
<td>CanvasOffsetY</td><td>float</td><td>获取画布原点相对于控件 Y 方向上的偏移位置</td>
</tr>
<tr>
<td>CanvasOffset</td><td>PointF</td><td>获取画布原点相对于控件偏移位置</td>
</tr>
<tr>
<td>CanvasValidBounds</td><td>Rectangle</td><td>获取画布中的有被用到的有效区域</td>
</tr>
<tr>
<td>CanvasScale</td><td>float</td><td>获取画布的缩放比例</td>
</tr>
<tr>
<td>Curvature</td><td>float</td><td>获取或设置 Option 之间连线的曲度</td>
</tr>
<tr>
<td>Magnet</td><td>bool</td><td>获取或设置移动画布中 Node 时候 是否启用磁铁效果</td>
</tr>
<tr>
<td>ShowBorder</td><td>bool</td><td>获取或设置移动画布中是否显示 Node 边框</td>
</tr>
<tr>
<td>ShowGrid</td><td>bool</td><td>获取或设置画布中是否绘制背景网格线条</td>
</tr>
<tr>
<td>ShowLocation</td><td>bool</td><td>获取或设置是否在画布边缘显示超出视角的 Node 位置信息</td>
</tr>
<tr>
<td>Nodes</td><td>STNodeCollection</td><td>获取画布中 Node 集合</td>
</tr>
<tr>
<td>ActiveNode</td><td>STNode</td><td>获取当前画布中被选中的活动 Node</td>
</tr>
<tr>
<td>HoverNode</td><td>STNode</td><td>获取当前画布中鼠标悬停的 Node</td>
</tr>
<tr>
<td>GridColor</td><td>Color</td><td>获取或设置绘制画布背景时 网格线条颜色</td>
</tr>
<tr>
<td>BorderColor</td><td>Color</td><td>获取或设置画布中 Node 边框颜色</td>
</tr>
<tr>
<td>BorderHoverColor</td><td>Color</td><td>获取或设置画布中悬停 Node 边框颜色</td>
</tr>
<tr>
<td>BorderSelectColor</td><td>Color</td><td>获取或设置画布中选中 Node 边框颜色</td>
</tr>
<tr>
<td>BorderActiveColor</td><td>Color</td><td>获取或设置画布中活动 Node 边框颜色</td>
</tr>
<tr>
<td>MarkForeColor</td><td>Color</td><td>获取或设置画布绘制 Node 标记详情采用的前景色</td>
</tr>
<tr>
<td>MarkBackColor</td><td>Color</td><td>获取或设置画布绘制 Node 标记详情采用的背景色</td>
</tr>
<tr>
<td>MagnetLineColor</td><td>Color</td><td>获取或设置画布中移动 Node 时候 磁铁标记线颜色</td>
</tr>
<tr>
<td>SelectedRectangleColor</td><td>Color</td><td>获取或设置画布中选择矩形区域的颜色</td>
</tr>
<tr>
<td>HighLineColor</td><td>Color</td><td>获取或设置画布中高亮连线的颜色</td>
</tr>
<tr>
<td>LocationForeColor</td><td>Color</td><td>获取或设置画布中边缘位置提示区域前景色</td>
</tr>
<tr>
<td>LocationBackColor</td><td>Color</td><td>获取或设置画布中边缘位置提示区域背景色</td>
</tr>
<tr>
<td>UnknownTypeColor</td><td>Color</td><td>获取或设置画布中当 Node 中 Option 数据类型无法确定时应当使用的颜色</td>
</tr>
<tr>
<td>TypeColor</td><td>Dictionary&lt;Type, Color&gt;</td><td>获取或设置画布中 Node 中 Option 数据类型预设颜色</td>
</tr>
</table>
<span class="span_title anchor_point" anchor="e">受保护字段</span>
<table>
<tr style="text-align:center;background-color:#505050;">
<td>属性名称</td><td>类型</td><td>描述</td>
</tr>
<tr>
<td>m_pt_in_control</td><td>Point</td><td>当前鼠标在控件中的实时位置</td>
</tr>
<tr>
<td>m_pt_in_canvas</td><td>PointF</td><td>当前鼠标在画布中的实时位置</td>
</tr>
<tr>
<td>m_pt_down_in_control</td><td>Point</td><td>鼠标点击时在控件上的位置</td>
</tr>
<tr>
<td>m_pt_down_in_canvas</td><td>PointF</td><td>鼠标点击时在画布中的位置</td>
</tr>
<tr>
<td>m_pt_canvas_old</td><td>PointF</td><td>用于鼠标点击移动画布时候 鼠标点下时候的画布坐标位置</td>
</tr>
<tr>
<td>m_pt_dot_down</td><td>Point</td><td>用于保存连线过程中保存点下 Option 的起点坐标</td>
</tr>
<tr>
<td>m_option_down</td><td>STNodeOption</td><td>用于保存连线过程中鼠标点下的起点Option 当MouseUp时候 确定是否连接此节点</td>
</tr>
</table>
<ul>
<li>
<span class="span_property" style="background-color:#0090c9;">TypeColor</span>
<ul>
<li>此属性为当前<span class="span_property">STNodeEditor</span>中所有的数据类型所对应的颜色 节点中所有的数据类型都应当包含在此集合中 即便有动态需要加载的未知类型的节点 那么对应的节点也应该在节点的<span class="span_property">OnOwnerChanged()</span>中向容器提交自己数据类型以及对应颜色
<br/><span style="color:orangered;">若节点有单独对<span class="span_property">STNodeOption.DotColor</span>设置值 则忽略该对照表中的颜色 即不为默认颜色<span class="span_property">Color.Transparent</span></span></li>
</ul>
</li>
<hr/>
<li>
<span class="span_property" style="background-color:#0090c9">UnknownTypeColor</span>
<ul>
<li>若节点中并没有单独设置数据类型颜色且在<span class="span_property">TypeColor</span>中无法匹配时候采用此值 所以将此特殊类型单独设置</li>
</ul>
</li>
</ul>
<hr/><span class="span_title anchor_point" anchor="f">可重载函数</span><hr/>
<table>
<tr style="background-color:#505050"><td colspan="2">当绘制背景网格线时候发生</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawGrid(DrawingTools dt, int nWidth, int nHeight)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr>
<td>nWidth</td><td>需要绘制宽度</td>
</tr>
<tr>
<td>nHeight</td><td>需要绘制高度</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">当绘制 Node 时候发生</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawNode(DrawingTools dt, Rectangle rect)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr>
<td>rect</td><td>可视画布区域大小</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">当绘制已连接路径时候发生</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawConnectedLine(DrawingTools dt)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">当绘制 Mark 详情信息时候发生</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawMark(DrawingTools dt)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">当移动 Node 时候 需要显示对齐参考线时候发生</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawMagnetLine(DrawingTools dt, MagnetInfo mi)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr>
<td>mi</td><td>匹配的磁铁信息</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">绘制选择的矩形区域</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawSelectedRectangle(DrawingTools dt, RectangleF rectf)</td></tr>
<tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr>
<td>rectf</td><td>位于控件上的矩形区域</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">绘制超出视觉区域的 Node 位置提示信息</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawNodeOutLocation(DrawingTools dt, Size sz, List&lt;Point&gt; lstPts)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr>
<td>sz</td><td>提示框边距</td>
<tr>
<td>lstPts</td><td>超出视觉区域的 Node 位置信息</td>
</tr>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">绘制提示信息</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawAlert(DrawingTools dt, Rectangle rect, string strText, Color foreColor, Color backColor, AlertLocation al)</td></tr>
<tr><td>dt</td><td>绘制工具</td></tr>
<tr><td>rect</td><td>需要绘制区域</td></tr>
<tr><td>strText</td><td>需要绘制文本</td></tr>
<tr><td>foreColor</td><td>信息前景色</td></tr>
<tr><td>backColor</td><td>信息背景色</td></tr>
<tr><td>al</td><td>信息位置</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">获取提示信息需要绘制的矩形区域</td></tr>
<tr style="background-color:#505050"><td colspan="2">Rectangle GetAlertRectangle(Graphics g, string strText, AlertLocation al)</td></tr>
<tr><td>g</td><td>绘图表面</td></tr>
<tr><td>strText</td><td>需要绘制文本</td></tr>
<tr><td>al</td><td>信息位置</td></tr>
</table>
<span class="span_title anchor_point" anchor="g">公开函数</span>
<table>
<tr style="background-color:#505050"><td colspan="2">通过画布坐标进行寻找</td></tr>
<tr style="background-color:#505050"><td colspan="2">NodeFindInfo FindNodeFromPoint(PointF pt)</td></tr>
<tr>
<td>pt</td><td>画布中的坐标</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">获取已经被选择的 Node 集合</td></tr>
<tr style="background-color:#505050"><td colspan="2">STNode[] GetSelectedNode()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">将画布坐标转换为控件坐标</td></tr>
<tr style="background-color:#505050"><td colspan="2">float CanvasToControl(XXX xxx)</td></tr>
<tr>
<td>xxx</td><td>Rectangle,RectangleF,Point,PointF...</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">将控件坐标转换为画布坐标</td></tr>
<tr style="background-color:#505050"><td colspan="2">float ControlToCanvas(XXX xxx)</td></tr>
<tr>
<td>xxx</td><td>Rectangle,RectangleF,Point,PointF...</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">移动画布原点坐标到指定的控件坐标位置</td></tr>
<tr style="background-color:#505050"><td colspan="2">void MoveCanvas(float x, float y, bool bAnimation, CanvasMoveArgs ma)</td></tr>
<tr>
<td>x</td><td>X 坐标</td>
</tr>
<tr>
<td>y</td><td>Y 坐标</td>
</tr>
<tr>
<td>bAnimation</td><td>移动过程中是否启动动画效果</td>
</tr>
<tr>
<td>ma</td><td>指定需要修改的坐标参数</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">缩放画布</td></tr>
<tr style="background-color:#505050"><td colspan="2">void ScaleCanvas(float f, float x, float y)</td></tr>
<tr>
<td>f</td><td>缩放比例</td>
</tr>
<tr>
<td>x</td><td>以指定控件坐标 X 为中心进行缩放</td>
</tr>
<tr>
<td>y</td><td>以指定控件坐标 Y 为中心进行缩放</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">获取当前已连接的 Option 对应关系</td></tr>
<tr style="background-color:#505050"><td colspan="2">ConnectionInfo[] GetConnectionInfo()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">判断两个 Node 之间是否存在连接路径</td></tr>
<tr style="background-color:#505050"><td colspan="2">static bool CanFindNodePath(STNode nodeStart, STNode nodeFind)</td></tr>
<tr>
<td>nodeStart</td><td>起始 Node</td>
</tr>
<tr>
<td>nodeFind</td><td>目标 Node</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">获取画布中指定矩形区域图像</td></tr>
<tr style="background-color:#505050"><td colspan="2">Image GetCanvasImage(Rectangle rect, float fScale)</td></tr>
<tr>
<td>rect</td><td>画布中指定的矩形区域</td>
</tr>
<tr>
<td>fScale</td><td>缩放比例</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">保存画布中的类容到文件中</td></tr>
<tr style="background-color:#505050"><td colspan="2">void SaveCanvas(string strFileName)</td></tr>
<tr>
<td>strFileName</td><td>文件路径</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">保存画布中的类容到数据流</td></tr>
<tr style="background-color:#505050"><td colspan="2">void SaveCanvas(Stream s)</td></tr>
<tr>
<td>s</td><td>数据流对象</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">获取画布中内容二进制数据</td></tr>
<tr style="background-color:#505050"><td colspan="2">byte[] GetCanvasData()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">加载程序集</td></tr>
<tr style="background-color:#505050"><td colspan="2">bool LoadAssembly(string strFile)</td></tr>
<tr>
<td>返回值</td><td>此文件中是否有类型被加载</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">加载程序集</td></tr>
<tr style="background-color:#505050"><td colspan="2">int LoadAssembly(string[] strFiles)</td></tr>
<tr>
<td>返回值</td><td>存在STNode类型的文件的个数</td>
</tr>
<tr><td colspan="2" style="background-color:#343434"><span style="color:orangered">编辑器中的节点并不一定来自自身程序集 也可能包含在其他程序集中 这时候需提前加载对应程序集 对应的<span class="span_property">STNode</span>才能正确的从文件或者数据中动态加载到画布类容</span></td></tr>
<tr style="background-color:#505050"><td colspan="2">获取当前编辑器中已加载的Node类型</td></tr>
<tr style="background-color:#505050"><td colspan="2">Type[] GetTypes()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">从文件中加载数据</td></tr>
<tr style="background-color:#505050"><td colspan="2">void LoadCanvas(string strFileName)</td></tr>
<tr>
<td>strFileName</td><td>文件路径</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">从二进制加载数据</td></tr>
<tr style="background-color:#505050"><td colspan="2">void LoadCanvas(byte[] byData)</td></tr>
<tr>
<td>byData</td><td>二进制数据</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">从数据流中加载数据</td></tr>
<tr style="background-color:#505050"><td colspan="2">void LoadCanvas(Stream s)</td></tr>
<tr>
<td>s</td><td>数据流对象</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">在画布中显示提示信息</td></tr>
<tr style="background-color:#505050"><td colspan="2">void ShowAlert(string strText, Color foreColor, Color backColor, int nTime, AlertLocation al, bool bRedraw)</td></tr>
<tr><td>strText</td><td>要显示的信息</td></tr>
<tr><td>foreColor</td><td>信息前景色</td></tr>
<tr><td>backColor</td><td>信息背景色</td></tr>
<tr><td>nTime</td><td>信息持续时间</td></tr>
<tr><td>al</td><td>信息要显示的位置</td></tr>
<tr><td>bRedraw</td><td>是否立即重绘</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">设置画布中活动的节点</td></tr>
<tr style="background-color:#505050"><td colspan="2">STNode SetActiveNode(STNode node)</td></tr>
<tr><td>STNode</td><td>需要被设置为活动的节点</td></tr>
<tr><td>返回值</td><td>设置前的活动节点</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">向编辑器中添加默认数据类型颜色</td></tr>
<tr style="background-color:#505050"><td colspan="2">SetTypeColor(Type t, Color clr)</td></tr>
<tr><td>t</td><td>数据类型</td></tr>
<tr><td>clr</td><td>对应颜色</td></tr>
</table>
<hr/><span class="span_title anchor_point" anchor="h">事件</span><hr/>
<table>
<tr><td>SelectedChanged</td><td>选择的节点发生变化时候发生</td><tr/>
<tr><td>HoverChanged</td><td>悬停的节点发生变化时候发生</td><tr/>
<tr><td>NodeAdded</td><td>当节点被添加时候发生</td><tr/>
<tr><td>NodeRemoved</td><td>当节点被移除时候发生</td><tr/>
<tr><td>CanvasMoved</td><td>移动画布原点时候发生</td><tr/>
<tr><td>CanvasScaled</td><td>缩放画布时候发生</td><tr/>
<tr><td>OptionConnected</td><td>连接节点选项时候发生</td><tr/>
<tr><td>OptionDisConnected</td><td>断开节点选项时候发生</td><tr/>
<tr><td>OptionConnecting</td><td>正在连接节点选项时候发生</td><tr/>
<tr><td>OptionDisConnecting</td><td>正在断开节点选项时候发生</td><tr/>
</table>
<ul>
<li>
<span class="span_property" style="background-color:#0090c9;">Option(Dis)Connected</span>
<ul>
<li><span style="color:orangered">此事件触发时并不代表当前画布中的节点连线成功</span> 请通过获取事件参数中的<span class="span_property">Status</span>字段进行判断
<br/>但是<span class="span_property">STNodeOption.Connected</span>事件是成功后才触发 之所以要这样设计是因为考虑到 对于<span class="span_property">STNodeEditor</span>来说 它需要知道一个连接的状态 即使连接不成功也应当告知前端用户为什么会连接不成功 所以失败和成功都会触发事件</li>
</ul>
</li>
<hr/>
<li>
<span class="span_property" style="background-color:#0090c9">Option(Dis)Connecting</span>
<ul>
<li>此事件是在连接或者断开连接正在发生的时候触发 <span style="color:orangered">可通过事件参数<span class="span_property">Continue</span>来决定是否继续或者停止操作</span></li>
</ul>
</li>
</ul>
<h2 class="anchor_point" anchor="i">STNode</h2>
<p><span style="color:orangered"><span class="span_property">STNode</span>为抽象类 不可实例化 需要用户自己继承重写</span></p>
<p><span class="span_property">STNode</span>有三个比较重要的属性<span class="span_property">InputOptions</span><span class="span_property">OutputOptions</span><span class="span_property">Controls</span></p>
<p>若把<span class="span_property">STNodeEditor</span>类比成桌面 那么<span class="span_property">STNode</span>就可以类比成一个窗体 虽然作者在节点中提供了<span class="span_property">Controls</span>集合 但是作者没有提供任何的一个封装好的控件 只提供了一个<span class="span_property">STNodeControl</span>基类 若有需要用户可继承此类绘制自己需要的控件</p>
<p><span style="color:orangered">继承<span class="span_property">STNode</span>必须提供空参构造器 否则对节点进行保存后无法还原节点 因为还原过程并非序列化 而是通过反射重新构造对象</span></p>
<hr/>
<span class="span_title anchor_point" anchor="j">属性</span>
<hr/>
<table>
<tr style="text-align:center;background-color:#505050;">
<td>属性名称</td><td>类型</td><td>描述</td>
</tr>
<tr><td>Owner</td><td>STNodeEditor</td><td>获取当前 Node 所有者</td></tr>
<tr><td>IsSelected</td><td>bool</td><td>获取或设置 Node 是否处于被选中状态</td></tr>
<tr><td>IsActive</td><td>bool</td><td>获取 Node 是否处于活动状态</td></tr>
<tr><td>TitleColor</td><td>Color</td><td>获取或设置标题背景颜色</td></tr>
<tr><td>MarkColor</td><td>Color</td><td>获取或设置标记信息背景颜色</td></tr>
<tr><td>ForeColor</td><td>Color</td><td>获取或设置当前 Node 前景色</td></tr>
<tr><td>BackColor</td><td>Color</td><td>获取或设置当前 Node 背景色</td></tr>
<tr><td>Title</td><td>string</td><td>获取或设置 Node 标题</td></tr>
<tr><td>Mark</td><td>string</td><td>获取或设置 Node 标记信息</td></tr>
<tr><td>MarkLines</td><td>string[]</td><td>获取 Node 标记信息行数据</td></tr>
<tr><td>Left</td><td>int</td><td>获取或设置 Node 左边坐标</td></tr>
<tr><td>Top</td><td>int</td><td>获取或设置 Node 上边坐标</td></tr>
<tr><td>Width</td><td>int</td><td>获取或设置 Node 宽度</td></tr>
<tr><td>Height</td><td>int</td><td>获取或设置 Node 高度</td></tr>
<tr><td>Right</td><td>int</td><td>获取 Node 右边边坐标</td></tr>
<tr><td>Bottom</td><td>int</td><td>获取 Node 下边坐标</td></tr>
<tr><td>Rectangle</td><td>Rectangle</td><td>获取 Node 矩形区域</td></tr>
<tr><td>TitleRectangle</td><td>Rectangle</td><td>获取 Node 标题矩形区域</td></tr>
<tr><td>MarkRectangle</td><td>Rectangle</td><td>获取 Node 标记矩形区域</td></tr>
<tr><td>TitleHeight</td><td>int</td><td>获取或设置 Node 标题高度</td></tr>
<tr><td>InputOptions</td><td>STNodeOptionCollection</td><td>获取输入选项集合</td></tr>
<tr><td>InputOptionsCount</td><td>int</td><td>获取输入选项集合个数</td></tr>
<tr><td>OutputOptions</td><td>STNodeOptionCollection</td><td>获取输出选项</td></tr>
<tr><td>OutputOptionsCount</td><td>int</td><td>获取输出选项个数</td></tr>
<tr><td>Controls</td><td>STNodeControlCollection</td><td>获取 Node 所包含的控件集合</td></tr>
<tr><td>ControlsCount</td><td>int</td><td>获取 Node 所包含的控件集合个数</td></tr>
<tr><td>Location</td><td>Point</td><td>获取 Node 坐标位置</td></tr>
<tr><td>Size</td><td>Size</td><td>获取 Node 大小</td></tr>
<tr><td>Font</td><td>Font</td><td>获取或设置 Node 字体</td></tr>
<tr><td>LockOption</td><td>bool</td><td>获取或设置是否锁定Option选项 锁定后不在接受连接</td></tr>
<tr><td>LockLocation</td><td>bool</td><td>获取或设置是否锁定Node位置 锁定后不可移动</td></tr>
<tr><td>ContextMenuStrip</td><td>ContextMenuStrip</td><td>获取或设置当前Node 上下文菜单</td></tr>
<tr><td>Tag</td><td>object</td><td>获取或设置用户自定义保存的数据</td></tr>
</table>
<ul>
<li>
<span class="span_property" style="background-color:#0090c9;">Lock(Option/Location)</span>
<ul>
<li><img src="./images/lock.gif"/><br/><span class="span_property">LockOption</span>被设定后左上角会出现一把锁 表示节点内所有选项不再接受连接 但可以与其他节点选项断开连接 <span style="color:orangered">但是其他节点也同为被锁定状态 则无法断开</span>
<br/><span class="span_property">LockLocation</span>被设定后右上角会出现一枚图钉 表示节点位置不再接受设置</li>
</ul>
</li>
</ul>
<hr/><span class="span_title anchor_point" anchor="k">受保护字段</span>
<table>
<tr style="text-align:center;background-color:#505050;">
<td>属性名称</td><td>类型</td><td>描述</td>
</tr>
<tr><td>m_ctrl_active</td><td>STNodeControl</td><td>当前Node中 活动的控件</td></tr>
<tr><td>m_ctrl_hover</td><td>STNodeControl</td><td>当前Node中 悬停的控件</td></tr>
</table>
<p><span style="color:orangered">在上述属性中除了<span class="span_property">Mark</span><span class="span_property">Left</span><span class="span_property">Top</span>其余均为只读属性 被<span class="span_property">protected</span>所修饰 仅继承<span class="span_property">STNode</span>后才可访问 不然作者觉得太危险了 比如下面这段代码</span></p>
<p><span class="span_property">NodeXXX.Owner.Nodes[nIndex].InputOptions.RemoveAt(0);</span></p>
<p>节点都是模块化的 可能会有不同的人来做开发 甚至是以插件的方式提供 如果大家都遵守约定那将很好 可如果一旦有人不遵守约定 或者出现意外情况 那么就可能出现跨节点操作 破坏掉原本一个正常的节点 如果确实有需要请更改源代码</p>
<hr/>
<span class="span_title anchor_point" anchor="l">可重载函数</span>
<table>
<tr style="background-color:#505050"><td colspan="2">当Node被构造时候发生</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnCreate()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">当所有者发生改变时候发生</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnOwnerChanged()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">当选中状态改变时候发生</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnSelectedChanged()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">当活动状态改变时候发生</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnActiveChanged()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">绘制整个Node</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawNode(DrawingTools dt)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">绘制Node标题部分</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawTitle(DrawingTools dt)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">绘制Node主体部分 除去标题部分</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawBody(DrawingTools dt)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">绘制标记信息</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawMark(DrawingTools dt)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr><tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">绘制选项连线的点</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawOptionDot(DrawingTools dt, STNodeOption op)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr>
<td>op</td><td>需要绘制的Option</td>
</tr></tr><tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">绘制选项的文本</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnDrawOptionText(DrawingTools dt, STNodeOption op)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
<tr/>
<tr>
<td>op</td><td>需要绘制的Option</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">当计算Option连线点位置时候发生</td></tr>
<tr style="background-color:#505050"><td colspan="2">Point OnSetOptionDotLocation(STNodeOption op, Point pt)</td></tr>
<tr>
<td>op</td><td>需要计算的Option</td>
</tr>
<tr>
<td>op</td><td>自动计算出的位置</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">当计算Option文本区域时候发生</td></tr>
<tr style="background-color:#505050"><td colspan="2">Rectangle OnSetOptionTextRectangle(STNodeOption op, Rectangle rect)</td></tr>
<tr>
<td>op</td><td>需要计算的Option</td>
</tr>
<tr>
<td>op</td><td>自动计算出的区域</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">计算当前Node所需要的矩形区域</td></tr>
<tr style="background-color:#505050"><td colspan="2">Size OnBuildNodeSize(DrawingTools dt)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr>
<td colspan="2" style="background-color:#343434"><span style="color:orangered">若需要自己重绘Node 则应当重写此函数 以确定绘图区域大小
<br/>返回的大小并不会限制绘制区域 任然可以在此区域之外绘制
<br/>但是并不会被STNodeEditor所接受 并触发对应事件</span></td>
</tr>
<tr style="background-color:#505050"><td colspan="2">计算当前Mark所需要的矩形区域</td></tr>
<tr style="background-color:#505050"><td colspan="2">Rectangle OnBuildMarkRectangle(DrawingTools dt)</td></tr>
<tr>
<td>dt</td><td>绘制工具</td>
</tr>
<tr>
<td colspan="2" style="background-color:#343434"><span style="color:orangered">若需要自己重绘Mark 则应当重写此函数 以确定绘图区域大小
<br/>返回的大小并不会限制绘制区域 任然可以在此区域之外绘制
<br/>但是并不会被STNodeEditor所接受 并触发对应事件</span></td>
</tr>
<tr style="background-color:#505050"><td colspan="2">当需要保存时候 此Node有哪些需要额外保存的数据</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnSaveNode(Dictionary&lt;string, byte[]&gt;)</td></tr>
<tr>
<td>dic</td><td>需要保存的数据</td>
</tr>
<tr><td colspan="2" style="background-color:#343434"><span style="color:orangered">保存时并不会进行序列化 仅自动保存部分(<span class="span_property">Guid,Left,Top,Mark,LockOption,LockLocation</span>)属性 还原时候仅重新通过空参数构造器创建此Node
<br/>然后调用<span class="span_property">OnLoadNode()</span>将保存的数据进行还原</span></td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnLoadNode(Dictionary&lt;string, byte[]&gt; dic)</td></tr>
<tr>
<td>dic</td><td>保存时候的数据</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">当编辑器加载完成所有的节点时候发生</td></tr>
<tr style="background-color:#505050"><td colspan="2">void OnEditorLoadCompleted()</td></tr>
</table>
<div style="overflow:auto;margin:0px 10px;max-width:100%;"><pre><span style="color:cornflowerblue">protected override void</span> OnSaveNode(<span style="color:cyan">Dictionary</span>&lt;<span style="color:cornflowerblue">string</span>, <span style="color:cornflowerblue">byte</span>[]&gt;) {
dic.Add(<span style="color:lime">"count"</span>, <span style="color:cyan">BitConverter</span>.GetBytes(<span style="color:cornflowerblue">this</span>.InputOptionsCount));
}
<span style="color:cornflowerblue">protected</span> <span style="color:cornflowerblue">internal</span> <span style="color:cornflowerblue">override void </span>OnLoadNode(<span style="color:cyan">Dictionary</span>&lt;<span style="color:cornflowerblue">string</span>, <span style="color:cornflowerblue">byte</span>[]&gt; dic) {
<span style="color:cornflowerblue">int</span> nCount = <span style="color:cyan">BitConverter</span>.ToInt32(dic[<span style="color:lime">"count"</span>], 0);
<span style="color:cornflowerblue">while</span> (<span style="color:cornflowerblue">this</span>.InputOptionsCount &lt; nCount && <span style="color:cornflowerblue">this</span>.InputOptionsCount != nCount)
<span style="color:cornflowerblue">this</span>.Addhub();
}
</pre></div>
<p><img style="width:116px;" src="./images/object_type.png"/>
<br/>上面代码片段为<span class="span_property">STNodeHub</span>重载 当需要保存时候此节点需要保存当前已拥有的行数 还原的时候需要将行数还原 因为新构造的节点只有一行 若上面已有连线关系的时候只有一行可能无法正常还原连线关系</p>
<hr/>
<div><pre><span style="color:cornflowerblue">protected override void</span> OnSaveNode(<span style="color:cyan">Dictionary</span>&lt;<span style="color:cornflowerblue">string</span>, <span style="color:cornflowerblue">byte</span>[]&gt;) {
dic.Add(<span style="color:lime">"file"</span>, <span style="color:cyan">Encoding</span>.UTF8.GetBytes(m_str_file));
}
<span style="color:cornflowerblue">protected</span> <span style="color:cornflowerblue">override void </span>OnLoadNode(<span style="color:cyan">Dictionary</span>&lt;<span style="color:cornflowerblue">string</span>, <span style="color:cornflowerblue">byte</span>[]&gt; dic) {
m_str_file = <span style="color:cyan">Encoding</span>.UTF8.GetString(dic[<span style="color:lime">"file"</span>]);
if (System.IO.<span style="color:cyan">File</span>.Exists(m_str_file)) { <span style="color:lime">//如果文件存在加载并投递数据</span>
m_option_out.TransferData(<span style="color:cyan">Image</span>.FromFile(m_str_file));
}
}</pre></div>
<p><img style="width:173px;" src="./images/open_image.png"/>
<br/>上面代码片段为DEMO中<span class="span_property">STNodeImageInput</span>重载 因为保存时候 需要把已经打开的文件路劲一起保存 还原的时候再次打开文件并传递数据</p>
<hr/>
<span class="span_title anchor_point" anchor="m">其他重载函数</span>
<p>
<span class="span_property">OnGotFocus</span><span class="span_property">OnLostFocus</span><span class="span_property">OnMouse***</span><span class="span_property">OnKey***</span><span class="span_property">OnMove</span>
</p>
<span class="span_title anchor_point" anchor="n">公开函数</span>
<table>
<tr style="background-color:#505050"><td colspan="2">重绘Node</td></tr>
<tr style="background-color:#505050"><td colspan="2">void Invalidate()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">重绘 Node 指定区域</td></tr>
<tr style="background-color:#505050"><td colspan="2">void Invalidate(Rectangle rect)</td></tr>
<tr>
<td>rect</td><td>Node 指定区域</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">获取此Node所包含的输入Option集合</td></tr>
<tr style="background-color:#505050"><td colspan="2">STNodeOption[] GetInputOptions()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">获取此Node所包含的输出Option集合</td></tr>
<tr style="background-color:#505050"><td colspan="2">STNodeOption[] GetOutputOptions()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">设置Node的选中状态</td></tr>
<tr style="background-color:#505050"><td colspan="2">void SetSelected(bool bSelected, bool bRedraw)</td></tr>
<tr>
<td>bSelected</td><td>是否选中</td>
</tr>
<tr>
<td>bRedraw</td><td>是否立即重绘</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">此函数参照<span class="span_property">System.Windows.Forms.Control</span></td></tr>
<tr style="background-color:#505050"><td colspan="2">XXX Begin/Invoke(Delegate method, params object[] args)</td></tr>
</table>
<span class="span_title anchor_point" anchor="o">关于重绘</span>
<p><img style="max-width:537px;" src="./images/node.png"/>
<br/>若无特殊需求 仅仅需要的是输入输出点就足够了 那么无需用户进行重绘操作 上图 "Demo_Node" 就是默认的绘制 用户只需要在节点中加入需要的输入输出选项即可</p>
<div style="overflow:auto;margin:0px 10px;max-width:100%"><pre><span style="color:cornflowerblue">protected</span> <span style="color:cornflowerblue">override</span> <span style="color:cornflowerblue">void</span> OnCreate() {
<span style="color:cornflowerblue">base</span>.OnCreate();
<span style="color:cornflowerblue">this</span>.InputOptions.Add(<span style="color:cornflowerblue">new</span> STNodeOption(<span style="color:lime">"Input"</span>, <span style="color:cornflowerblue">typeof</span>(<span style="color:cornflowerblue">string</span>), <span style="color:cornflowerblue">false</span>));
<span style="color:cornflowerblue">this</span>.InputOptions.Add(<span style="color:cornflowerblue">new</span> STNodeOption(<span style="color:lime">"SingleNode"</span>, <span style="color:cornflowerblue">typeof</span>(<span style="color:cyan">Image</span>), <span style="color:cornflowerblue">true</span>));
<span style="color:cornflowerblue">this</span>.InputOptions.Add(<span style="color:cornflowerblue">new</span> STNodeOption(<span style="color:lime">"SingleNode"</span>, <span style="color:cornflowerblue">typeof</span>(<span style="color:cyan">Icon</span>), <span style="color:cornflowerblue">true</span>));
<span style="color:cornflowerblue">this</span>.OutputOptions.Add(<span style="color:cornflowerblue">new</span> STNodeOption(<span style="color:lime">"output"</span>, <span style="color:cornflowerblue">typeof</span>(<span style="color:cornflowerblue">string</span>), <span style="color:cornflowerblue">false</span>));
<span style="color:cornflowerblue">this</span>.OutputOptions.Add(<span style="color:cornflowerblue">new</span> STNodeOption(<span style="color:lime">"Single"</span>, <span style="color:cornflowerblue">typeof</span>(<span style="color:cornflowerblue">string</span>), <span style="color:cornflowerblue">true</span>));
<span style="color:cornflowerblue">this</span>.Title = <span style="color:lime">"Demo_Node"</span>;
}</pre></div>
<p>上面代码片段为 "Demo_Node" 的<span class="span_property">OnCreate</span>重载 加入了三个输入节点和两个输出节点 <span style="color:orangered">(当然上面的代码并没有绑定<span class="span_property">STNodeOption</span>的事件)</span> 若需要自定义绘制参考Demo代码</p>
<h2 class="anchor_point" anchor="p">STNodeOption</h2>
<p><span class="span_property">STNodeOption</span><span class="span_property">STNode</span>下选项的连接点</p>
<p style="color:orangered"><span class="span_property">TransferData(object data)</span>是关键函数 当被调用时数据将自动传递到与它相连的所有选项中 并触发目标选项的<span class="span_property">DataTransfer</span>事件
<br/>此函数是节点选项之间数据传递的核心</p>
<hr/>
<span class="span_title anchor_point" anchor="q">属性</span>
<table>
<tr style="text-align:center;background-color:#505050;">
<td>属性名称</td><td>类型</td><td>描述</td>
</tr>
<tr><td>Owner</td><td>STNode</td><td>获取当前 Option 所属的 Node</td></tr>
<tr><td>IsSingle</td><td>bool</td><td>获取当前 Option 是否仅能被连接一次</td></tr>
<tr><td>IsInput</td><td>bool</td><td>获取当前 Option 是否是输入选项</td></tr>
<tr><td>TextColor</td><td>Color</td><td>获取或设置当前 Option 文本的颜色</td></tr>
<tr><td>DotColor</td><td>Color</td><td>获取或设置当前 Option 连接点的颜色</td></tr>
<tr><td>Text</td><td>string</td><td>获取或设置当前 Option 显示文本</td></tr>
<tr><td>DotLeft</td><td>int</td><td>获取当前 Option 连接点的左边坐标</td></tr>
<tr><td>DotTop</td><td>int</td><td>获取当前 Option 连接点的上边坐标</td></tr>
<tr><td>DotSize</td><td>int</td><td>获取当前 Option 连接点的宽度</td></tr>
<tr><td>Data</td><td>object</td><td>获取或者设置当前 Option 所包含的数据</td></tr>
<tr><td>DataType</td><td>Type</td><td>获取当前 Option 数据类型</td></tr>
<tr><td>DotRectangle</td><td>Rectangle</td><td>获取当前 Option 连接点的区域</td></tr>
<tr><td>ConnectionCount</td><td>int</td><td>获取当前 Option 被连接的个数</td></tr>
</table>
<span class="span_title anchor_point" anchor="r">受保护字段</span>
<table>
<tr style="text-align:center;background-color:#505050;">
<td>属性名称</td><td>类型</td><td>描述</td>
</tr>
<tr><td>m_hs_connected</td><td>HashSet&lt;STNodeOption&gt;</td><td>保存已经被连接的点</td></tr>
</table>
<p><span style="color:orangered">上述中不带 "设置" 二字的都为只读属性 即便是继承至<span class="span_property">STNodeOption</span></span>
<br/>因为作者认为<span class="span_property">STNodeOption</span>最大的用途应该是作为节点之间连线的载体和事件的触发 而对于其他外观需求并不是很重要</p>
<ul>
<li>
<span class="span_property" style="background-color:#0090c9;">DotColor</span>
<ul>
<li>若设置此值 则绘制时候以此值为准 否则查询<span class="span_property">STNodeEditor.TypeColor</span>字段进行颜色匹配</li>
</ul>
</li>
<li>
<span class="span_property" style="background-color:#0090c9;">DataType</span>
<ul>
<li>此选项允许被连接或者输出的数据类型 若类型无法被兼容 无法完成连线
<br/><span style="color:orangered">对于输入类型的节点 每个与其参与连接的输出节点的数据类型必须与输入节点的数据类型相同 或者为输入节点数据类型的子类</span>
<br/>在使用过程中作者并不建议子类和父类混合使用 对于输入或者输出的数据类型应当统一为子类或者父类 若混合使用将在一定程度上产生歧义 比如父类颜色被设定成红色子类颜色被设定成黄色 那么在UI上会给人一种无法被连接的错觉 应当统一数据类型所对应的颜色
<br/><span style="color:orangered">当数据类型为<span class="span_property">object</span>时 则表示可兼容所有数据类型且以 空心 状态绘制</span>
<br/><img style="width:116px;" src="./images/object_type.png"/>
<br/>上图中的 "HUB" 节点为内置节点 其节点类型为<span class="span_property">STNodeHub</span>默认状态下其输入输出点都为<span class="span_property">object</span>类型 表示可兼容所有类型 且重载了<span class="span_property">ConnectOption(STNodeOption op)</span><span class="span_property">CanConnect(STNodeOption op)</span>函数 一旦被接入或者被接入时则会变更为和对应的数据类型进行连接 若连接成功另则会再增加一行类型为<span class="span_property">object</span>的新行
<br/><span style="color:orangered;">但是决定输入类型的<span class="span_property">DataType</span>属性被<span class="span_property">internal</span>修饰 所以若你即使继承至<span class="span_property">STNodeOption</span>也无法改写 只能通过构造器传递 因为处于连线状态突然变更类型会带来一系列的问题 即便编辑器发现变更时候可以选择自动断开所有不相符的连接 但是作者并不建议或者打算这么做 若有此需求请更改源代码</span></li>
</ul>
</li>
</ul>
<hr/>
<span class="span_title anchor_point" anchor="s">构造器</span>
<hr/>
<table>
<tr style="background-color:#505050"><td colspan="2">构造一个 Option</td></tr>
<tr style="background-color:#505050"><td colspan="2">STNodeOption(string strText, Type dataType, bool bSingle)</td></tr>
<tr>
<td>strText</td><td>显示文本</td>
</tr>
<tr>
<td>dataType</td><td>数据类型</td>
</tr>
<tr>
<td>bSingle</td><td>是否为单连接 <span style="color:orangered">若为true则以圆形绘制 否则方形</span></td>
</tr>
</table>
<hr/>
<span class="span_title anchor_point" anchor="t">可重载函数</span>
<p><span class="span_property">OnConnected</span><span class="span_property">OnConnecting</span><span class="span_property">OnDisConnected</span><span class="span_property">OnDisConnecting</span><span class="span_property">OnDataTransfer</span></p>
<hr/>
<span class="span_title anchor_point" anchor="u">公开函数</span>
<hr/>
<table>
<tr style="background-color:#505050"><td colspan="2">当前 Option 连接目标 Option</td></tr>
<tr style="background-color:#505050"><td colspan="2">virtual ConnectionStatus ConnectOption(STNodeOption op)</td></tr>
<tr>
<td>op</td><td>需要连接的 Option</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">检测当前 Option 是否可以连接目标 Option</td></tr>
<tr style="background-color:#505050"><td colspan="2">virtual ConnectionStatus CanConnect(STNodeOption op)</td></tr>
<tr>
<td>op</td><td>需要连接的 Option</td>
</tr>
<tr><td colspan="2" style="background-color:#343434"><span style="color:orangered">当前Option具备连接目标Option的条件 不代表目标Option也具备连当前Option的条件</span></td></tr>
<tr style="background-color:#505050"><td colspan="2">当前 Option 断开目标 Option</td></tr>
<tr style="background-color:#505050"><td colspan="2">virtual bool DisConnection(STNodeOption op)</td></tr>
<tr>
<td>op</td><td>需要断开的 Option</td>
</tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">断开当前 Option 的所有连接</td></tr>
<tr style="background-color:#505050"><td colspan="2">void DisConnectionAll()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2">获取当前 Option 所连接的 Option 集合</td></tr>
<tr style="background-color:#505050"><td colspan="2">List&lt;STNodeOption&gt; GetConnectedOption()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2"><span style="color:orangered">向当前 Option 所连接的所有 Option 投递数据</span></td></tr>
<tr style="background-color:#505050"><td colspan="2">void TransferData()</td></tr>
<tr><td colspan="2" style="background-color:#343434">&nbsp;</td></tr>
<tr style="background-color:#505050"><td colspan="2"><span style="color:orangered">向当前 Option 所连接的所有 Option 投递数据</span></td></tr>
<tr style="background-color:#505050"><td colspan="2">void TransferData(object data)</td></tr>
<tr>
<td>data</td><td>需要投递的数据</td>
</tr>
</table>
<hr/><span class="span_title anchor_point" anchor="v">事件</span><hr/>
<table>
<tr><td>Connected</td><td>当被连接时候发生</td><tr/>
<tr><td>Connecting</td><td>当连接正在被连接时发生</td><tr/>
<tr><td>Disconnected</td><td>当被断开时候发生</td><tr/>
<tr><td>DisConnecting</td><td>当连接正在被断开时发生</td><tr/>
<tr><td>DataTransfer</td><td>当有数据传递时候发生</td><tr/>
</table>
<p><span class="span_property">Connected</span><span class="span_property">DisConnected</span>触发的同时<span class="span_property">DataTransfer</span>也会触发
<br/>所以通常情况下<span class="span_property">DataTransfer</span>事件足够使用 通过对事件参数<span class="span_property">Status</span>可得到 当前事件触发时候目标<span class="span_property">Option</span>与当前<span class="span_property">Option</span>的连线关系</p>
<p><span style="color:orangered">当一个连线完成或者断开时候 输入和输出<span class="span_property">STNodeOption</span>都将触发事件 因为无论连接还是断开都应该是相对的需要两个节点共同参与 而通常情况下只需要对输入节点进行事件绑定即可</span></p>
<h2 class="anchor_point" anchor="w">STNodeOptionEventArgs</h2>
<p><span class="span_property">STNodeOptionEventArgs</span>为包含了<span class="span_property">STNodeOption</span>相关事件的事件参数</p>
<hr/>
<span class="span_title anchor_point" anchor="x">属性</span>
<table>
<tr style="text-align:center;background-color:#505050;">
<td>属性名称</td><td>类型</td><td>描述</td>
</tr>
<tr><td>TargetOption</td><td>STNodeOption</td><td>触发此事件的对应Option</td></tr>
<tr><td>Status</td><td>ConnectionStatus</td><td>Option之间的连线状态</td></tr>
<tr><td>IsSponsor</td><td>bool</td><td>是否为此次行为的发起者</td></tr>
</table>
<hr/>
<h2 class="anchor_point" anchor="y">STNodeControl</h2>
<p>关于此类不做过多介绍 可将其视为<span class="span_property">System.Windows.Forms.Control</span>虽然提供的属性和事件并没有太多 但是作者认为也应当足够了 若还有其他需求请自行修改源代码</p>
<hr/>
<span class="span_title anchor_point" anchor="z">Demo</span>
<hr/>
<div style="overflow:auto;margin:0px 10px;max-width:100%"><pre><span style="color:cornflowerblue">public class</span> <span style="color:cyan">STNodeButton</span> : <span style="color:cyan">STNodeControl</span> //自定义一个Button控件
{
<span style="color:cornflowerblue">private bool</span> m_isHover;
<span style="color:cornflowerblue">protected</span> <span style="color:cornflowerblue">override void</span> OnMouseEnter(<span style="color:cyan">EventArgs</span> e) {
<span style="color:cornflowerblue">base</span>.OnMouseEnter(e);
m_isHover = <span style="color:cornflowerblue">true</span>;
<span style="color:cornflowerblue">this</span>.Invalidate();
}
<span style="color:cornflowerblue">protected</span> <span style="color:cornflowerblue">override void</span> OnMouseLeave(<span style="color:cyan">EventArgs</span> e) {
<span style="color:cornflowerblue">base</span>.OnMouseLeave(e);
m_isHover = <span style="color:cornflowerblue">false</span>;
<span style="color:cornflowerblue">this</span>.Invalidate();
}
<span style="color:cornflowerblue">protected</span> <span style="color:cornflowerblue">override void</span> OnPaint(<span style="color:cyan">DrawingTools</span> dt) {
<span style="color:lime">//base.OnPaint(dt);</span>
<span style="color:cyan">Graphics</span> g = dt.<span style="color:cyan">Graphics</span>;
<span style="color:cyan">SolidBrush</span> brush = dt.<span style="color:cyan">SolidBrush</span>;
brush.Color = m_isHover ? <span style="color:cyan">Color</span>.DodgerBlue : <span style="color:cornflowerblue">this</span>.BackColor;
g.FillRectangle(brush, 0, 0, <span style="color:cornflowerblue">this</span>.Width, <span style="color:cornflowerblue">this</span>.Height);
g.DrawString(<span style="color:cornflowerblue">this</span>.Text, <span style="color:cornflowerblue">this</span>.Font, <span style="color:cyan">Brushes</span>.White, <span style="color:cornflowerblue">this</span>.ClientRectangle, <span style="color:cornflowerblue">base</span>.m_sf);
}
}</pre></div><div style="overflow:auto;margin:0px 10px;max-width:100%;"><pre><span style="color:cornflowerblue">public</span> <span style="color:cornflowerblue">class</span> <span style="color:cyan">STNodeImageInput</span> : <span style="color:cyan">STNode</span>
{
<span style="color:cornflowerblue">private</span> <span style="color:cyan">STNodeOption</span> m_option_out;
<span style="color:cornflowerblue">protected</span> <span style="color:cornflowerblue">override void</span> OnCreate() {
<span style="color:cornflowerblue">base</span>.OnCreate();
<span style="color:cornflowerblue">this</span>.Title = <span style="color:lime">"ImageInput"</span>;
m_option_out = <span style="color:cornflowerblue">new</span> <span style="color:cyan">STNodeOption</span>(<span style="color:lime">""</span>, <span style="color:cornflowerblue">typeof</span>(<span style="color:cyan">Image</span>), <span style="color:cornflowerblue">false</span>);
<span style="color:cornflowerblue">this</span>.OutputOptions.Add(m_option_out);
<span style="color:cyan">STNodeButton</span> btn = <span style="color:cornflowerblue">new</span> <span style="color:cyan">STNodeButton</span>();
btn.Left = 10; btn.Top = 10;
btn.Text = <span style="color:lime">"OpenImage"</span>;
btn.MouseClick += <span style="color:cornflowerblue">new</span> <span style="color:cyan">MouseEventHandler</span>(btn_MouseClick);
<span style="color:cornflowerblue">this</span>.Controls.Add(btn);
}
<span style="color:lime">//...other code</span>
}</pre>
</div>
<p><img style="width:173px;" src="./images/open_image.png"/>
<br/>上面代码片段为 DEMO 中 "ImageInput" 节点与自定义 Button 控件代码 以上代码演示了如何自定义与使用一个控件 可以看到与<span class="span_property">System.Windows.Forms.Control</span>差异并不是很大 多以这里并不做过多介绍</p>
</div>
</div>
</body>
</html>

View File

@ -1,87 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<title>STNodeEditor - 文档教程</title>
<script type="text/javascript" src="./js/jquery-1.10.2.min.js"></script>
<style type="text/css">
body{margin:0px;}
iframe{border:none;}
#left{
position:absolute;
width:50%;
left:0px;
top:0px;
height:100%;
}
#right{
border-left:solid 1px black;
position:absolute;
width:50%;
left:50%;
top:0px;
height:100%;
}
.a_btn{
position: fixed;
display: block;
width: 30px;
height: 30px;
top: 5px;
background-color: rgba(125,125,125,.8);
z-index: 100;
border-radius: 15px;
background-size: cover;
opacity:0.6;
}
.a_btn:hover{background-color:dodgerblue;}
.active:after{
content: ' ';
display: block;
width: 28px;
height: 28px;
border-radius: 14px;
border: solid 1px dodgerblue;
}
#a_btn_lr{
right:85px;
background-image:url('./images/layout_lr.png');
}
#a_btn_l{
right:50px;
background-image:url('./images/layout_l.png');
}
#a_btn_r{
right:15px;
background-image:url('./images/layout_l.png');
}
</style>
<script type="text/javascript">
$(document).ready(function(){
var e_l = $("#left"), e_r = $("#right");
$("#a_btn_l").click(function(){
e_l.css({opacity:1,"z-index":10,width:"100%"});
e_r.css({opacity:0,"z-index":0});
});
$("#a_btn_r").click(function(){
e_l.css({opacity:0,"z-index":0});
e_r.css({opacity:1,"z-index":10,left:0,width:"100%"});
});
$("#a_btn_lr").click(function(){
e_l.css({opacity:1,width:"50%"});
e_r.css({opacity:1,left:"50%",width:"50%"});
});
$(".a_btn").click(function(){
$('.a_btn').removeClass('active');
$(this).addClass('active');
});
});
</script>
</head>
<body>
<a id="a_btn_lr" class="a_btn active"></a><a id="a_btn_l" class="a_btn"></a><a id="a_btn_r" class="a_btn"></a>
<iframe id="left" src="./tutorials_cn.html"></iframe>
<iframe id="right" src="./api.html"></iframe>
</body>
</html>

View File

@ -1,87 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<title>STNodeEditor - Document and tutorials</title>
<script type="text/javascript" src="./js/jquery-1.10.2.min.js"></script>
<style type="text/css">
body{margin:0px;}
iframe{border:none;}
#left{
position:absolute;
width:50%;
left:0px;
top:0px;
height:100%;
}
#right{
border-left:solid 1px black;
position:absolute;
width:50%;
left:50%;
top:0px;
height:100%;
}
.a_btn{
position: fixed;
display: block;
width: 30px;
height: 30px;
top: 5px;
background-color: rgba(125,125,125,.8);
z-index: 100;
border-radius: 15px;
background-size: cover;
opacity:0.6;
}
.a_btn:hover{background-color:dodgerblue;}
.active:after{
content: ' ';
display: block;
width: 28px;
height: 28px;
border-radius: 14px;
border: solid 1px dodgerblue;
}
#a_btn_lr{
right:85px;
background-image:url('./images/layout_lr.png');
}
#a_btn_l{
right:50px;
background-image:url('./images/layout_l.png');
}
#a_btn_r{
right:15px;
background-image:url('./images/layout_l.png');
}
</style>
<script type="text/javascript">
$(document).ready(function(){
var e_l = $("#left"), e_r = $("#right");
$("#a_btn_l").click(function(){
e_l.css({opacity:1,"z-index":10,width:"100%"});
e_r.css({opacity:0,"z-index":0});
});
$("#a_btn_r").click(function(){
e_l.css({opacity:0,"z-index":0});
e_r.css({opacity:1,"z-index":10,left:0,width:"100%"});
});
$("#a_btn_lr").click(function(){
e_l.css({opacity:1,width:"50%"});
e_r.css({opacity:1,left:"50%",width:"50%"});
});
$(".a_btn").click(function(){
$('.a_btn').removeClass('active');
$(this).addClass('active');
});
});
</script>
</head>
<body>
<a id="a_btn_lr" class="a_btn active"></a><a id="a_btn_l" class="a_btn"></a><a id="a_btn_r" class="a_btn"></a>
<iframe id="left" src="./tutorials_en.html"></iframe>
<iframe id="right" src="./api.html"></iframe>
</body>
</html>

BIN
docs/grid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Some files were not shown because too many files have changed in this diff Show More