diff --git a/ST.Library.UI/NodeEditor/FrmNodePreviewPanel.cs b/ST.Library.UI/NodeEditor/FrmNodePreviewPanel.cs new file mode 100644 index 0000000..09b61f2 --- /dev/null +++ b/ST.Library.UI/NodeEditor/FrmNodePreviewPanel.cs @@ -0,0 +1,144 @@ +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); + } + } +} diff --git a/ST.Library.UI/NodeEditor/FrmSTNodePropertyInput.cs b/ST.Library.UI/NodeEditor/FrmSTNodePropertyInput.cs new file mode 100644 index 0000000..d5574e4 --- /dev/null +++ b/ST.Library.UI/NodeEditor/FrmSTNodePropertyInput.cs @@ -0,0 +1,114 @@ +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); + } + } +} diff --git a/ST.Library.UI/NodeEditor/FrmSTNodePropertySelect.cs b/ST.Library.UI/NodeEditor/FrmSTNodePropertySelect.cs new file mode 100644 index 0000000..3adf24e --- /dev/null +++ b/ST.Library.UI/NodeEditor/FrmSTNodePropertySelect.cs @@ -0,0 +1,116 @@ +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 m_lst_item = new List(); + + 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); + } + } + } +} diff --git a/ST.Library.UI/STNodeEditor/STNode.cs b/ST.Library.UI/NodeEditor/STNode.cs old mode 100755 new mode 100644 similarity index 66% rename from ST.Library.UI/STNodeEditor/STNode.cs rename to ST.Library.UI/NodeEditor/STNode.cs index c9598a7..07e422a --- a/ST.Library.UI/STNodeEditor/STNode.cs +++ b/ST.Library.UI/NodeEditor/STNode.cs @@ -1,953 +1,1117 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using System.Drawing; -using System.Windows.Forms; -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. - */ -/* - * time: 2021-01-06 - * Author: Crystal_lz - * blog: st233.com - * Github: DebugST.github.io - */ -namespace ST.Library.UI -{ - public abstract class STNode - { - private STNodeEditor _Owner; - /// - /// 获取当前 Node 所有者 - /// - public STNodeEditor Owner { - get { return _Owner; } - internal set { - if (value == _Owner) return; - if (_Owner != null) { - foreach (STNodeOption op in this._InputOptions.ToArray()) op.DisConnectionAll(); - foreach (STNodeOption op in this._OutputOptions.ToArray()) op.DisConnectionAll(); - } - _Owner = value; - this.BuildSize(true, true, false); - this.OnOwnerChanged(); - } - } - - private bool _IsSelected; - /// - /// 获取或设置 Node 是否处于被选中状态 - /// - public bool IsSelected { - get { return _IsSelected; } - set { - if (value == _IsSelected) return; - _IsSelected = value; - this.Invalidate(); - this.OnSelectedChanged(); - } - } - - private bool _IsActive; - /// - /// 获取 Node 是否处于活动状态 - /// - public bool IsActive { - get { return _IsActive; } - internal set { - if (value == _IsActive) return; - _IsActive = value; - this.OnActiveChanged(); - } - } - - private Color _TitleColor; - /// - /// 获取或设置标题背景颜色 - /// - public Color TitleColor { - get { return _TitleColor; } - protected set { - _TitleColor = value; - this.Invalidate(this.TitleRectangle); - } - } - - private Color _MarkColor; - /// - /// 获取或设置标记信息背景颜色 - /// - public Color MarkColor { - get { return _MarkColor; } - protected set { - _MarkColor = value; - this.Invalidate(this._MarkRectangle); - } - } - - private Color _ForeColor = Color.White; - /// - /// 获取或设置当前 Node 前景色 - /// - public Color ForeColor { - get { return _ForeColor; } - protected set { - _ForeColor = value; - this.Invalidate(); - } - } - - private Color _BackColor; - /// - /// 获取或设置当前 Node 背景色 - /// - public Color BackColor { - get { return _BackColor; } - protected set { - _BackColor = value; - this.Invalidate(); - } - } - - private string _Title; - /// - /// 获取或设置 Node 标题 - /// - public string Title { - get { return _Title; } - protected set { - _Title = value; - this.Invalidate(this.TitleRectangle); - } - } - - private string _Mark; - /// - /// 获取或设置 Node 标记信息 - /// - public string Mark { - get { return _Mark; } - set { - _Mark = value; - if (value == null) - _MarkLines = null; - else - _MarkLines = (from s in value.Split('\n') select s.Trim()).ToArray(); - this.BuildSize(false, true, true); - //if (this._Owner != null) this._Owner.Invalidate(); - //this.Invalidate(); - //this.Invalidate(this._MarkRectangle); - } - } - - private string[] _MarkLines;//单独存放行数据 不用每次在绘制中去拆分 - /// - /// 获取 Node 标记信息行数据 - /// - public string[] MarkLines { - get { return _MarkLines; } - } - - private int _Left; - /// - /// 获取或设置 Node 左边坐标 - /// - public int Left { - get { return _Left; } - set { - if (this._LockLocation) return; - _Left = value; - this.BuildSize(false, true, false); - //this._MarkRectangle = this.OnBuildMarkRectangle(); - if (this._Owner != null) { - this._Owner.BuildLinePath(); - this._Owner.BuildBounds(); - } - this.OnMove(new EventArgs()); - } - } - - private int _Top; - /// - /// 获取或设置 Node 上边坐标 - /// - public int Top { - get { return _Top; } - set { - if (this._LockLocation) return; - _Top = value; - this.BuildSize(false, true, false); - //this._MarkRectangle = this.OnBuildMarkRectangle(); - if (this._Owner != null) { - this._Owner.BuildLinePath(); - this._Owner.BuildBounds(); - } - this.OnMove(new EventArgs()); - } - } - - private int _Width; - /// - /// 获取或设置 Node 宽度 - /// - public int Width { - get { return _Width; } - } - - private int _Height; - /// - /// 获取或设置 Node 高度 - /// - public int Height { - get { return _Height; } - } - /// - /// 获取 Node 右边边坐标 - /// - public int Right { - get { return _Left + _Width; } - } - /// - /// 获取 Node 下边坐标 - /// - public int Bottom { - get { return _Top + _Height; } - } - /// - /// 获取 Node 矩形区域 - /// - public Rectangle Rectangle { - get { - return new Rectangle(this._Left, this._Top, this._Width, this._Height); - } - } - /// - /// 获取 Node 标题矩形区域 - /// - public Rectangle TitleRectangle { - get { - return new Rectangle(this._Left, this._Top, this._Width, this._TitleHeight); - } - } - - private Rectangle _MarkRectangle; - /// - /// 获取 Node 标记矩形区域 - /// - public Rectangle MarkRectangle { - get { return _MarkRectangle; } - } - - private int _TitleHeight = 20; - /// - /// 获取或设置 Node 标题高度 - /// - public int TitleHeight { - get { return _TitleHeight; } - protected set { _TitleHeight = value; } - } - - private STNodeOptionCollection _InputOptions; - /// - /// 获取输入选项集合 - /// - protected internal STNodeOptionCollection InputOptions { - get { return _InputOptions; } - } - /// - /// 获取输入选项集合个数 - /// - public int InputOptionsCount { get { return _InputOptions.Count; } } - - private STNodeOptionCollection _OutputOptions; - /// - /// 获取输出选项 - /// - protected internal STNodeOptionCollection OutputOptions { - get { return _OutputOptions; } - } - /// - /// 获取输出选项个数 - /// - public int OutputOptionsCount { get { return _OutputOptions.Count; } } - - private STNodeControlCollection _Controls; - /// - /// 获取 Node 所包含的控件集合 - /// - protected STNodeControlCollection Controls { - get { return _Controls; } - } - /// - /// 获取 Node 所包含的控件集合个数 - /// - public int ControlsCount { get { return _Controls.Count; } } - /// - /// 获取 Node 坐标位置 - /// - public Point Location { get { return new Point(this._Left, this._Top); } } - /// - /// 获取 Node 大小 - /// - public Size Size { get { return new Size(this._Width, this._Height); } } - - private Font _Font; - /// - /// 获取或设置 Node 字体 - /// - protected Font Font { - get { return _Font; } - set { - if (value == _Font) return; - this._Font.Dispose(); - _Font = value; - } - } - - private bool _LockOption; - /// - /// 获取或设置是否锁定Option选项 锁定后不在接受连接 - /// - public bool LockOption { - get { return _LockOption; } - set { - _LockOption = value; - this.Invalidate(new Rectangle(0, 0, this._Width, this._TitleHeight)); - } - } - - private bool _LockLocation; - /// - /// 获取或设置是否锁定Node位置 锁定后不可移动 - /// - public bool LockLocation { - get { return _LockLocation; } - set { - _LockLocation = value; - this.Invalidate(new Rectangle(0, 0, this._Width, this._TitleHeight)); - } - } - - private ContextMenuStrip _ContextMenuStrip; - /// - /// 获取或设置当前Node 上下文菜单 - /// - public ContextMenuStrip ContextMenuStrip { - get { return _ContextMenuStrip; } - set { _ContextMenuStrip = value; } - } - - private object _Tag; - /// - /// 获取或设置用户自定义保存的数据 - /// - public object Tag { - get { return _Tag; } - set { _Tag = value; } - } - - private Guid _Guid; - /// - /// 获取全局唯一标识 - /// - public Guid Guid { - get { return _Guid; } - } - - private static Point m_static_pt_init = new Point(10, 10); - - public STNode(/*string strTitle, int x, int y*/) { - //this._Title = strTitle; - this._Title = "Untitled"; - this._Height = this._TitleHeight; - this._MarkRectangle.Height = this._Height; - this._Left = this._MarkRectangle.X = m_static_pt_init.X; - this._Top = m_static_pt_init.Y; - this._MarkRectangle.Y = this._Top - 30; - this._InputOptions = new STNodeOptionCollection(this, true); - this._OutputOptions = new STNodeOptionCollection(this, false); - this._Controls = new STNodeControlCollection(this); - this._BackColor = Color.FromArgb(200, 64, 64, 64); - this._TitleColor = Color.FromArgb(200, Color.DodgerBlue); - this._MarkColor = Color.FromArgb(200, Color.Brown); - this._Font = new Font("courier new", 8.25f); - - m_sf = new StringFormat(); - m_sf.Alignment = StringAlignment.Near; - m_sf.LineAlignment = StringAlignment.Center; - m_sf.FormatFlags = StringFormatFlags.NoWrap; - m_sf.SetTabStops(0, new float[] { 40 }); - m_static_pt_init.X += 10; - m_static_pt_init.Y += 10; - this._Guid = Guid.NewGuid(); - this.OnCreate(); - } - - private int m_nItemHeight = 20; - protected StringFormat m_sf; - /// - /// 当前Node中 活动的控件 - /// - protected STNodeControl m_ctrl_active; - /// - /// 当前Node中 悬停的控件 - /// - protected STNodeControl m_ctrl_hover; - - protected internal void BuildSize(bool bBuildNode, bool bBuildMark, bool bRedraw) { - if (this._Owner == null) return; - Pen p = new Pen(this._BackColor); - SolidBrush sb = new SolidBrush(this._BackColor); - using (Graphics g = this._Owner.CreateGraphics()) { - DrawingTools dt = new DrawingTools() { - Graphics = g, - Pen = p, - SolidBrush = sb - }; - if (bBuildNode) { - Size sz = this.OnBuildNodeSize(dt); - this._Width = sz.Width; - this._Height = sz.Height; - this.SetOptionLocation(); - this.OnResize(new EventArgs()); - } - if (bBuildMark) { - if (string.IsNullOrEmpty(this._Mark)) return; - this._MarkRectangle = this.OnBuildMarkRectangle(dt); - } - } - if (bRedraw) this._Owner.Invalidate(); - } - - internal Dictionary OnSaveNode() { - Dictionary dic = new Dictionary(); - dic.Add("Guid", this._Guid.ToByteArray()); - dic.Add("Left", BitConverter.GetBytes(this._Left)); - dic.Add("Top", BitConverter.GetBytes(this._Top)); - dic.Add("Mark", string.IsNullOrEmpty(this._Mark) ? new byte[] { 0 } : Encoding.UTF8.GetBytes(this._Mark)); - dic.Add("LockOption", new byte[] { (byte)(this._LockLocation ? 1 : 0) }); - dic.Add("LockLocation", new byte[] { (byte)(this._LockLocation ? 1 : 0) }); - this.OnSaveNode(dic); - return dic; - } - - internal virtual byte[] GetSaveData() { - List lst = new List(); - Type t = this.GetType(); - byte[] byData = Encoding.UTF8.GetBytes(t.Module.Name); - lst.Add((byte)byData.Length); - lst.AddRange(byData); - byData = Encoding.UTF8.GetBytes(t.GUID.ToString()); - lst.Add((byte)byData.Length); - lst.AddRange(byData); - - var dic = this.OnSaveNode(); - if (dic != null) { - foreach (var v in dic) { - byData = Encoding.UTF8.GetBytes(v.Key); - lst.AddRange(BitConverter.GetBytes(byData.Length)); - lst.AddRange(byData); - lst.AddRange(BitConverter.GetBytes(v.Value.Length)); - lst.AddRange(v.Value); - } - } - return lst.ToArray(); - } - - //internal virtual byte[] GetSaveData() { - // List lst = new List(); - // Type t = this.GetType(); - // //lst.AddRange(BitConverter.GetBytes(this._Left)); - // //lst.AddRange(BitConverter.GetBytes(this._Top)); - // byte[] byData = Encoding.UTF8.GetBytes(t.Module.Name); - // lst.Add((byte)byData.Length); - // lst.AddRange(byData); - // byData = Encoding.UTF8.GetBytes(t.FullName); - // lst.Add((byte)byData.Length); - // lst.AddRange(byData); - - // //if (!string.IsNullOrEmpty(this._Mark)) { - // // byData = Encoding.UTF8.GetBytes(this._Mark); - // // lst.AddRange(BitConverter.GetBytes(byData.Length)); - // // lst.AddRange(byData); - // //} else lst.AddRange(new byte[] { 0, 0, 0, 0 }); - // var dic = this.OnSaveNode(); - // if (dic != null) { - // foreach (var v in dic) { - // byData = Encoding.UTF8.GetBytes(v.Key); - // lst.AddRange(BitConverter.GetBytes(byData.Length)); - // lst.AddRange(byData); - // lst.AddRange(BitConverter.GetBytes(v.Value.Length)); - // lst.AddRange(v.Value); - // } - // } - // return lst.ToArray(); - //} - - #region protected - /// - /// 当Node被构造时候发生 - /// - protected virtual void OnCreate() { } - /// - /// 绘制整个Node - /// - /// 绘制工具 - protected internal virtual void OnDrawNode(DrawingTools dt) { - dt.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; - //Fill background - if (this._BackColor.A != 0) { - dt.SolidBrush.Color = this._BackColor; - dt.Graphics.FillRectangle(dt.SolidBrush, this._Left, this._Top + this._TitleHeight, this._Width, this.Height - this._TitleHeight); - } - this.OnDrawTitle(dt); - this.OnDrawBody(dt); - } - /// - /// 绘制Node标题部分 - /// - /// 绘制工具 - protected virtual void OnDrawTitle(DrawingTools dt) { - m_sf.Alignment = StringAlignment.Center; - m_sf.LineAlignment = StringAlignment.Center; - Graphics g = dt.Graphics; - SolidBrush brush = dt.SolidBrush; - if (this._TitleColor.A != 0) { - brush.Color = this._TitleColor; - g.FillRectangle(brush, this.TitleRectangle); - } - if (this._LockOption) { - dt.Pen.Color = this.ForeColor; - int n = this._Top + this._TitleHeight / 2 - 5; - g.DrawRectangle(dt.Pen, this._Left + 3, n + 0, 6, 3); - g.DrawRectangle(dt.Pen, this._Left + 2, n + 3, 8, 6); - g.DrawLine(dt.Pen, this._Left + 6, n + 5, this._Left + 6, n + 7); - - } - if (this._LockLocation) { - dt.Pen.Color = this.ForeColor; - brush.Color = this._ForeColor; - int n = this._Top + this._TitleHeight / 2 - 5; - g.FillRectangle(brush, this.Right - 9, n, 5, 7); - g.DrawLine(dt.Pen, this.Right - 10, n, this.Right - 4, n); - g.DrawLine(dt.Pen, this.Right - 11, n + 6, this.Right - 3, n + 6); - g.DrawLine(dt.Pen, this.Right - 7, n + 7, this.Right - 7, n + 9); - } - if (!string.IsNullOrEmpty(this._Title) && this._ForeColor.A != 0) { - brush.Color = this._ForeColor; - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - g.DrawString(this._Title, this._Font, brush, this.TitleRectangle, m_sf); - } - } - /// - /// 绘制Node主体部分 除去标题部分 - /// - /// 绘制工具 - protected virtual void OnDrawBody(DrawingTools dt) { - SolidBrush brush = dt.SolidBrush; - foreach (STNodeOption op in this._InputOptions) { - this.OnDrawOptionDot(dt, op); - this.OnDrawOptionText(dt, op); - } - foreach (STNodeOption op in this._OutputOptions) { - this.OnDrawOptionDot(dt, op); - this.OnDrawOptionText(dt, op); - } - if (this._Controls.Count != 0) { //绘制子控件 - //将坐标原点与节点对齐 - dt.Graphics.TranslateTransform(this._Left, this._Top + this._TitleHeight); - Point pt = Point.Empty; //当前需要偏移的量 - Point pt_last = Point.Empty; //最后一个控件相对于节点的坐标 - foreach (STNodeControl v in this._Controls) { - pt.X = v.Left - pt.X; - pt.Y = v.Top - pt.Y; - pt_last = v.Location; - dt.Graphics.TranslateTransform(pt.X, pt.Y); //将原点坐标移动至控件位置 - v.OnPaint(dt); - } - //dt.Graphics.TranslateTransform(-pt_last.X, -pt_last.Y); 还原坐标 - dt.Graphics.TranslateTransform(-this._Left - pt_last.X, -this._Top - this._TitleHeight - pt_last.Y); - } - } - /// - /// 绘制标记信息 - /// - /// 绘制工具 - protected internal virtual void OnDrawMark(DrawingTools dt) { - if (string.IsNullOrEmpty(this._Mark)) return; - Graphics g = dt.Graphics; - SolidBrush brush = dt.SolidBrush; - m_sf.LineAlignment = StringAlignment.Center; - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; - brush.Color = this._MarkColor; - g.FillRectangle(brush, this._MarkRectangle); //填充背景色 - - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; //确定文本绘制所需大小 - var sz = g.MeasureString(this.Mark, this.Font, this._MarkRectangle.Width); - brush.Color = this._ForeColor; - if (sz.Height > m_nItemHeight || sz.Width > this._MarkRectangle.Width) { //如果超过绘图区 则绘制部分 - Rectangle rect = new Rectangle(this._MarkRectangle.Left + 2, this._MarkRectangle.Top + 2, this._MarkRectangle.Width - 20, 16); - m_sf.Alignment = StringAlignment.Near; - g.DrawString(this._MarkLines[0], this._Font, brush, rect, m_sf); - m_sf.Alignment = StringAlignment.Far; - rect.Width = this._MarkRectangle.Width - 5; - g.DrawString("+", this._Font, brush, rect, m_sf); // + 表示超过绘图区 - } else { - m_sf.Alignment = StringAlignment.Near; - g.DrawString(this._MarkLines[0].Trim(), this._Font, brush, this._MarkRectangle, m_sf); - } - } - /// - /// 绘制选项连线的点 - /// - /// 绘制工具 - /// 指定的选项 - protected virtual void OnDrawOptionDot(DrawingTools dt, STNodeOption op) { - Graphics g = dt.Graphics; - Pen pen = dt.Pen; - SolidBrush brush = dt.SolidBrush; - var t = typeof(object); - if (op.DotColor != Color.Transparent) //设置颜色 - brush.Color = op.DotColor; - else { - if (op.DataType == t) - pen.Color = this.Owner.UnknownTypeColor; - else - brush.Color = this.Owner.TypeColor.ContainsKey(op.DataType) ? this.Owner.TypeColor[op.DataType] : this.Owner.UnknownTypeColor; - } - if (op.IsSingle) { //单连接 圆形 - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - if (op.DataType == t) { //未知类型绘制 否则填充 - g.DrawEllipse(pen, op.DotRectangle.X, op.DotRectangle.Y, op.DotRectangle.Width - 1, op.DotRectangle.Height - 1); - } else - g.FillEllipse(brush, op.DotRectangle); - } else { //多连接 矩形 - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; - if (op.DataType == t) { - g.DrawRectangle(pen, op.DotRectangle.X, op.DotRectangle.Y, op.DotRectangle.Width - 1, op.DotRectangle.Height - 1); - } else - g.FillRectangle(brush, op.DotRectangle); - } - } - /// - /// 绘制选项的文本 - /// - /// 绘制工具 - /// 指定的选项 - protected virtual void OnDrawOptionText(DrawingTools dt, STNodeOption op) { - Graphics g = dt.Graphics; - SolidBrush brush = dt.SolidBrush; - if (op.IsInput) { - m_sf.Alignment = StringAlignment.Near; - } else { - m_sf.Alignment = StringAlignment.Far; - } - brush.Color = op.TextColor; - g.DrawString(op.Text, this.Font, brush, op.TextRectangle, m_sf); - } - /// - /// 当计算Option连线点位置时候发生 - /// - /// 需要计算的Option - /// 自动计算出的位置 - /// 新的位置 - protected virtual Point OnSetOptionDotLocation(STNodeOption op, Point pt) { - return pt; - } - /// - /// 当计算Option文本区域时候发生 - /// - /// 需要计算的Option - /// 自动计算出的区域 - /// 新的区域 - protected virtual Rectangle OnSetOptionTextRectangle(STNodeOption op, Rectangle rect) { - return rect; - } - /// - /// 计算当前Node所需要的矩形区域 - /// 若需要自己重绘Node 则应当重写此函数 以确定绘图区域大小 - /// 返回的大小并不会限制绘制区域 任然可以在此区域之外绘制 - /// 但是并不会被STNodeEditor所接受 并触发对应事件 - /// - protected virtual Size OnBuildNodeSize(DrawingTools dt) { - int nInputHeight = 0, nOutputHeight = 0; - foreach (STNodeOption op in this._InputOptions) nInputHeight += m_nItemHeight; - foreach (STNodeOption op in this._OutputOptions) nOutputHeight += m_nItemHeight; - int nHeight = this._TitleHeight + (nInputHeight > nOutputHeight ? nInputHeight : nOutputHeight); - - SizeF szf_input = SizeF.Empty, szf_output = SizeF.Empty; - foreach (STNodeOption v in this._InputOptions) { - if (string.IsNullOrEmpty(v.Text)) continue; - SizeF szf = dt.Graphics.MeasureString(v.Text, this._Font); - if (szf.Width > szf_input.Width) szf_input = szf; - } - foreach (STNodeOption v in this._OutputOptions) { - if (string.IsNullOrEmpty(v.Text)) continue; - SizeF szf = dt.Graphics.MeasureString(v.Text, this._Font); - if (szf.Width > szf_output.Width) szf_output = szf; - } - int nWidth = (int)(szf_input.Width + szf_output.Width + 25); - if (!string.IsNullOrEmpty(this.Title)) szf_input = dt.Graphics.MeasureString(this.Title, this.Font); - if (szf_input.Width + 30 > nWidth) nWidth = (int)szf_input.Width + 30; - return new Size(nWidth, nHeight); - } - /// - /// 计算当前Mark所需要的矩形区域 - /// 若需要自己重绘Mark 则应当重写此函数 以确定绘图区域大小 - /// 返回的大小并不会限制绘制区域 任然可以在此区域之外绘制 - /// 但是并不会被STNodeEditor所接受 并触发对应事件 - /// - protected virtual Rectangle OnBuildMarkRectangle(DrawingTools dt) { - //if (string.IsNullOrEmpty(this._Mark)) return Rectangle.Empty; - return new Rectangle(this._Left, this._Top - 30, this._Width, 20); - } - /// - /// 当需要保存时候 此Node有哪些需要额外保存的数据 - /// 注意: 保存时并不会进行序列化 还原时候仅重新通过空参数构造器创建此Node - /// 然后调用 OnLoadNode() 将保存的数据进行还原 - /// - /// 需要保存的数据 - protected virtual void OnSaveNode(Dictionary dic) { } - /// - /// 当还原该节点时候会将 OnSaveNode() 所返回的数据重新传入此函数 - /// - /// 保存时候的数据 - protected internal virtual void OnLoadNode(Dictionary dic) { - if (dic.ContainsKey("Guid")) this._Guid = new Guid(dic["Guid"]); - if (dic.ContainsKey("Left")) this._Left = BitConverter.ToInt32(dic["Left"], 0); - if (dic.ContainsKey("Top")) this._Top = BitConverter.ToInt32(dic["Top"], 0); - if (dic.ContainsKey("Mark")) { - string strText = Encoding.UTF8.GetString(dic["Mark"]); - if (strText != "\0") this.Mark = strText; - } - if (dic.ContainsKey("LockOption")) this._LockOption = dic["LockOption"][0] == 1; - if (dic.ContainsKey("LockLocation")) this._LockLocation = dic["LockLocation"][0] == 1; - } - /// - /// 当编辑器加载完成所有的节点时候发生 - /// - protected internal virtual void OnEditorLoadCompleted() { - - } - - //[event]===========================[event]==============================[event]============================[event] - - protected internal virtual void OnGotFocus(EventArgs e) { } - - protected internal virtual void OnLostFocus(EventArgs e) { } - - protected internal virtual void OnMouseEnter(EventArgs e) { } - - protected internal virtual void OnMouseDown(MouseEventArgs e) { - Point pt = e.Location; - pt.Y -= this._TitleHeight; - for (int i = this._Controls.Count - 1; i >= 0; i--) { - var c = this._Controls[i]; - if (c.DisplayRectangle.Contains(pt)) { - c.OnMouseDown(new MouseEventArgs(e.Button, e.Clicks, e.X - c.Left, pt.Y - c.Top, e.Delta)); - if (m_ctrl_active != c) { - c.OnGotFocus(new EventArgs()); - if (m_ctrl_active != null) m_ctrl_active.OnLostFocus(new EventArgs()); - m_ctrl_active = c; - } - return; - } - } - if (m_ctrl_active != null) m_ctrl_active.OnLostFocus(new EventArgs()); - m_ctrl_active = null; - } - - protected internal virtual void OnMouseMove(MouseEventArgs e) { - Point pt = e.Location; - pt.Y -= this._TitleHeight; - for (int i = this._Controls.Count - 1; i >= 0; i--) { - var c = this._Controls[i]; - if (c.DisplayRectangle.Contains(pt)) { - if (m_ctrl_hover != this._Controls[i]) { - c.OnMouseEnter(new EventArgs()); - if (m_ctrl_hover != null) m_ctrl_hover.OnMouseLeave(new EventArgs()); - m_ctrl_hover = c; - } - m_ctrl_hover.OnMouseMove(new MouseEventArgs(e.Button, e.Clicks, e.X - c.Left, pt.Y - c.Top, e.Delta)); - return; - } - } - if (m_ctrl_hover != null) m_ctrl_hover.OnMouseLeave(new EventArgs()); - m_ctrl_hover = null; - } - - protected internal virtual void OnMouseUp(MouseEventArgs e) { - Point pt = e.Location; - pt.Y -= this._TitleHeight; - if (m_ctrl_active != null) { - m_ctrl_active.OnMouseUp(new MouseEventArgs(e.Button, e.Clicks, - e.X - m_ctrl_active.Left, pt.Y - m_ctrl_active.Top, e.Delta)); - } - //for (int i = this._Controls.Count - 1; i >= 0; i--) { - // var c = this._Controls[i]; - // if (c.DisplayRectangle.Contains(pt)) { - // c.OnMouseUp(new MouseEventArgs(e.Button, e.Clicks, e.X - c.Left, pt.Y - c.Top, e.Delta)); - // return; - // } - //} - } - - protected internal virtual void OnMouseLeave(EventArgs e) { - if (m_ctrl_hover != null) m_ctrl_hover.OnMouseLeave(e); - m_ctrl_hover = null; - } - - protected internal virtual void OnMouseClick(MouseEventArgs e) { - Point pt = e.Location; - pt.Y -= this._TitleHeight; - if (m_ctrl_active != null) - m_ctrl_active.OnMouseClick(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_active.Left, pt.Y - m_ctrl_active.Top, e.Delta)); - } - - protected internal virtual void OnMouseWheel(MouseEventArgs e) { - Point pt = e.Location; - pt.Y -= this._TitleHeight; - if (m_ctrl_hover != null) { - m_ctrl_hover.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_hover.Left, pt.Y - m_ctrl_hover.Top, e.Delta)); - return; - } - } - protected internal virtual void OnMouseHWheel(MouseEventArgs e) { - if (m_ctrl_hover != null) { - m_ctrl_hover.OnMouseHWheel(e); - return; - } - } - - protected internal virtual void OnKeyDown(KeyEventArgs e) { - if (m_ctrl_active != null) m_ctrl_active.OnKeyDown(e); - } - protected internal virtual void OnKeyUp(KeyEventArgs e) { - if (m_ctrl_active != null) m_ctrl_active.OnKeyUp(e); - } - protected internal virtual void OnKeyPress(KeyPressEventArgs e) { - if (m_ctrl_active != null) m_ctrl_active.OnKeyPress(e); - } - - protected internal virtual void OnMove(EventArgs e) { this.SetOptionLocation(); } - protected internal virtual void OnResize(EventArgs e) { this.SetOptionLocation(); } - - - /// - /// 当所有者发生改变时候发生 - /// - protected virtual void OnOwnerChanged() { } - /// - /// 当选中状态改变时候发生 - /// - protected virtual void OnSelectedChanged() { } - /// - /// 当活动状态改变时候发生 - /// - protected virtual void OnActiveChanged() { } - - #endregion protected - - private void SetOptionLocation() { - Rectangle rect = new Rectangle(this.Left + 10, this._Top + this._TitleHeight, this._Width - 20, m_nItemHeight); - foreach (STNodeOption op in this._InputOptions) { - Point pt = this.OnSetOptionDotLocation(op, new Point(this.Left - 5, rect.Y + 5)); - op.TextRectangle = this.OnSetOptionTextRectangle(op, rect); - op.DotLeft = pt.X; - op.DotTop = pt.Y; - rect.Y += m_nItemHeight; - } - rect.Y = this._Top + this._TitleHeight; - m_sf.Alignment = StringAlignment.Far; - foreach (STNodeOption op in this._OutputOptions) { - Point pt = this.OnSetOptionDotLocation(op, new Point(this._Left + this._Width - 5, rect.Y + 5)); - op.TextRectangle = this.OnSetOptionTextRectangle(op, rect); - op.DotLeft = pt.X; - op.DotTop = pt.Y; - rect.Y += m_nItemHeight; - } - } - - /// - /// 重绘Node - /// - public void Invalidate() { - if (this._Owner != null) { - this._Owner.Invalidate(this._Owner.CanvasToControl(new Rectangle(this._Left - 5, this._Top - 5, this._Width + 10, this._Height + 10))); - } - } - /// - /// 重绘 Node 指定区域 - /// - /// Node 指定区域 - public void Invalidate(Rectangle rect) { - rect.X += this._Left; - rect.Y += this._Top; - if (this._Owner != null) { - this._Owner.Invalidate(this._Owner.CanvasToControl(rect)); - } - } - /// - /// 获取此Node所包含的输入Option集合 - /// - /// Option集合 - public STNodeOption[] GetInputOptions() { - STNodeOption[] ops = new STNodeOption[this._InputOptions.Count]; - for (int i = 0; i < this._InputOptions.Count; i++) ops[i] = this._InputOptions[i]; - return ops; - } - /// - /// 获取此Node所包含的输出Option集合 - /// - /// Option集合 - public STNodeOption[] GetOutputOptions() { - STNodeOption[] ops = new STNodeOption[this._OutputOptions.Count]; - for (int i = 0; i < this._OutputOptions.Count; i++) ops[i] = this._OutputOptions[i]; - return ops; - } - /// - /// 设置Node的选中状态 - /// - /// 是否选中 - /// 是否重绘 - public void SetSelected(bool bSelected, bool bRedraw) { - if (this._IsSelected == bSelected) return; - this._IsSelected = bSelected; - if (bRedraw) this.Invalidate(); - this.OnSelectedChanged(); - } - 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); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using System.Windows.Forms; +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-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 abstract class STNode + { + private STNodeEditor _Owner; + /// + /// 获取当前 Node 所有者 + /// + public STNodeEditor Owner { + get { return _Owner; } + internal set { + if (value == _Owner) return; + if (_Owner != null) { + foreach (STNodeOption op in this._InputOptions.ToArray()) op.DisConnectionAll(); + foreach (STNodeOption op in this._OutputOptions.ToArray()) op.DisConnectionAll(); + } + _Owner = value; + if (!this._AutoSize) this.SetOptionsLocation(); + this.BuildSize(true, true, false); + this.OnOwnerChanged(); + } + } + + private bool _IsSelected; + /// + /// 获取或设置 Node 是否处于被选中状态 + /// + public bool IsSelected { + get { return _IsSelected; } + set { + if (value == _IsSelected) return; + _IsSelected = value; + this.Invalidate(); + this.OnSelectedChanged(); + if (this._Owner != null) this._Owner.OnSelectedChanged(EventArgs.Empty); + } + } + + private bool _IsActive; + /// + /// 获取 Node 是否处于活动状态 + /// + public bool IsActive { + get { return _IsActive; } + internal set { + if (value == _IsActive) return; + _IsActive = value; + this.OnActiveChanged(); + } + } + + private Color _TitleColor; + /// + /// 获取或设置标题背景颜色 + /// + public Color TitleColor { + get { return _TitleColor; } + protected set { + _TitleColor = value; + this.Invalidate(new Rectangle(0, 0, this._Width, this._TitleHeight)); + } + } + + private Color _MarkColor; + /// + /// 获取或设置标记信息背景颜色 + /// + public Color MarkColor { + get { return _MarkColor; } + protected set { + _MarkColor = value; + this.Invalidate(this._MarkRectangle); + } + } + + private Color _ForeColor = Color.White; + /// + /// 获取或设置当前 Node 前景色 + /// + public Color ForeColor { + get { return _ForeColor; } + protected set { + _ForeColor = value; + this.Invalidate(); + } + } + + private Color _BackColor; + /// + /// 获取或设置当前 Node 背景色 + /// + public Color BackColor { + get { return _BackColor; } + protected set { + _BackColor = value; + this.Invalidate(); + } + } + + private string _Title; + /// + /// 获取或设置 Node 标题 + /// + public string Title { + get { return _Title; } + protected set { + _Title = value; + if (this._AutoSize) this.BuildSize(true, true, true); + //this.Invalidate(this.TitleRectangle); + } + } + + private string _Mark; + /// + /// 获取或设置 Node 标记信息 + /// + public string Mark { + get { return _Mark; } + set { + _Mark = value; + if (value == null) + _MarkLines = null; + else + _MarkLines = (from s in value.Split('\n') select s.Trim()).ToArray(); + this.Invalidate(new Rectangle(-5, -5, this._MarkRectangle.Width + 10, this._MarkRectangle.Height + 10)); + } + } + + private string[] _MarkLines;//单独存放行数据 不用每次在绘制中去拆分 + /// + /// 获取 Node 标记信息行数据 + /// + public string[] MarkLines { + get { return _MarkLines; } + } + + private int _Left; + /// + /// 获取或设置 Node 左边坐标 + /// + public int Left { + get { return _Left; } + set { + if (this._LockLocation || value == _Left) return; + _Left = value; + this.SetOptionsLocation(); + this.BuildSize(false, true, false); + this.OnMove(EventArgs.Empty); + if (this._Owner != null) { + this._Owner.BuildLinePath(); + this._Owner.BuildBounds(); + } + } + } + + private int _Top; + /// + /// 获取或设置 Node 上边坐标 + /// + public int Top { + get { return _Top; } + set { + if (this._LockLocation || value == _Top) return; + _Top = value; + this.SetOptionsLocation(); + this.BuildSize(false, true, false); + this.OnMove(EventArgs.Empty); + if (this._Owner != null) { + this._Owner.BuildLinePath(); + this._Owner.BuildBounds(); + } + } + } + + private int _Width = 100; + /// + /// 获取或设置 Node 宽度 当AutoSize被设置时 无法设置此值 + /// + public int Width { + get { return _Width; } + protected set { + if (value < 50) return; + if (this._AutoSize || value == _Width) return; + _Width = value; + this.SetOptionsLocation(); + this.BuildSize(false, true, false); + this.OnResize(EventArgs.Empty); + if (this._Owner != null) { + this._Owner.BuildLinePath(); + this._Owner.BuildBounds(); + } + this.Invalidate(); + } + } + + private int _Height = 40; + /// + /// 获取或设置 Node 高度 当AutoSize被设置时 无法设置此值 + /// + public int Height { + get { return _Height; } + protected set { + if (value < 40) return; + if (this._AutoSize || value == _Height) return; + _Height = value; + this.SetOptionsLocation(); + this.BuildSize(false, true, false); + this.OnResize(EventArgs.Empty); + if (this._Owner != null) { + this._Owner.BuildLinePath(); + this._Owner.BuildBounds(); + } + this.Invalidate(); + } + } + + private int _ItemHeight = 20; + /// + /// 获取或设置 Node 每个选项的高度 + /// + public int ItemHeight { + get { return _ItemHeight; } + protected set { + if (value < 16) value = 16; + if (value > 200) value = 200; + if (value == _ItemHeight) return; + _ItemHeight = value; + if (this._AutoSize) { + this.BuildSize(true, false, true); + } else { + this.SetOptionsLocation(); + if (this._Owner != null) this._Owner.Invalidate(); + } + } + } + + private bool _AutoSize = true; + /// + /// 获取或设置 Node 是否自动计算宽高 + /// + public bool AutoSize { + get { return _AutoSize; } + protected set { _AutoSize = value; } + } + /// + /// 获取 Node 右边边坐标 + /// + public int Right { + get { return _Left + _Width; } + } + /// + /// 获取 Node 下边坐标 + /// + public int Bottom { + get { return _Top + _Height; } + } + /// + /// 获取 Node 矩形区域 + /// + public Rectangle Rectangle { + get { + return new Rectangle(this._Left, this._Top, this._Width, this._Height); + } + } + /// + /// 获取 Node 标题矩形区域 + /// + public Rectangle TitleRectangle { + get { + return new Rectangle(this._Left, this._Top, this._Width, this._TitleHeight); + } + } + + private Rectangle _MarkRectangle; + /// + /// 获取 Node 标记矩形区域 + /// + public Rectangle MarkRectangle { + get { return _MarkRectangle; } + } + + private int _TitleHeight = 20; + /// + /// 获取或设置 Node 标题高度 + /// + public int TitleHeight { + get { return _TitleHeight; } + protected set { _TitleHeight = value; } + } + + private STNodeOptionCollection _InputOptions; + /// + /// 获取输入选项集合 + /// + protected internal STNodeOptionCollection InputOptions { + get { return _InputOptions; } + } + /// + /// 获取输入选项集合个数 + /// + public int InputOptionsCount { get { return _InputOptions.Count; } } + + private STNodeOptionCollection _OutputOptions; + /// + /// 获取输出选项 + /// + protected internal STNodeOptionCollection OutputOptions { + get { return _OutputOptions; } + } + /// + /// 获取输出选项个数 + /// + public int OutputOptionsCount { get { return _OutputOptions.Count; } } + + private STNodeControlCollection _Controls; + /// + /// 获取 Node 所包含的控件集合 + /// + protected STNodeControlCollection Controls { + get { return _Controls; } + } + /// + /// 获取 Node 所包含的控件集合个数 + /// + public int ControlsCount { get { return _Controls.Count; } } + /// + /// 获取 Node 坐标位置 + /// + public Point Location { + get { return new Point(this._Left, this._Top); } + set { + this.Left = value.X; + this.Top = value.Y; + } + } + /// + /// 获取 Node 大小 + /// + public Size Size { + get { return new Size(this._Width, this._Height); } + set { + this.Width = value.Width; + this.Height = value.Height; + } + } + + private Font _Font; + /// + /// 获取或设置 Node 字体 + /// + protected Font Font { + get { return _Font; } + set { + if (value == _Font) return; + this._Font.Dispose(); + _Font = value; + } + } + + private bool _LockOption; + /// + /// 获取或设置是否锁定Option选项 锁定后不在接受连接 + /// + public bool LockOption { + get { return _LockOption; } + set { + _LockOption = value; + this.Invalidate(new Rectangle(0, 0, this._Width, this._TitleHeight)); + } + } + + private bool _LockLocation; + /// + /// 获取或设置是否锁定Node位置 锁定后不可移动 + /// + public bool LockLocation { + get { return _LockLocation; } + set { + _LockLocation = value; + this.Invalidate(new Rectangle(0, 0, this._Width, this._TitleHeight)); + } + } + + private ContextMenuStrip _ContextMenuStrip; + /// + /// 获取或设置当前Node 上下文菜单 + /// + public ContextMenuStrip ContextMenuStrip { + get { return _ContextMenuStrip; } + set { _ContextMenuStrip = value; } + } + + private object _Tag; + /// + /// 获取或设置用户自定义保存的数据 + /// + public object Tag { + get { return _Tag; } + set { _Tag = value; } + } + + private Guid _Guid; + /// + /// 获取全局唯一标识 + /// + public Guid Guid { + get { return _Guid; } + } + + private bool _LetGetOptions = false; + /// + /// 获取或设置是否允许外部访问STNodeOption + /// + public bool LetGetOptions { + get { return _LetGetOptions; } + protected set { _LetGetOptions = value; } + } + + private static Point m_static_pt_init = new Point(10, 10); + + public STNode() { + this._Title = "Untitled"; + this._MarkRectangle.Height = this._Height; + this._Left = this._MarkRectangle.X = m_static_pt_init.X; + this._Top = m_static_pt_init.Y; + this._MarkRectangle.Y = this._Top - 30; + this._InputOptions = new STNodeOptionCollection(this, true); + this._OutputOptions = new STNodeOptionCollection(this, false); + this._Controls = new STNodeControlCollection(this); + this._BackColor = Color.FromArgb(200, 64, 64, 64); + this._TitleColor = Color.FromArgb(200, Color.DodgerBlue); + this._MarkColor = Color.FromArgb(200, Color.Brown); + this._Font = new Font("courier new", 8.25f); + + m_sf = new StringFormat(); + m_sf.Alignment = StringAlignment.Near; + m_sf.LineAlignment = StringAlignment.Center; + m_sf.FormatFlags = StringFormatFlags.NoWrap; + m_sf.SetTabStops(0, new float[] { 40 }); + m_static_pt_init.X += 10; + m_static_pt_init.Y += 10; + this._Guid = Guid.NewGuid(); + this.OnCreate(); + } + + //private int m_nItemHeight = 30; + protected StringFormat m_sf; + /// + /// 当前Node中 活动的控件 + /// + protected STNodeControl m_ctrl_active; + /// + /// 当前Node中 悬停的控件 + /// + protected STNodeControl m_ctrl_hover; + /// + /// 当前Node中 鼠标点下的控件 + /// + protected STNodeControl m_ctrl_down; + + protected internal void BuildSize(bool bBuildNode, bool bBuildMark, bool bRedraw) { + if (this._Owner == null) return; + using (Graphics g = this._Owner.CreateGraphics()) { + if (this._AutoSize && bBuildNode) { + Size sz = this.GetDefaultNodeSize(g); + if (this._Width != sz.Width || this._Height != sz.Height) { + this._Width = sz.Width; + this._Height = sz.Height; + this.SetOptionsLocation(); + this.OnResize(EventArgs.Empty); + } + } + if (bBuildMark && !string.IsNullOrEmpty(this._Mark)) { + this._MarkRectangle = this.OnBuildMarkRectangle(g); + } + } + if (bRedraw) this._Owner.Invalidate(); + } + + internal Dictionary OnSaveNode() { + Dictionary dic = new Dictionary(); + dic.Add("Guid", this._Guid.ToByteArray()); + dic.Add("Left", BitConverter.GetBytes(this._Left)); + dic.Add("Top", BitConverter.GetBytes(this._Top)); + dic.Add("Width", BitConverter.GetBytes(this._Width)); + dic.Add("Height", BitConverter.GetBytes(this._Height)); + dic.Add("AutoSize", new byte[] { (byte)(this._AutoSize ? 1 : 0) }); + if (this._Mark != null) dic.Add("Mark", Encoding.UTF8.GetBytes(this._Mark)); + dic.Add("LockOption", new byte[] { (byte)(this._LockLocation ? 1 : 0) }); + dic.Add("LockLocation", new byte[] { (byte)(this._LockLocation ? 1 : 0) }); + Type t = this.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 InvalidOperationException("[STNodePropertyAttribute.Type]参数值必须为[STNodePropertyDescriptor]或者其子类的类型"); + var desc = (STNodePropertyDescriptor)Activator.CreateInstance(attr.DescriptorType); + desc.Node = this; + desc.PropertyInfo = p; + byte[] byData = desc.GetBytesFromValue(); + if (byData == null) continue; + dic.Add(p.Name, byData); + } + } + this.OnSaveNode(dic); + return dic; + } + + internal byte[] GetSaveData() { + List lst = new List(); + Type t = this.GetType(); + byte[] byData = Encoding.UTF8.GetBytes(t.Module.Name + "|" + t.FullName); + lst.Add((byte)byData.Length); + lst.AddRange(byData); + byData = Encoding.UTF8.GetBytes(t.GUID.ToString()); + lst.Add((byte)byData.Length); + lst.AddRange(byData); + + var dic = this.OnSaveNode(); + if (dic != null) { + foreach (var v in dic) { + byData = Encoding.UTF8.GetBytes(v.Key); + lst.AddRange(BitConverter.GetBytes(byData.Length)); + lst.AddRange(byData); + lst.AddRange(BitConverter.GetBytes(v.Value.Length)); + lst.AddRange(v.Value); + } + } + return lst.ToArray(); + } + + #region protected + /// + /// 当Node被构造时候发生 + /// + protected virtual void OnCreate() { } + /// + /// 绘制整个Node + /// + /// 绘制工具 + protected internal virtual void OnDrawNode(DrawingTools dt) { + dt.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; + //Fill background + if (this._BackColor.A != 0) { + dt.SolidBrush.Color = this._BackColor; + dt.Graphics.FillRectangle(dt.SolidBrush, this._Left, this._Top + this._TitleHeight, this._Width, this.Height - this._TitleHeight); + } + this.OnDrawTitle(dt); + this.OnDrawBody(dt); + } + /// + /// 绘制Node标题部分 + /// + /// 绘制工具 + protected virtual void OnDrawTitle(DrawingTools dt) { + m_sf.Alignment = StringAlignment.Center; + m_sf.LineAlignment = StringAlignment.Center; + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + if (this._TitleColor.A != 0) { + brush.Color = this._TitleColor; + g.FillRectangle(brush, this.TitleRectangle); + } + if (this._LockOption) { + //dt.Pen.Color = this.ForeColor; + brush.Color = this._ForeColor; + int n = this._Top + this._TitleHeight / 2 - 5; + g.FillRectangle(dt.SolidBrush, this._Left + 4, n + 0, 2, 4); + g.FillRectangle(dt.SolidBrush, this._Left + 6, n + 0, 2, 2); + g.FillRectangle(dt.SolidBrush, this._Left + 8, n + 0, 2, 4); + g.FillRectangle(dt.SolidBrush, this._Left + 3, n + 4, 8, 6); + //g.DrawLine(dt.Pen, this._Left + 6, n + 5, this._Left + 6, n + 7); + //g.DrawRectangle(dt.Pen, this._Left + 3, n + 0, 6, 3); + //g.DrawRectangle(dt.Pen, this._Left + 2, n + 3, 8, 6); + //g.DrawLine(dt.Pen, this._Left + 6, n + 5, this._Left + 6, n + 7); + + } + if (this._LockLocation) { + //dt.Pen.Color = this.ForeColor; + brush.Color = this._ForeColor; + int n = this._Top + this._TitleHeight / 2 - 5; + g.FillRectangle(brush, this.Right - 9, n, 4, 4); + g.FillRectangle(brush, this.Right - 11, n + 4, 8, 2); + g.FillRectangle(brush, this.Right - 8, n + 6, 2, 4); + //g.DrawLine(dt.Pen, this.Right - 10, n + 6, this.Right - 4, n + 6); + //g.DrawLine(dt.Pen, this.Right - 10, n, this.Right - 4, n); + //g.DrawLine(dt.Pen, this.Right - 11, n + 6, this.Right - 3, n + 6); + //g.DrawLine(dt.Pen, this.Right - 7, n + 7, this.Right - 7, n + 9); + } + if (!string.IsNullOrEmpty(this._Title) && this._ForeColor.A != 0) { + brush.Color = this._ForeColor; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.DrawString(this._Title, this._Font, brush, this.TitleRectangle, m_sf); + } + } + /// + /// 绘制Node主体部分 除去标题部分 + /// + /// 绘制工具 + protected virtual void OnDrawBody(DrawingTools dt) { + SolidBrush brush = dt.SolidBrush; + foreach (STNodeOption op in this._InputOptions) { + if (op == STNodeOption.Empty) continue; + this.OnDrawOptionDot(dt, op); + this.OnDrawOptionText(dt, op); + } + foreach (STNodeOption op in this._OutputOptions) { + if (op == STNodeOption.Empty) continue; + this.OnDrawOptionDot(dt, op); + this.OnDrawOptionText(dt, op); + } + if (this._Controls.Count != 0) { //绘制子控件 + //将坐标原点与节点对齐 + //dt.Graphics.ResetTransform(); + dt.Graphics.TranslateTransform(this._Left, this._Top + this._TitleHeight); + Point pt = Point.Empty; //当前需要偏移的量 + Point pt_last = Point.Empty; //最后一个控件相对于节点的坐标 + foreach (STNodeControl v in this._Controls) { + if (!v.Visable) continue; + pt.X = v.Left - pt_last.X; + pt.Y = v.Top - pt_last.Y; + pt_last = v.Location; + dt.Graphics.TranslateTransform(pt.X, pt.Y); //将原点坐标移动至控件位置 + dt.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; + v.OnPaint(dt); + } + //dt.Graphics.TranslateTransform(-pt_last.X, -pt_last.Y); 还原坐标 + dt.Graphics.TranslateTransform(-this._Left - pt_last.X, -this._Top - this._TitleHeight - pt_last.Y); + //dt.Graphics. + } + } + /// + /// 绘制标记信息 + /// + /// 绘制工具 + protected internal virtual void OnDrawMark(DrawingTools dt) { + if (string.IsNullOrEmpty(this._Mark)) return; + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + m_sf.LineAlignment = StringAlignment.Center; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; + brush.Color = this._MarkColor; + g.FillRectangle(brush, this._MarkRectangle); //填充背景色 + + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; //确定文本绘制所需大小 + var sz = g.MeasureString(this.Mark, this.Font, this._MarkRectangle.Width); + brush.Color = this._ForeColor; + if (sz.Height > this._ItemHeight || sz.Width > this._MarkRectangle.Width) { //如果超过绘图区 则绘制部分 + Rectangle rect = new Rectangle(this._MarkRectangle.Left + 2, this._MarkRectangle.Top + 2, this._MarkRectangle.Width - 20, 16); + m_sf.Alignment = StringAlignment.Near; + g.DrawString(this._MarkLines[0], this._Font, brush, rect, m_sf); + m_sf.Alignment = StringAlignment.Far; + rect.Width = this._MarkRectangle.Width - 5; + g.DrawString("+", this._Font, brush, rect, m_sf); // + 表示超过绘图区 + } else { + m_sf.Alignment = StringAlignment.Near; + g.DrawString(this._MarkLines[0].Trim(), this._Font, brush, this._MarkRectangle, m_sf); + } + } + /// + /// 绘制选项连线的点 + /// + /// 绘制工具 + /// 指定的选项 + protected virtual void OnDrawOptionDot(DrawingTools dt, STNodeOption op) { + Graphics g = dt.Graphics; + Pen pen = dt.Pen; + SolidBrush brush = dt.SolidBrush; + var t = typeof(object); + if (op.DotColor != Color.Transparent) //设置颜色 + brush.Color = op.DotColor; + else { + if (op.DataType == t) + pen.Color = this.Owner.UnknownTypeColor; + else + brush.Color = this.Owner.TypeColor.ContainsKey(op.DataType) ? this.Owner.TypeColor[op.DataType] : this.Owner.UnknownTypeColor; + } + if (op.IsSingle) { //单连接 圆形 + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + if (op.DataType == t) { //未知类型绘制 否则填充 + g.DrawEllipse(pen, op.DotRectangle.X, op.DotRectangle.Y, op.DotRectangle.Width - 1, op.DotRectangle.Height - 1); + } else + g.FillEllipse(brush, op.DotRectangle); + } else { //多连接 矩形 + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; + if (op.DataType == t) { + g.DrawRectangle(pen, op.DotRectangle.X, op.DotRectangle.Y, op.DotRectangle.Width - 1, op.DotRectangle.Height - 1); + } else + g.FillRectangle(brush, op.DotRectangle); + } + } + /// + /// 绘制选项的文本 + /// + /// 绘制工具 + /// 指定的选项 + protected virtual void OnDrawOptionText(DrawingTools dt, STNodeOption op) { + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + if (op.IsInput) { + m_sf.Alignment = StringAlignment.Near; + } else { + m_sf.Alignment = StringAlignment.Far; + } + brush.Color = op.TextColor; + g.DrawString(op.Text, this.Font, brush, op.TextRectangle, m_sf); + } + /// + /// 当计算Option连线点位置时候发生 + /// + /// 需要计算的Option + /// 自动计算出的位置 + /// 当前Option的索引 + /// 新的位置 + protected virtual Point OnSetOptionDotLocation(STNodeOption op, Point pt, int nIndex) { + return pt; + } + /// + /// 当计算Option文本区域时候发生 + /// + /// 需要计算的Option + /// 自动计算出的区域 + /// 当前Option的索引 + /// 新的区域 + protected virtual Rectangle OnSetOptionTextRectangle(STNodeOption op, Rectangle rect, int nIndex) { + return rect; + } + /// + /// 获取当前STNode所需要的默认大小 + /// 返回的大小并不会限制绘制区域 任然可以在此区域之外绘制 + /// 但是并不会被STNodeEditor所接受 并触发对应事件 + /// + /// 绘图面板 + /// 计算出来的大小 + protected virtual Size GetDefaultNodeSize(Graphics g) { + int nInputHeight = 0, nOutputHeight = 0; + foreach (STNodeOption op in this._InputOptions) nInputHeight += this._ItemHeight; + foreach (STNodeOption op in this._OutputOptions) nOutputHeight += this._ItemHeight; + int nHeight = this._TitleHeight + (nInputHeight > nOutputHeight ? nInputHeight : nOutputHeight); + + SizeF szf_input = SizeF.Empty, szf_output = SizeF.Empty; + foreach (STNodeOption v in this._InputOptions) { + if (string.IsNullOrEmpty(v.Text)) continue; + SizeF szf = g.MeasureString(v.Text, this._Font); + if (szf.Width > szf_input.Width) szf_input = szf; + } + foreach (STNodeOption v in this._OutputOptions) { + if (string.IsNullOrEmpty(v.Text)) continue; + SizeF szf = g.MeasureString(v.Text, this._Font); + if (szf.Width > szf_output.Width) szf_output = szf; + } + int nWidth = (int)(szf_input.Width + szf_output.Width + 25); + if (!string.IsNullOrEmpty(this.Title)) szf_input = g.MeasureString(this.Title, this.Font); + if (szf_input.Width + 30 > nWidth) nWidth = (int)szf_input.Width + 30; + return new Size(nWidth, nHeight); + } + /// + /// 计算当前Mark所需要的矩形区域 + /// 返回的大小并不会限制绘制区域 任然可以在此区域之外绘制 + /// 但是并不会被STNodeEditor所接受 并触发对应事件 + /// + /// 绘图面板 + /// 计算后的区域 + protected virtual Rectangle OnBuildMarkRectangle(Graphics g) { + //if (string.IsNullOrEmpty(this._Mark)) return Rectangle.Empty; + return new Rectangle(this._Left, this._Top - 30, this._Width, 20); + } + /// + /// 当需要保存时候 此Node有哪些需要额外保存的数据 + /// 注意: 保存时并不会进行序列化 还原时候仅重新通过空参数构造器创建此Node + /// 然后调用 OnLoadNode() 将保存的数据进行还原 + /// + /// 需要保存的数据 + protected virtual void OnSaveNode(Dictionary dic) { } + /// + /// 当还原该节点时候会将 OnSaveNode() 所返回的数据重新传入此函数 + /// + /// 保存时候的数据 + protected internal virtual void OnLoadNode(Dictionary dic) { + if (dic.ContainsKey("AutoSize")) this._AutoSize = dic["AutoSize"][0] == 1; + if (dic.ContainsKey("LockOption")) this._LockOption = dic["LockOption"][0] == 1; + if (dic.ContainsKey("LockLocation")) this._LockLocation = dic["LockLocation"][0] == 1; + if (dic.ContainsKey("Guid")) this._Guid = new Guid(dic["Guid"]); + if (dic.ContainsKey("Left")) this._Left = BitConverter.ToInt32(dic["Left"], 0); + if (dic.ContainsKey("Top")) this._Top = BitConverter.ToInt32(dic["Top"], 0); + if (dic.ContainsKey("Width") && !this._AutoSize) this._Width = BitConverter.ToInt32(dic["Width"], 0); + if (dic.ContainsKey("Height") && !this._AutoSize) this._Height = BitConverter.ToInt32(dic["Height"], 0); + if (dic.ContainsKey("Mark")) this.Mark = Encoding.UTF8.GetString(dic["Mark"]); + Type t = this.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 InvalidOperationException("[STNodePropertyAttribute.Type]参数值必须为[STNodePropertyDescriptor]或者其子类的类型"); + var desc = (STNodePropertyDescriptor)Activator.CreateInstance(attr.DescriptorType); + desc.Node = this; + desc.PropertyInfo = p; + try { + if (dic.ContainsKey(p.Name)) desc.SetValue(dic[p.Name]); + } catch (Exception ex) { + string strErr = "属性[" + this.Title + "." + p.Name + "]的值无法被还原 可通过重写[STNodePropertyAttribute.GetBytesFromValue(),STNodePropertyAttribute.GetValueFromBytes(byte[])]确保保存和加载时候的二进制数据正确"; + Exception e = ex; + while (e != null) { + strErr += "\r\n----\r\n[" + e.GetType().Name + "] -> " + e.Message; + e = e.InnerException; + } + throw new InvalidOperationException(strErr, ex); + } + } + } + } + /// + /// 当编辑器加载完成所有的节点时候发生 + /// + protected internal virtual void OnEditorLoadCompleted() { } + /// + /// 设置Option的文本信息 + /// + /// 目标Option + /// 文本 + /// 是否成功 + protected bool SetOptionText(STNodeOption op, string strText) { + if (op.Owner != this) return false; + op.Text = strText; + return true; + } + /// + /// 设置Option文本信息颜色 + /// + /// 目标Option + /// 颜色 + /// 是否成功 + protected bool SetOptionTextColor(STNodeOption op, Color clr) { + if (op.Owner != this) return false; + op.TextColor = clr; + return true; + } + /// + /// 设置Option连线点颜色 + /// + /// 目标Option + /// 颜色 + /// 是否成功 + protected bool SetOptionDotColor(STNodeOption op, Color clr) { + if (op.Owner != this) return false; + op.DotColor = clr; + return false; + } + + //[event]===========================[event]==============================[event]============================[event] + + protected internal virtual void OnGotFocus(EventArgs e) { } + + protected internal virtual void OnLostFocus(EventArgs e) { } + + protected internal virtual void OnMouseEnter(EventArgs e) { } + + protected internal virtual void OnMouseDown(MouseEventArgs e) { + Point pt = e.Location; + pt.Y -= this._TitleHeight; + for (int i = this._Controls.Count - 1; i >= 0; i--) { + var c = this._Controls[i]; + if (c.DisplayRectangle.Contains(pt)) { + if (!c.Enabled) return; + if (!c.Visable) continue; + c.OnMouseDown(new MouseEventArgs(e.Button, e.Clicks, e.X - c.Left, pt.Y - c.Top, e.Delta)); + m_ctrl_down = c; + if (m_ctrl_active != c) { + c.OnGotFocus(EventArgs.Empty); + if (m_ctrl_active != null) m_ctrl_active.OnLostFocus(EventArgs.Empty); + m_ctrl_active = c; + } + return; + } + } + if (m_ctrl_active != null) m_ctrl_active.OnLostFocus(EventArgs.Empty); + m_ctrl_active = null; + } + + protected internal virtual void OnMouseMove(MouseEventArgs e) { + Point pt = e.Location; + pt.Y -= this._TitleHeight; + if (m_ctrl_down != null) { + if (m_ctrl_down.Enabled && m_ctrl_down.Visable) + m_ctrl_down.OnMouseMove(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_down.Left, pt.Y - m_ctrl_down.Top, e.Delta)); + return; + } + for (int i = this._Controls.Count - 1; i >= 0; i--) { + var c = this._Controls[i]; + if (c.DisplayRectangle.Contains(pt)) { + if (m_ctrl_hover != this._Controls[i]) { + c.OnMouseEnter(EventArgs.Empty); + if (m_ctrl_hover != null) m_ctrl_hover.OnMouseLeave(EventArgs.Empty); + m_ctrl_hover = c; + } + m_ctrl_hover.OnMouseMove(new MouseEventArgs(e.Button, e.Clicks, e.X - c.Left, pt.Y - c.Top, e.Delta)); + return; + } + } + if (m_ctrl_hover != null) m_ctrl_hover.OnMouseLeave(EventArgs.Empty); + m_ctrl_hover = null; + } + + protected internal virtual void OnMouseUp(MouseEventArgs e) { + Point pt = e.Location; + pt.Y -= this._TitleHeight; + if (m_ctrl_down != null && m_ctrl_down.Enabled && m_ctrl_down.Visable) { + m_ctrl_down.OnMouseUp(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_down.Left, pt.Y - m_ctrl_down.Top, e.Delta)); + } + //if (m_ctrl_active != null) { + // m_ctrl_active.OnMouseUp(new MouseEventArgs(e.Button, e.Clicks, + // e.X - m_ctrl_active.Left, pt.Y - m_ctrl_active.Top, e.Delta)); + //} + m_ctrl_down = null; + } + + protected internal virtual void OnMouseLeave(EventArgs e) { + if (m_ctrl_hover != null && m_ctrl_hover.Enabled && m_ctrl_hover.Visable) m_ctrl_hover.OnMouseLeave(e); + m_ctrl_hover = null; + } + + protected internal virtual void OnMouseClick(MouseEventArgs e) { + Point pt = e.Location; + pt.Y -= this._TitleHeight; + if (m_ctrl_active != null && m_ctrl_active.Enabled && m_ctrl_active.Visable) + m_ctrl_active.OnMouseClick(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_active.Left, pt.Y - m_ctrl_active.Top, e.Delta)); + } + + protected internal virtual void OnMouseWheel(MouseEventArgs e) { + Point pt = e.Location; + pt.Y -= this._TitleHeight; + if (m_ctrl_hover != null && m_ctrl_active.Enabled && m_ctrl_hover.Visable) { + m_ctrl_hover.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_hover.Left, pt.Y - m_ctrl_hover.Top, e.Delta)); + return; + } + } + protected internal virtual void OnMouseHWheel(MouseEventArgs e) { + if (m_ctrl_hover != null && m_ctrl_active.Enabled && m_ctrl_hover.Visable) { + m_ctrl_hover.OnMouseHWheel(e); + return; + } + } + + protected internal virtual void OnKeyDown(KeyEventArgs e) { + if (m_ctrl_active != null && m_ctrl_active.Enabled && m_ctrl_active.Visable) m_ctrl_active.OnKeyDown(e); + } + protected internal virtual void OnKeyUp(KeyEventArgs e) { + if (m_ctrl_active != null && m_ctrl_active.Enabled && m_ctrl_active.Visable) m_ctrl_active.OnKeyUp(e); + } + protected internal virtual void OnKeyPress(KeyPressEventArgs e) { + if (m_ctrl_active != null && m_ctrl_active.Enabled && m_ctrl_active.Visable) m_ctrl_active.OnKeyPress(e); + } + + protected virtual void OnMove(EventArgs e) { /*this.SetOptionLocation();*/ } + protected virtual void OnResize(EventArgs e) { /*this.SetOptionLocation();*/ } + + + /// + /// 当所有者发生改变时候发生 + /// + protected virtual void OnOwnerChanged() { } + /// + /// 当选中状态改变时候发生 + /// + protected virtual void OnSelectedChanged() { } + /// + /// 当活动状态改变时候发生 + /// + protected virtual void OnActiveChanged() { } + + #endregion protected + /// + /// 计算每个Option的位置 + /// + protected virtual void SetOptionsLocation() { + int nIndex = 0; + Rectangle rect = new Rectangle(this.Left + 10, this._Top + this._TitleHeight, this._Width - 20, this._ItemHeight); + foreach (STNodeOption op in this._InputOptions) { + if (op != STNodeOption.Empty) { + Point pt = this.OnSetOptionDotLocation(op, new Point(this.Left - op.DotSize / 2, rect.Y + (rect.Height - op.DotSize) / 2), nIndex); + op.TextRectangle = this.OnSetOptionTextRectangle(op, rect, nIndex); + op.DotLeft = pt.X; + op.DotTop = pt.Y; + } + rect.Y += this._ItemHeight; + nIndex++; + } + rect.Y = this._Top + this._TitleHeight; + m_sf.Alignment = StringAlignment.Far; + foreach (STNodeOption op in this._OutputOptions) { + if (op != STNodeOption.Empty) { + Point pt = this.OnSetOptionDotLocation(op, new Point(this._Left + this._Width - op.DotSize / 2, rect.Y + (rect.Height - op.DotSize) / 2), nIndex); + op.TextRectangle = this.OnSetOptionTextRectangle(op, rect, nIndex); + op.DotLeft = pt.X; + op.DotTop = pt.Y; + } + rect.Y += this._ItemHeight; + nIndex++; + } + } + + /// + /// 重绘Node + /// + public void Invalidate() { + if (this._Owner != null) { + this._Owner.Invalidate(this._Owner.CanvasToControl(new Rectangle(this._Left - 5, this._Top - 5, this._Width + 10, this._Height + 10))); + } + } + /// + /// 重绘 Node 指定区域 + /// + /// Node 指定区域 + public void Invalidate(Rectangle rect) { + rect.X += this._Left; + rect.Y += this._Top; + if (this._Owner != null) { + rect = this._Owner.CanvasToControl(rect); + rect.Width += 1; rect.Height += 1;//坐标系统转换可能导致进度丢失 多加上一个像素 + this._Owner.Invalidate(rect); + } + } + /// + /// 获取此Node所包含的输入Option集合 + /// + /// Option集合 + public STNodeOption[] GetInputOptions() { + if (!this._LetGetOptions) return null; + STNodeOption[] ops = new STNodeOption[this._InputOptions.Count]; + for (int i = 0; i < this._InputOptions.Count; i++) ops[i] = this._InputOptions[i]; + return ops; + } + /// + /// 获取此Node所包含的输出Option集合 + /// + /// Option集合 + public STNodeOption[] GetOutputOptions() { + if (!this._LetGetOptions) return null; + STNodeOption[] ops = new STNodeOption[this._OutputOptions.Count]; + for (int i = 0; i < this._OutputOptions.Count; i++) ops[i] = this._OutputOptions[i]; + return ops; + } + /// + /// 设置Node的选中状态 + /// + /// 是否选中 + /// 是否重绘 + public void SetSelected(bool bSelected, bool bRedraw) { + if (this._IsSelected == bSelected) return; + this._IsSelected = bSelected; + if (this._Owner != null) { + if (bSelected) + this._Owner.AddSelectedNode(this); + else + this._Owner.RemoveSelectedNode(this); + } + if (bRedraw) this.Invalidate(); + this.OnSelectedChanged(); + if (this._Owner != null) this._Owner.OnSelectedChanged(EventArgs.Empty); + } + 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); + } + } +} diff --git a/ST.Library.UI/NodeEditor/STNodeAttribute.cs b/ST.Library.UI/NodeEditor/STNodeAttribute.cs new file mode 100644 index 0000000..2319e78 --- /dev/null +++ b/ST.Library.UI/NodeEditor/STNodeAttribute.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace ST.Library.UI.NodeEditor +{ + /// + /// STNode节点特性 + /// 用于描述STNode开发者信息 以及部分行为 + /// + public class STNodeAttribute : Attribute + { + private string _Path; + /// + /// 获取STNode节点期望在树形控件的路径 + /// + public string Path { + get { return _Path; } + } + + private string _Author; + /// + /// 获取STNode节点的作者名称 + /// + public string Author { + get { return _Author; } + } + + private string _Mail; + /// + /// 获取STNode节点的作者邮箱 + /// + public string Mail { + get { return _Mail; } + } + + private string _Link; + /// + /// 获取STNode节点的作者链接 + /// + public string Link { + get { return _Link; } + } + + private string _Description; + /// + /// 获取STNode节点的描述信息 + /// + public string Description { + get { return _Description; } + } + + private static char[] m_ch_splitter = new char[] { '/', '\\' }; + private static Regex m_reg = new Regex(@"^https?://", RegexOptions.IgnoreCase); + /// + /// 构造一个STNode特性 + /// + /// 期望路径 + public STNodeAttribute(string strPath) : this(strPath, null, null, null, null) { } + /// + /// 构造一个STNode特性 + /// + /// 期望路径 + /// 描述信息 + public STNodeAttribute(string strPath, string strDescription) : this(strPath, null, null, null, strDescription) { } + /// + /// 构造一个STNode特性 + /// + /// 期望路径 + /// STNode作者名称 + /// STNode作者邮箱 + /// STNode作者链接 + /// STNode节点描述信息 + 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 m_dic = new Dictionary(); + /// + /// 获取类型的帮助函数 + /// + /// 节点类型 + /// 函数信息 + 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; + } + /// + /// 执行对应节点类型的帮助函数 + /// + /// 节点类型 + public static void ShowHelp(Type stNodeType) { + var mi = STNodeAttribute.GetHelpMethod(stNodeType); + if (mi == null) return; + mi.Invoke(null, new object[] { stNodeType.Module.FullyQualifiedName }); + } + } +} diff --git a/ST.Library.UI/STNodeEditor/STNodeCollection.cs b/ST.Library.UI/NodeEditor/STNodeCollection.cs old mode 100755 new mode 100644 similarity index 91% rename from ST.Library.UI/STNodeEditor/STNodeCollection.cs rename to ST.Library.UI/NodeEditor/STNodeCollection.cs index 6d23e01..55a73dd --- a/ST.Library.UI/STNodeEditor/STNodeCollection.cs +++ b/ST.Library.UI/NodeEditor/STNodeCollection.cs @@ -1,236 +1,253 @@ -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(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]; - } - /// - /// 确认空间是否足够 空间不足扩大容量 - /// - /// 需要增加的个数 - 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.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(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]; + } + /// + /// 确认空间是否足够 空间不足扩大容量 + /// + /// 需要增加的个数 + 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; + } + } +} diff --git a/ST.Library.UI/STNodeEditor/STNodeControl.cs b/ST.Library.UI/NodeEditor/STNodeControl.cs old mode 100755 new mode 100644 similarity index 82% rename from ST.Library.UI/STNodeEditor/STNodeControl.cs rename to ST.Library.UI/NodeEditor/STNodeControl.cs index 3c0d4a3..4bd9cca --- a/ST.Library.UI/STNodeEditor/STNodeControl.cs +++ b/ST.Library.UI/NodeEditor/STNodeControl.cs @@ -1,263 +1,319 @@ -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); - } - } -} +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 + { + /// + /// 绘制工具 + /// + public DrawingTools DrawingTools { get; private set; } + + public STNodeControlPaintEventArgs(DrawingTools dt) { + this.DrawingTools = dt; + } + } +} diff --git a/ST.Library.UI/STNodeEditor/STNodeControlCollection.cs b/ST.Library.UI/NodeEditor/STNodeControlCollection.cs old mode 100755 new mode 100644 similarity index 96% rename from ST.Library.UI/STNodeEditor/STNodeControlCollection.cs rename to ST.Library.UI/NodeEditor/STNodeControlCollection.cs index b0a9ad1..822e01b --- a/ST.Library.UI/STNodeEditor/STNodeControlCollection.cs +++ b/ST.Library.UI/NodeEditor/STNodeControlCollection.cs @@ -1,214 +1,214 @@ -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(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]; - } - /// - /// 确认空间是否足够 空间不足扩大容量 - /// - /// 需要增加的个数 - 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.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(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]; + } + /// + /// 确认空间是否足够 空间不足扩大容量 + /// + /// 需要增加的个数 + 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(); + } + } +} diff --git a/ST.Library.UI/STNodeEditor/STNodeEditor.cs b/ST.Library.UI/NodeEditor/STNodeEditor.cs old mode 100755 new mode 100644 similarity index 86% rename from ST.Library.UI/STNodeEditor/STNodeEditor.cs rename to ST.Library.UI/NodeEditor/STNodeEditor.cs index aaed817..75bb669 --- a/ST.Library.UI/STNodeEditor/STNodeEditor.cs +++ b/ST.Library.UI/NodeEditor/STNodeEditor.cs @@ -1,1954 +1,2109 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using System.IO; -using System.Windows.Forms; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Threading; -using System.ComponentModel; -using System.Reflection; -using System.IO.Compression; -/* -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 STNodeEditor : Control - { - private const UInt32 WM_MOUSEHWHEEL = 0x020E; - protected static readonly Type m_type_node = typeof(STNode); - - #region protected enum,struct -------------------------------------------------------------------------------------- - - protected enum CanvasAction //当前鼠标移动操作表示进行下列哪一个行为 - { - None, //无 - MoveNode, //正在移动 Node - ConnectOption, //正在连接 Option - SelectRectangle, //正在选择矩形区域 - DrawMarkDetails //正在绘制标记信息详情 - } - - protected struct MagnetInfo - { - public bool XMatched; //X轴是否有磁铁匹配上 - public bool YMatched; - public int X; //与X轴那个数字匹配上 - public int Y; - public int OffsetX; //当前节点X位置与匹配上的X的相对偏移 - public int OffsetY; - } - - #endregion - - #region Properties ------------------------------------------------------------------------------------------------------ - - private float _CanvasOffsetX; - /// - /// 获取画布原点相对于控件 X 方向上的偏移位置 - /// - [Browsable(false)] - public float CanvasOffsetX { - get { return _CanvasOffsetX; } - } - - private float _CanvasOffsetY; - /// - /// 获取画布原点相对于控件 Y 方向上的偏移位置 - /// - [Browsable(false)] - public float CanvasOffsetY { - get { return _CanvasOffsetY; } - } - - private PointF _CanvasOffset; - /// - /// 获取画布原点相对于控件偏移位置 - /// - [Browsable(false)] - public PointF CanvasOffset { - get { - _CanvasOffset.X = _CanvasOffsetX; - _CanvasOffset.Y = _CanvasOffsetY; - return _CanvasOffset; - } - } - - private Rectangle _CanvasValidBounds; - /// - /// 获取画布中的有被用到的有效区域 - /// - [Browsable(false)] - public Rectangle CanvasValidBounds { - get { return _CanvasValidBounds; } - } - - private float _CanvasScale = 1; - /// - /// 获取画布的缩放比例 - /// - [Browsable(false)] - public float CanvasScale { - get { return _CanvasScale; } - } - - private float _Curvature = 0.3F; - /// - /// 获取或设置 Option 之间连线的曲度 - /// - [Browsable(false)] - public float Curvature { - get { return _Curvature; } - set { - if (value < 0) value = 0; - if (value > 1) value = 1; - _Curvature = value; - if (m_dic_gp_info.Count != 0) this.BuildLinePath(); - } - } - - private bool _Magnet = true; - /// - /// 获取或设置移动画布中 Node 时候 是否启用磁铁效果 - /// - [Description("获取或设置移动画布中 Node 时候 是否启用磁铁效果"), DefaultValue(true)] - public bool Magnet { - get { return _Magnet; } - set { _Magnet = value; } - } - - private bool _ShowBorder = true; - /// - /// 获取或设置 移动画布中是否显示 Node 边框 - /// - [Description("获取或设置 移动画布中是否显示 Node 边框"), DefaultValue(true)] - public bool ShowBorder { - get { return _ShowBorder; } - set { - _ShowBorder = value; - this.Invalidate(); - } - } - - private bool _ShowGrid = true; - /// - /// 获取或设置画布中是否绘制背景网格线条 - /// - [Description("获取或设置画布中是否绘制背景网格线条"), DefaultValue(true)] - public bool ShowGrid { - get { return _ShowGrid; } - set { - _ShowGrid = value; - this.Invalidate(); - } - } - - private bool _ShowLocation = true; - /// - /// 获取或设置是否在画布边缘显示超出视角的 Node 位置信息 - /// - [Description("获取或设置是否在画布边缘显示超出视角的 Node 位置信息"), DefaultValue(true)] - public bool ShowLocation { - get { return _ShowLocation; } - set { - _ShowLocation = value; - this.Invalidate(); - } - } - - private STNodeCollection _Nodes; - /// - /// 获取画布中 Node 集合 - /// - [Browsable(false)] - public STNodeCollection Nodes { - get { - return _Nodes; - } - } - - private STNode _ActiveNode; - /// - /// 获取或设置当前画布中被选中的活动 Node - /// - [Browsable(false)] - public STNode ActiveNode { - get { return _ActiveNode; } - //set { - // if (value == _ActiveSelectedNode) return; - // if (_ActiveSelectedNode != null) _ActiveSelectedNode.OnLostFocus(new EventArgs()); - // _ActiveSelectedNode = value; - // _ActiveSelectedNode.IsActive = true; - // this.Invalidate(); - // this.OnSelectedChanged(new EventArgs()); - //} - } - - private STNode _HoverNode; - /// - /// 获取当前画布中鼠标悬停的 Node - /// - [Browsable(false)] - public STNode HoverNode { - get { return _HoverNode; } - } - //========================================color================================ - private Color _GridColor = Color.Black; - /// - /// 获取或设置绘制画布背景时 网格线条颜色 - /// - [Description("获取或设置绘制画布背景时 网格线条颜色"), DefaultValue(typeof(Color), "Black")] - public Color GridColor { - get { return _GridColor; } - set { - _GridColor = value; - this.Invalidate(); - } - } - - private Color _BorderColor = Color.Black; - /// - /// 获取或设置画布中 Node 边框颜色 - /// - [Description("获取或设置画布中 Node 边框颜色"), DefaultValue(typeof(Color), "Black")] - public Color BorderColor { - get { return _BorderColor; } - set { - _BorderColor = value; - if (m_img_border != null) m_img_border.Dispose(); - m_img_border = this.CreateBorderImage(value); - this.Invalidate(); - } - } - - private Color _BorderHoverColor = Color.Gray; - /// - /// 获取或设置画布中悬停 Node 边框颜色 - /// - [Description("获取或设置画布中悬停 Node 边框颜色"), DefaultValue(typeof(Color), "Gray")] - public Color BorderHoverColor { - get { return _BorderHoverColor; } - set { - _BorderHoverColor = value; - if (m_img_border_hover != null) m_img_border_hover.Dispose(); - m_img_border_hover = this.CreateBorderImage(value); - this.Invalidate(); - } - } - - private Color _BorderSelectColor = Color.Orange; - /// - /// 获取或设置画布中选中 Node 边框颜色 - /// - [Description("获取或设置画布中选中 Node 边框颜色"), DefaultValue(typeof(Color), "Orange")] - public Color BorderSelectColor { - get { return _BorderSelectColor; } - set { - _BorderSelectColor = value; - if (m_img_border_selected != null) m_img_border_selected.Dispose(); - m_img_border_selected = this.CreateBorderImage(value); - this.Invalidate(); - } - } - - private Color _BorderActiveColor = Color.OrangeRed; - /// - /// 获取或设置画布中活动 Node 边框颜色 - /// - [Description("获取或设置画布中活动 Node 边框颜色"), DefaultValue(typeof(Color), "OrangeRed")] - public Color BorderActiveColor { - get { return _BorderActiveColor; } - set { - _BorderActiveColor = value; - if (m_img_border_active != null) m_img_border_active.Dispose(); - m_img_border_active = this.CreateBorderImage(value); - this.Invalidate(); - } - } - - private Color _MarkForeColor = Color.White; - /// - /// 获取或设置画布绘制 Node 标记详情采用的前景色 - /// - [Description("获取或设置画布绘制 Node 标记详情采用的前景色"), DefaultValue(typeof(Color), "White")] - public Color MarkForeColor { - get { return _MarkBackColor; } - set { - _MarkBackColor = value; - this.Invalidate(); - } - } - - private Color _MarkBackColor = Color.FromArgb(180, Color.Black); - /// - /// 获取或设置画布绘制 Node 标记详情采用的背景色 - /// - [Description("获取或设置画布绘制 Node 标记详情采用的背景色"), DefaultValue(typeof(Color), "Black")] - public Color MarkBackColor { - get { return _MarkBackColor; } - set { - _MarkBackColor = value; - this.Invalidate(); - } - } - - private Color _MagnetLineColor = Color.Magenta; - /// - /// 获取或设置画布中移动 Node 时候 磁铁标记线颜色 - /// - [Description("获取或设置画布中移动 Node 时候 磁铁标记线颜色"), DefaultValue(typeof(Color), "Magenta")] - public Color MagnetLineColor { - get { return _MagnetLineColor; } - set { _MagnetLineColor = value; } - } - - private Color _SelectedRectangleColor = Color.DodgerBlue; - /// - /// 获取或设置画布中选择矩形区域的颜色 - /// - [Description("获取或设置画布中选择矩形区域的颜色"), DefaultValue(typeof(Color), "DodgerBlue")] - public Color SelectedRectangleColor { - get { return _SelectedRectangleColor; } - set { _SelectedRectangleColor = value; } - } - - private Color _HighLineColor = Color.Cyan; - /// - /// 获取或设置画布中高亮连线的颜色 - /// - [Description("获取或设置画布中高亮连线的颜色"), DefaultValue(typeof(Color), "Cyan")] - public Color HighLineColor { - get { return _HighLineColor; } - set { _HighLineColor = value; } - } - - private Color _LocationForeColor = Color.Red; - /// - /// 获取或设置画布中边缘位置提示区域前景色 - /// - [Description("获取或设置画布中边缘位置提示区域前景色"), DefaultValue(typeof(Color), "Red")] - public Color LocationForeColor { - get { return _LocationForeColor; } - set { - _LocationForeColor = value; - this.Invalidate(); - } - } - - private Color _LocationBackColor = Color.FromArgb(120, Color.Black); - /// - /// 获取或设置画布中边缘位置提示区域背景色 - /// - [Description("获取或设置画布中边缘位置提示区域背景色")] - public Color LocationBackColor { - get { return _LocationBackColor; } - set { - _LocationBackColor = value; - this.Invalidate(); - } - } - - private Color _UnknownTypeColor = Color.Gray; - /// - /// 获取或设置画布中当 Node 中 Option 数据类型无法确定时应当使用的颜色 - /// - [Description("获取或设置画布中当 Node 中 Option 数据类型无法确定时应当使用的颜色"), DefaultValue(typeof(Color), "Gray")] - public Color UnknownTypeColor { - get { return _UnknownTypeColor; } - set { - _UnknownTypeColor = value; - this.Invalidate(); - } - } - - private Dictionary _TypeColor = new Dictionary(); - /// - /// 获取或设置画布中 Node 中 Option 数据类型预设颜色 - /// - [Browsable(false)] - public Dictionary TypeColor { - get { return _TypeColor; } - } - - #endregion - - #region protected properties ---------------------------------------------------------------------------------------- - /// - /// 当前鼠标在控件中的实时位置 - /// - protected Point m_pt_in_control; - /// - /// 当前鼠标在画布中的实时位置 - /// - protected PointF m_pt_in_canvas; - /// - /// 鼠标点击时在控件上的位置 - /// - protected Point m_pt_down_in_control; - /// - /// 鼠标点击时在画布中的位置 - /// - protected PointF m_pt_down_in_canvas; - /// - /// 用于鼠标点击移动画布时候 鼠标点下时候的画布坐标位置 - /// - protected PointF m_pt_canvas_old; - /// - /// 用于保存连线过程中保存点下 Option 的起点坐标 - /// - protected Point m_pt_dot_down; - /// - /// 用于保存连线过程中鼠标点下的起点Option 当MouseUP时候 确定是否连接此节点 - /// - protected STNodeOption m_option_down; - - #endregion - - public STNodeEditor() { - 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._Nodes = new STNodeCollection(this); - this.BackColor = Color.FromArgb(255, 34, 34, 34); - this.Size = new Size(200, 200); - } - - #region private fields -------------------------------------------------------------------------------------- - - private DrawingTools m_drawing_tools; - private NodeFindInfo m_find = new NodeFindInfo(); - private MagnetInfo m_mi = new MagnetInfo(); - - private RectangleF m_rect_select = new RectangleF(); - //节点边框预设图案 - private Image m_img_border; - private Image m_img_border_hover; - private Image m_img_border_selected; - private Image m_img_border_active; - //用于鼠标滚动或者触摸板移动画布时候的动画效果 该值为需要移动到的真实坐标地址 查看->MoveCanvasThread() - private float m_real_canvas_x; - private float m_real_canvas_y; - //用于移动节点时候 保存鼠标点下时候选中的节点初始坐标 - private Dictionary m_dic_pt_selected = new Dictionary(); - //用于磁铁效果 移动节点时候 非选择节点的统计出来的需要参与磁铁效果的坐标 查看->BuildMagnetLocation() - private List m_lst_magnet_x = new List(); - private List m_lst_magnet_y = new List(); - //用于磁铁效果 移动节点时候 活动选择节点统计出来需要参与磁铁效果的坐标 查看->CheckMagnet() - private List m_lst_magnet_mx = new List(); - private List m_lst_magnet_my = new List(); - //用于鼠标滚动中计算时间触发间隔 根据间隔不同 画布产生的位移不同 查看->OnMouseWheel(),OnMouseHWheel() - private DateTime m_dt_vw = DateTime.Now; - private DateTime m_dt_hw = DateTime.Now; - //移动鼠标过程中的当前行为 - private CanvasAction m_ca; - //保存已选中的节点 - private HashSet m_hs_node_selected = new HashSet(); - - private bool m_is_process_mouse_event; //是否向下(Node or NodeControls)传递鼠标相关事件 如断开连接相关操作不应向下传递 - private bool m_is_buildpath; //用于重绘过程中 判断该次是否要重新建立缓存连线的路径 - private Pen m_p_line = new Pen(Color.Cyan, 2f); //用于绘制已经连接的线条 - private Pen m_p_line_hover = new Pen(Color.Cyan, 4f); //用于绘制鼠标悬停时候的线条 - private GraphicsPath m_gp_hover; //当前鼠标悬停的连线路径 - private StringFormat m_sf = new StringFormat(); //文本格式 用于Mark绘制时候 设置文本格式 - //保存每个连接线条与之对应的节点关系 - private Dictionary m_dic_gp_info = new Dictionary(); - //保存超出视觉区域的 Node 的位置 - private List m_lst_node_out = new List(); - //当前编辑器已加载的 Node 类型 用于从文件或者数据中加载节点使用 - private Dictionary m_dic_type = new Dictionary(); - - private int m_time_alert; - private int m_alpha_alert; - private string m_str_alert; - private Color m_forecolor_alert; - private Color m_backcolor_alert; - private DateTime m_dt_alert; - private Rectangle m_rect_alert; - private AlertLocation m_al; - - #endregion - - #region event ---------------------------------------------------------------------------------------------------- - /// - /// 选择的节点发生变化时候发生 - /// - [Description("选择的节点发生变化时候发生")] - public event EventHandler SelectedChanged; - /// - /// 悬停的节点发生变化时候发生 - /// - [Description("悬停的节点发生变化时候发生")] - public event EventHandler HoverChanged; - /// - /// 当节点被添加时候发生 - /// - [Description("当节点被添加时候发生")] - public event STNodeEditorEventHandler NodeAdded; - /// - /// 当节点被移除时候发生 - /// - [Description("当节点被移除时候发生")] - public event STNodeEditorEventHandler NodeRemoved; - /// - /// 移动画布原点时候发生 - /// - [Description("移动画布原点时候发生")] - public event EventHandler CanvasMoved; - /// - /// 缩放画布时候发生 - /// - [Description("缩放画布时候发生")] - public event EventHandler CanvasScaled; - /// - /// 连接节点选项时候发生 - /// - [Description("连接节点选项时候发生")] - public event STNodeEditorOptionEventHandler OptionConnected; - /// - /// 正在连接节点选项时候发生 - /// - [Description("正在连接节点选项时候发生")] - public event STNodeEditorOptionEventHandler OptionConnecting; - /// - /// 断开节点选项时候发生 - /// - [Description("断开节点选项时候发生")] - public event STNodeEditorOptionEventHandler OptionDisConnected; - /// - /// 正在断开节点选项时候发生 - /// - [Description("正在断开节点选项时候发生")] - public event STNodeEditorOptionEventHandler OptionDisConnecting; - - protected virtual void OnSelectedChanged(EventArgs e) { - if (this.SelectedChanged != null) this.SelectedChanged(this, e); - } - protected virtual void OnHoverChanged(EventArgs e) { - if (this.HoverChanged != null) this.HoverChanged(this, e); - } - protected internal virtual void OnNodeAdded(STNodeEditorEventArgs e) { - if (this.NodeAdded != null) this.NodeAdded(this, e); - } - protected internal virtual void OnNodeRemoved(STNodeEditorEventArgs e) { - if (this.NodeRemoved != null) this.NodeRemoved(this, e); - } - protected virtual void OnCanvasMoved(EventArgs e) { - if (this.CanvasMoved != null) this.CanvasMoved(this, e); - } - protected virtual void OnCanvasScaled(EventArgs e) { - if (this.CanvasScaled != null) this.CanvasScaled(this, e); - } - protected internal virtual void OnOptionConnected(STNodeEditorOptionEventArgs e) { - if (this.OptionConnected != null) this.OptionConnected(this, e); - } - protected internal virtual void OnOptionDisConnected(STNodeEditorOptionEventArgs e) { - if (this.OptionDisConnected != null) this.OptionDisConnected(this, e); - } - protected internal virtual void OnOptionConnecting(STNodeEditorOptionEventArgs e) { - if (this.OptionConnecting != null) this.OptionConnecting(this, e); - } - protected internal virtual void OnOptionDisConnecting(STNodeEditorOptionEventArgs e) { - if (this.OptionDisConnecting != null) this.OptionDisConnecting(this, e); - } - - #endregion event - - #region override ----------------------------------------------------------------------------------------------------- - - protected override void OnCreateControl() { - m_drawing_tools = new DrawingTools() { - Pen = new Pen(Color.Black, 1), - SolidBrush = new SolidBrush(Color.Black) - }; - m_img_border = this.CreateBorderImage(this._BorderColor); - m_img_border_active = this.CreateBorderImage(this._BorderActiveColor); - m_img_border_hover = this.CreateBorderImage(this._BorderHoverColor); - m_img_border_selected = this.CreateBorderImage(this._BorderSelectColor); - base.OnCreateControl(); - new Thread(this.MoveCanvasThread) { IsBackground = true }.Start(); - new Thread(this.ShowAlertThread) { IsBackground = true }.Start(); - m_sf = new StringFormat(); - m_sf.Alignment = StringAlignment.Near; - m_sf.FormatFlags = StringFormatFlags.NoWrap; - m_sf.SetTabStops(0, new float[] { 40 }); - } - - protected override void WndProc(ref Message m) { - base.WndProc(ref m); - Point pt = new Point(((int)m.LParam) >> 16, (ushort)m.LParam); - pt = base.PointToClient(pt); - if (m.Msg == WM_MOUSEHWHEEL) { //获取水平滚动消息 - MouseButtons mb = MouseButtons.None; - int n = (ushort)m.WParam; - if ((n & 0x0001) == 0x0001) mb |= MouseButtons.Left; - if ((n & 0x0010) == 0x0010) mb |= MouseButtons.Middle; - if ((n & 0x0002) == 0x0002) mb |= MouseButtons.Right; - if ((n & 0x0020) == 0x0020) mb |= MouseButtons.XButton1; - if ((n & 0x0040) == 0x0040) mb |= MouseButtons.XButton2; - this.OnMouseHWheel(new MouseEventArgs(mb, 0, pt.X, pt.Y, ((int)m.WParam) >> 16)); - } - } - - protected override void OnPaint(PaintEventArgs e) { - base.OnPaint(e); - Graphics g = e.Graphics; - g.Clear(this.BackColor); - g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit; - m_drawing_tools.Graphics = g; - SolidBrush brush = m_drawing_tools.SolidBrush; - - if (this._ShowGrid) this.OnDrawGrid(m_drawing_tools, this.Width, this.Height); - - g.TranslateTransform(this._CanvasOffsetX, this._CanvasOffsetY); //移动坐标系 - g.ScaleTransform(this._CanvasScale, this._CanvasScale); //缩放绘图表面 - - this.OnDrawConnectedLine(m_drawing_tools); - this.OnDrawNode(m_drawing_tools, this.ControlToCanvas(this.ClientRectangle)); - - if (m_ca == CanvasAction.ConnectOption) { //如果正在连线 - m_drawing_tools.Pen.Color = this._HighLineColor; - g.SmoothingMode = SmoothingMode.HighQuality; - if (m_option_down.IsInput) - this.DrawBezier(g, m_drawing_tools.Pen, m_pt_in_canvas, m_pt_dot_down, this._Curvature); - else - this.DrawBezier(g, m_drawing_tools.Pen, m_pt_dot_down, m_pt_in_canvas, this._Curvature); - } - //重置绘图坐标 我认为除了节点以外的其它 修饰相关的绘制不应该在Canvas坐标系中绘制 而应该使用控件的坐标进行绘制 不然会受到缩放比影响 - g.ResetTransform(); - - switch (m_ca) { - case CanvasAction.MoveNode: //移动过程中 绘制对齐参考线 - this.OnDrawMagnetLine(m_drawing_tools, m_mi); - break; - case CanvasAction.SelectRectangle: //绘制矩形选取 - this.OnDrawSelectedRectangle(m_drawing_tools, this.CanvasToControl(m_rect_select)); - break; - case CanvasAction.DrawMarkDetails: //绘制标记信息详情 - if (!string.IsNullOrEmpty(m_find.Mark)) this.OnDrawMark(m_drawing_tools); - break; - } - - if (this._ShowLocation) this.OnDrawNodeOutLocation(m_drawing_tools, this.Size, m_lst_node_out); - this.OnDrawAlert(g); - } - - protected override void OnMouseDown(MouseEventArgs e) { - base.OnMouseDown(e); - this.Focus(); - m_ca = CanvasAction.None; - m_mi.XMatched = m_mi.YMatched = false; - m_pt_down_in_control = e.Location; - m_pt_down_in_canvas.X = ((e.X - this._CanvasOffsetX) / this._CanvasScale); - m_pt_down_in_canvas.Y = ((e.Y - this._CanvasOffsetY) / this._CanvasScale); - m_pt_canvas_old.X = this._CanvasOffsetX; - m_pt_canvas_old.Y = this._CanvasOffsetY; - - if (m_gp_hover != null && e.Button == MouseButtons.Right) { //断开连接 - this.DisConnectionHover(); - m_is_process_mouse_event = false; //终止MouseClick与MouseUp向下传递 - return; - } - - NodeFindInfo nfi = this.FindNodeFromPoint(m_pt_down_in_canvas); - if (!string.IsNullOrEmpty(nfi.Mark)) { //如果点下的是标记信息 - m_ca = CanvasAction.DrawMarkDetails; - this.Invalidate(); - return; - } - - if (nfi.NodeOption != null) { //如果点下的Option的连接点 - this.StartConnect(nfi.NodeOption); - return; - } - - if (nfi.Node != null) { - nfi.Node.OnMouseDown(new MouseEventArgs(e.Button, e.Clicks, (int)m_pt_down_in_canvas.X - nfi.Node.Left, (int)m_pt_down_in_canvas.Y - nfi.Node.Top, e.Delta)); - bool bCtrlDown = (Control.ModifierKeys & Keys.Control) == Keys.Control; - if (bCtrlDown) { - if (nfi.Node.IsSelected) { - nfi.Node.SetSelected(false, false); - m_hs_node_selected.Remove(nfi.Node); - if (nfi.Node == this._ActiveNode) { - this.SetActiveNode(null); - } - } else { - nfi.Node.SetSelected(true, false); - m_hs_node_selected.Add(nfi.Node); - } - this.Invalidate(); - this.OnSelectedChanged(new EventArgs()); - return; - } else if (!nfi.Node.IsSelected) { - foreach (var n in m_hs_node_selected) n.SetSelected(false, false); - m_hs_node_selected.Clear(); - } - nfi.Node.SetSelected(true, false); - m_hs_node_selected.Add(nfi.Node); //添加到已选择节点 - if (this.PointInRectangle(nfi.Node.TitleRectangle, m_pt_down_in_canvas.X, m_pt_down_in_canvas.Y)) { - if (e.Button == MouseButtons.Right) { - if (nfi.Node.ContextMenuStrip != null) { - nfi.Node.ContextMenuStrip.Show(this.PointToScreen(e.Location)); - } - } else { - m_dic_pt_selected.Clear(); - foreach (STNode n in m_hs_node_selected)//记录已选择节点位置 如果需要移动已选中节点时候 将会有用 - m_dic_pt_selected.Add(n, n.Location); - m_ca = CanvasAction.MoveNode; //如果点下的是节点的标题 则可以移动该节点 - if (this._Magnet) this.BuildMagnetLocation(); //建立磁铁需要的坐标 如果需要移动已选中节点时候 将会有用 - } - } - //nfi.Node.OnMouseDown(new MouseEventArgs(e.Button, e.Clicks, (int)m_pt_down_in_canvas.X - nfi.Node.Left, (int)m_pt_down_in_canvas.Y - nfi.Node.Top, e.Delta)); - } else { - foreach (var n in m_hs_node_selected) n.SetSelected(false, false);//没有点下任何东西 清空已经选择节点 - m_ca = CanvasAction.SelectRectangle; //进入矩形区域选择模式 - m_rect_select.Width = m_rect_select.Height = 0; - } - this.SetActiveNode(nfi.Node); - } - - protected override void OnMouseMove(MouseEventArgs e) { - base.OnMouseMove(e); - m_pt_in_control = e.Location; - m_pt_in_canvas.X = ((e.X - this._CanvasOffsetX) / this._CanvasScale); - m_pt_in_canvas.Y = ((e.Y - this._CanvasOffsetY) / this._CanvasScale); - - if (e.Button == MouseButtons.Middle) { //鼠标中键移动画布 - this._CanvasOffsetX = m_real_canvas_x = m_pt_canvas_old.X + (e.X - m_pt_down_in_control.X); - this._CanvasOffsetY = m_real_canvas_y = m_pt_canvas_old.Y + (e.Y - m_pt_down_in_control.Y); - this.Invalidate(); - return; - } - if (e.Button == MouseButtons.Left) { //如果鼠标左键点下 判断行为 - m_gp_hover = null; - switch (m_ca) { - case CanvasAction.MoveNode: this.MoveNode(e.Location); return; //当前移动节点 - case CanvasAction.ConnectOption: this.Invalidate(); return; //当前正在连线 - case CanvasAction.SelectRectangle: //当前正在选取 - m_rect_select.X = m_pt_down_in_canvas.X < m_pt_in_canvas.X ? m_pt_down_in_canvas.X : m_pt_in_canvas.X; - m_rect_select.Y = m_pt_down_in_canvas.Y < m_pt_in_canvas.Y ? m_pt_down_in_canvas.Y : m_pt_in_canvas.Y; - m_rect_select.Width = Math.Abs(m_pt_in_canvas.X - m_pt_down_in_canvas.X); - m_rect_select.Height = Math.Abs(m_pt_in_canvas.Y - m_pt_down_in_canvas.Y); - foreach (STNode n in this._Nodes) { - n.SetSelected(m_rect_select.IntersectsWith(n.Rectangle), false); - } - this.Invalidate(); - return; - } - } - //若不存在行为 则判断鼠标下方是否存在其他对象 - NodeFindInfo nfi = this.FindNodeFromPoint(m_pt_in_canvas); - bool bRedraw = false; - if (this._HoverNode != nfi.Node) { //鼠标悬停到Node上 - if (nfi.Node != null) nfi.Node.OnMouseEnter(new EventArgs()); - if (this._HoverNode != null) - this._HoverNode.OnMouseLeave(new MouseEventArgs(e.Button, e.Clicks, - (int)m_pt_in_canvas.X - this._HoverNode.Left, - (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta)); - this._HoverNode = nfi.Node; - this.OnHoverChanged(new EventArgs()); - bRedraw = true; - } - if (this._HoverNode != null) { - this._HoverNode.OnMouseMove(new MouseEventArgs(e.Button, e.Clicks, - (int)m_pt_in_canvas.X - this._HoverNode.Left, - (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta)); - m_gp_hover = null; - } else { - GraphicsPath gp = null; - foreach (var v in m_dic_gp_info) { //判断鼠标是否悬停到连线路径上 - if (v.Key.IsOutlineVisible(m_pt_in_canvas, m_p_line_hover)) { - gp = v.Key; - break; - } - } - if (m_gp_hover != gp) { - m_gp_hover = gp; - bRedraw = true; - } - } - if (bRedraw) this.Invalidate(); - } - - protected override void OnMouseUp(MouseEventArgs e) { - base.OnMouseUp(e); - var nfi = this.FindNodeFromPoint(m_pt_in_canvas); - switch (m_ca) { //鼠标抬起时候 判断行为 - case CanvasAction.MoveNode: //若正在移动Node 则重新记录当前位置 - foreach (STNode n in m_dic_pt_selected.Keys.ToList()) m_dic_pt_selected[n] = n.Location; - break; - case CanvasAction.ConnectOption: //若正在连线 则结束连接 - if (e.Location == m_pt_down_in_control) break; - if (nfi.NodeOption != null) { - if (m_option_down.IsInput) - nfi.NodeOption.ConnectOption(m_option_down); - else - m_option_down.ConnectOption(nfi.NodeOption); - } - break; - case CanvasAction.SelectRectangle: //若正在进行选取 则判断是否有选中目标 - m_hs_node_selected.Clear(); - foreach (STNode n in this._Nodes) { - if (n.IsSelected) m_hs_node_selected.Add(n); - } - if (m_hs_node_selected.Count != 0) this.OnSelectedChanged(new EventArgs()); - break; - } - if (m_is_process_mouse_event && this._ActiveNode != null) { - var mea = new MouseEventArgs(e.Button, e.Clicks, - (int)m_pt_in_canvas.X - this._ActiveNode.Left, - (int)m_pt_in_canvas.Y - this._ActiveNode.Top, e.Delta); - this._ActiveNode.OnMouseUp(mea); - } - m_is_process_mouse_event = true; //当前为断开连接操作不进行事件传递 下次将接受事件 - m_ca = CanvasAction.None; - this.Invalidate(); - } - - protected override void OnMouseLeave(EventArgs e) { - base.OnMouseLeave(e); - if (this._HoverNode != null) this._HoverNode.OnMouseLeave(e); - this._HoverNode = null; - this.Invalidate(); - } - - protected override void OnMouseWheel(MouseEventArgs e) { - base.OnMouseWheel(e); - if ((Control.ModifierKeys & Keys.Control) == Keys.Control) { - float f = this._CanvasScale + (e.Delta < 0 ? -0.1f : 0.1f); - this.ScaleCanvas(f, this.Width / 2, this.Height / 2); - } else { - var nfi = this.FindNodeFromPoint(m_pt_in_canvas); - if (this._HoverNode != null) { - this._HoverNode.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks, - (int)m_pt_in_canvas.X - this._HoverNode.Left, - (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta)); - return; - } - int t = (int)DateTime.Now.Subtract(m_dt_vw).TotalMilliseconds; - if (t <= 30) t = 40; - else if (t <= 100) t = 20; - else if (t <= 150) t = 10; - else if (t <= 300) t = 4; - else t = 2; - this.MoveCanvas(this._CanvasOffsetX, m_real_canvas_y + (e.Delta < 0 ? -t : t), true, CanvasMoveArgs.Top);//process mouse mid - m_dt_vw = DateTime.Now; - } - } - - protected virtual void OnMouseHWheel(MouseEventArgs e) { - if ((Control.ModifierKeys & Keys.Control) == Keys.Control) return; - if (this._HoverNode != null) { - this._HoverNode.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks, - (int)m_pt_in_canvas.X - this._HoverNode.Left, - (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta)); - return; - } - int t = (int)DateTime.Now.Subtract(m_dt_hw).TotalMilliseconds; - if (t <= 30) t = 40; - else if (t <= 100) t = 20; - else if (t <= 150) t = 10; - else if (t <= 300) t = 4; - else t = 2; - this.MoveCanvas(m_real_canvas_x + (e.Delta > 0 ? -t : t), this._CanvasOffsetY, true, CanvasMoveArgs.Left); - m_dt_hw = DateTime.Now; - } - //===========================for node other event================================== - protected override void OnMouseClick(MouseEventArgs e) { - base.OnMouseClick(e); - if (this._ActiveNode != null && m_is_process_mouse_event) { - if (!this.PointInRectangle(this._ActiveNode.Rectangle, m_pt_in_canvas.X, m_pt_in_canvas.Y)) return; - this._ActiveNode.OnMouseClick(new MouseEventArgs(e.Button, e.Clicks, - (int)m_pt_down_in_canvas.X - this._ActiveNode.Left, - (int)m_pt_down_in_canvas.Y - this._ActiveNode.Top, e.Delta)); - } - } - - protected override void OnKeyDown(KeyEventArgs e) { - base.OnKeyDown(e); - if (this._ActiveNode != null) this._ActiveNode.OnKeyDown(e); - } - - protected override void OnKeyUp(KeyEventArgs e) { - base.OnKeyUp(e); - if (this._ActiveNode != null) this._ActiveNode.OnKeyUp(e); - } - - protected override void OnKeyPress(KeyPressEventArgs e) { - base.OnKeyPress(e); - if (this._ActiveNode != null) this._ActiveNode.OnKeyPress(e); - } - - #endregion - - #region protected ---------------------------------------------------------------------------------------------------- - /// - /// 当绘制背景网格线时候发生 - /// - /// 绘制工具 - /// 需要绘制宽度 - /// 需要绘制高度 - protected virtual void OnDrawGrid(DrawingTools dt, int nWidth, int nHeight) { - Graphics g = dt.Graphics; - using (Pen p_2 = new Pen(Color.FromArgb(65, this._GridColor))) { - using (Pen p_1 = new Pen(Color.FromArgb(30, this._GridColor))) { - float nIncrement = (20 * this._CanvasScale);             //网格间的间隔 根据比例绘制 - int n = 5 - (int)(this._CanvasOffsetX / nIncrement); - for (float f = this._CanvasOffsetX % nIncrement; f < nWidth; f += nIncrement) - g.DrawLine((n++ % 5 == 0 ? p_2 : p_1), f, 0, f, nHeight); - n = 5 - (int)(this._CanvasOffsetY / nIncrement); - for (float f = this._CanvasOffsetY % nIncrement; f < nHeight; f += nIncrement) - g.DrawLine((n++ % 5 == 0 ? p_2 : p_1), 0, f, nWidth, f); - //原点两天线 - p_1.Color = Color.FromArgb(this._Nodes.Count == 0 ? 255 : 120, this._GridColor); - g.DrawLine(p_1, this._CanvasOffsetX, 0, this._CanvasOffsetX, nHeight); - g.DrawLine(p_1, 0, this._CanvasOffsetY, nWidth, this._CanvasOffsetY); - } - } - } - /// - /// 当绘制 Node 时候发生 - /// - /// 绘制工具 - /// 可视画布区域大小 - protected virtual void OnDrawNode(DrawingTools dt, Rectangle rect) { - m_lst_node_out.Clear(); //清空超出视觉区域的 Node 的坐标 - //var rect_canvas_display = this.ControlToCanvas(rect); - Image img_border = null; - foreach (STNode n in this._Nodes) { - //n.CheckSize(dt); - img_border = m_img_border; - if (this._ShowBorder) { //如果绘制边框 判断状态 - if (this._ActiveNode == n) img_border = m_img_border_active; - else if (/*m_hs_node_selected.Contains(n)*/n.IsSelected) img_border = m_img_border_selected; - else if (this._HoverNode == n) img_border = m_img_border_hover; - this.RenderBorder(dt.Graphics, n.Rectangle, img_border); - if (!string.IsNullOrEmpty(n.Mark)) this.RenderBorder(dt.Graphics, n.MarkRectangle, img_border); - } - n.OnDrawNode(dt); //调用 Node 进行自身绘制主体部分 - if (!string.IsNullOrEmpty(n.Mark)) n.OnDrawMark(dt); //调用 Node 进行自身绘制 Mark 区域 - if (!rect.IntersectsWith(n.Rectangle)) { - m_lst_node_out.Add(n.Location); //判断此 Node 是否超出视觉区域 - } - } - } - /// - /// 当绘制已连接路径时候发生 - /// - /// 绘制工具 - protected virtual void OnDrawConnectedLine(DrawingTools dt) { - Graphics g = dt.Graphics; - g.SmoothingMode = SmoothingMode.HighQuality; - m_p_line_hover.Color = Color.FromArgb(50, 0, 0, 0); - var t = typeof(object); - foreach (STNode n in this._Nodes) { - foreach (STNodeOption op in n.OutputOptions) { - if (op.DotColor != Color.Transparent) //确定线条颜色 - m_p_line.Color = op.DotColor; - else { - if (op.DataType == t) - m_p_line.Color = this._UnknownTypeColor; - else - m_p_line.Color = this._TypeColor.ContainsKey(op.DataType) ? this._TypeColor[op.DataType] : this._UnknownTypeColor;//value can not be null - } - foreach (var v in op.ConnectedOption) { - this.DrawBezier(g, m_p_line_hover, op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2, - v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature); - this.DrawBezier(g, m_p_line, op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2, - v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature); - if (m_is_buildpath) { //如果当前绘制需要重新建立已连接的路径缓存 - GraphicsPath gp = this.CreateBezierPath(op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2, - v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature); - m_dic_gp_info.Add(gp, new ConnectionInfo() { Output = op, Input = v }); - } - } - } - } - m_p_line_hover.Color = this._HighLineColor; - if (m_gp_hover != null) { //如果当前有被悬停的连接路劲 则高亮绘制 - g.DrawPath(m_p_line_hover, m_gp_hover); - } - m_is_buildpath = false; //重置标志 下次绘制时候 不再重新建立路径缓存 - } - /// - /// 当绘制 Mark 详情信息时候发生 - /// - /// 绘制工具 - protected virtual void OnDrawMark(DrawingTools dt) { - Graphics g = dt.Graphics; - SizeF sz = g.MeasureString(m_find.Mark, this.Font); //确认文字需要的大小 - Rectangle rect = new Rectangle(m_pt_in_control.X + 15, - m_pt_in_control.Y + 10, - (int)sz.Width + 6, - 4 + (this.Font.Height + 4) * m_find.MarkLines.Length); //sz.Height并没有考虑文字的行距 所以这里高度自己计算 - - if (rect.Right > this.Width) rect.X = this.Width - rect.Width; - if (rect.Bottom > this.Height) rect.Y = this.Height - rect.Height; - if (rect.X < 0) rect.X = 0; - if (rect.Y < 0) rect.Y = 0; - - dt.SolidBrush.Color = this._MarkBackColor; - g.SmoothingMode = SmoothingMode.None; - g.FillRectangle(dt.SolidBrush, rect); //绘制背景区域 - rect.Width--; rect.Height--; - dt.Pen.Color = Color.FromArgb(255, this._MarkBackColor); - g.DrawRectangle(dt.Pen, rect); - dt.SolidBrush.Color = this._MarkForeColor; - - m_sf.LineAlignment = StringAlignment.Center; - //g.SmoothingMode = SmoothingMode.HighQuality; - rect.X += 2; rect.Width -= 3; - rect.Height = this.Font.Height + 4; - int nY = rect.Y + 2; - for (int i = 0; i < m_find.MarkLines.Length; i++) { //绘制文字 - rect.Y = nY + i * (this.Font.Height + 4); - g.DrawString(m_find.MarkLines[i], this.Font, dt.SolidBrush, rect, m_sf); - } - } - /// - /// 当移动 Node 时候 需要显示对齐参考线时候发生 - /// - /// 绘制工具 - /// 匹配的磁铁信息 - protected virtual void OnDrawMagnetLine(DrawingTools dt, MagnetInfo mi) { - Graphics g = dt.Graphics; - Pen pen = m_drawing_tools.Pen; - pen.Color = this._MagnetLineColor; - if (mi.XMatched) g.DrawLine(pen, this.CanvasToControl(mi.X, true), 0, this.CanvasToControl(mi.X, true), this.Height); - if (mi.YMatched) g.DrawLine(pen, 0, this.CanvasToControl(mi.Y, false), this.Width, this.CanvasToControl(mi.Y, false)); - } - /// - /// 绘制选择的矩形区域 - /// - /// 绘制工具 - /// 位于控件上的矩形区域 - protected virtual void OnDrawSelectedRectangle(DrawingTools dt, RectangleF rectf) { - Graphics g = dt.Graphics; - SolidBrush brush = dt.SolidBrush; - g.DrawRectangle(Pens.DodgerBlue, rectf.Left, rectf.Y, rectf.Width, rectf.Height); - brush.Color = Color.FromArgb(50, Color.DodgerBlue); - g.FillRectangle(brush, this.CanvasToControl(m_rect_select)); - } - /// - /// 绘制超出视觉区域的 Node 位置提示信息 - /// - /// 绘制工具 - /// 提示框边距 - /// 超出视觉区域的 Node 位置信息 - protected virtual void OnDrawNodeOutLocation(DrawingTools dt, Size sz, List lstPts) { - Graphics g = dt.Graphics; - SolidBrush brush = dt.SolidBrush; - brush.Color = this._LocationBackColor; - g.SmoothingMode = SmoothingMode.None; - if (lstPts.Count == this._Nodes.Count && this._Nodes.Count != 0) { //如果超出个数和集合个数一样多 则全部超出 绘制外切矩形 - g.FillRectangle(brush, this.CanvasToControl(this._CanvasValidBounds)); - } - g.FillRectangle(brush, 0, 0, 4, sz.Height); //绘制四边背景 - g.FillRectangle(brush, sz.Width - 4, 0, 4, sz.Height); - g.FillRectangle(brush, 4, 0, sz.Width - 8, 4); - g.FillRectangle(brush, 4, sz.Height - 4, sz.Width - 8, 4); - brush.Color = this._LocationForeColor; - foreach (var v in lstPts) { //绘制点 - var pt = this.CanvasToControl(v); - if (pt.X < 0) pt.X = 0; - if (pt.Y < 0) pt.Y = 0; - if (pt.X > sz.Width) pt.X = sz.Width - 4; - if (pt.Y > sz.Height) pt.Y = sz.Height - 4; - g.FillRectangle(brush, pt.X, pt.Y, 4, 4); - } - } - /// - /// 绘制提示信息 - /// - /// 绘制工具 - /// 需要绘制区域 - /// 需要绘制文本 - /// 信息前景色 - /// 信息背景色 - /// 信息位置 - protected virtual void OnDrawAlert(DrawingTools dt, Rectangle rect, string strText, Color foreColor, Color backColor, AlertLocation al) { - if (m_alpha_alert == 0) return; - Graphics g = dt.Graphics; - SolidBrush brush = dt.SolidBrush; - - g.SmoothingMode = SmoothingMode.None; - brush.Color = backColor; - dt.Pen.Color = brush.Color; - g.FillRectangle(brush, rect); - g.DrawRectangle(dt.Pen, rect.Left, rect.Top, rect.Width - 1, rect.Height - 1); - - brush.Color = foreColor; - m_sf.Alignment = StringAlignment.Center; - m_sf.LineAlignment = StringAlignment.Center; - g.SmoothingMode = SmoothingMode.HighQuality; - g.DrawString(strText, this.Font, brush, rect, m_sf); - } - /// - /// 获取提示信息需要绘制的矩形区域 - /// - /// 绘图表面 - /// 需要绘制文本 - /// 信息位置 - /// 矩形区域 - protected virtual Rectangle GetAlertRectangle(Graphics g, string strText, AlertLocation al) { - SizeF szf = g.MeasureString(m_str_alert, this.Font); - Size sz = new Size((int)Math.Round(szf.Width + 10), (int)Math.Round(szf.Height + 4)); - Rectangle rect = new Rectangle(4, this.Height - sz.Height - 4, sz.Width, sz.Height); - - switch (al) { - case AlertLocation.Left: - rect.Y = (this.Height - sz.Height) >> 1; - break; - case AlertLocation.Top: - rect.Y = 4; - rect.X = (this.Width - sz.Width) >> 1; - break; - case AlertLocation.Right: - rect.X = this.Width - sz.Width - 4; - rect.Y = (this.Height - sz.Height) >> 1; - break; - case AlertLocation.Bottom: - rect.X = (this.Width - sz.Width) >> 1; - break; - case AlertLocation.Center: - rect.X = (this.Width - sz.Width) >> 1; - rect.Y = (this.Height - sz.Height) >> 1; - break; - case AlertLocation.LeftTop: - rect.X = rect.Y = 4; - break; - case AlertLocation.RightTop: - rect.Y = 4; - rect.X = this.Width - sz.Width - 4; - break; - case AlertLocation.RightBottom: - rect.X = this.Width - sz.Width - 4; - break; - } - return rect; - } - - #endregion protected - - #region internal - - internal void BuildLinePath() { - foreach (var v in m_dic_gp_info) v.Key.Dispose(); - m_dic_gp_info.Clear(); - m_is_buildpath = true; - this.Invalidate(); - } - - internal void OnDrawAlert(Graphics g) { - m_rect_alert = this.GetAlertRectangle(g, m_str_alert, m_al); - Color clr_fore = Color.FromArgb((int)((float)m_alpha_alert / 255 * m_forecolor_alert.A), m_forecolor_alert); - Color clr_back = Color.FromArgb((int)((float)m_alpha_alert / 255 * m_backcolor_alert.A), m_backcolor_alert); - this.OnDrawAlert(m_drawing_tools, m_rect_alert, m_str_alert, clr_fore, clr_back, m_al); - } - - #endregion internal - - #region private ----------------------------------------------------------------------------------------------------- - - private void MoveCanvasThread() { - bool bRedraw; - while (true) { - bRedraw = false; - if (m_real_canvas_x != this._CanvasOffsetX) { - float nx = m_real_canvas_x - this._CanvasOffsetX; - float n = Math.Abs(nx) / 10; - float nTemp = Math.Abs(nx); - if (nTemp <= 4) n = 1; - else if (nTemp <= 12) n = 2; - else if (nTemp <= 30) n = 3; - if (nTemp < 1) this._CanvasOffsetX = m_real_canvas_x; - else - this._CanvasOffsetX += nx > 0 ? n : -n; - bRedraw = true; - } - if (m_real_canvas_y != this._CanvasOffsetY) { - float ny = m_real_canvas_y - this._CanvasOffsetY; - float n = Math.Abs(ny) / 10; - float nTemp = Math.Abs(ny); - if (nTemp <= 4) n = 1; - else if (nTemp <= 12) n = 2; - else if (nTemp <= 30) n = 3; - if (nTemp < 1) - this._CanvasOffsetY = m_real_canvas_y; - else - this._CanvasOffsetY += ny > 0 ? n : -n; - bRedraw = true; - } - if (bRedraw) { - m_pt_canvas_old.X = this._CanvasOffsetX; - m_pt_canvas_old.Y = this._CanvasOffsetY; - this.Invalidate(); - Thread.Sleep(30); - } else { - Thread.Sleep(100); - } - } - } - - private void ShowAlertThread() { - while (true) { - int nTime = m_time_alert - (int)DateTime.Now.Subtract(m_dt_alert).TotalMilliseconds; - if (nTime > 0) { - Thread.Sleep(nTime); - continue; - } - if (nTime < -1000) { - if (m_alpha_alert != 0) { - m_alpha_alert = 0; - this.Invalidate(); - } - Thread.Sleep(100); - } else { - m_alpha_alert = (int)(255 - (-nTime / 1000F) * 255); - this.Invalidate(m_rect_alert); - Thread.Sleep(50); - } - } - } - - private Image CreateBorderImage(Color clr) { - Image img = new Bitmap(12, 12); - using (Graphics g = Graphics.FromImage(img)) { - g.SmoothingMode = SmoothingMode.HighQuality; - using (GraphicsPath gp = new GraphicsPath()) { - gp.AddEllipse(new Rectangle(0, 0, 11, 11)); - using (PathGradientBrush b = new PathGradientBrush(gp)) { - b.CenterColor = Color.FromArgb(200, clr); - b.SurroundColors = new Color[] { Color.FromArgb(10, clr) }; - g.FillPath(b, gp); - } - } - } - return img; - } - - private ConnectionStatus DisConnectionHover() { - if (!m_dic_gp_info.ContainsKey(m_gp_hover)) return ConnectionStatus.DisConnected; - ConnectionInfo ci = m_dic_gp_info[m_gp_hover]; - var ret = ci.Output.DisConnectOption(ci.Input); - //this.OnOptionDisConnected(new STNodeOptionEventArgs(ci.Output, ci.Input, ret)); - if (ret == ConnectionStatus.DisConnected) { - m_dic_gp_info.Remove(m_gp_hover); - m_gp_hover.Dispose(); - m_gp_hover = null; - this.Invalidate(); - } - return ret; - } - - private void StartConnect(STNodeOption op) { - if (op.IsInput) { - m_pt_dot_down.X = op.DotLeft; - m_pt_dot_down.Y = op.DotTop + 5; - } else { - m_pt_dot_down.X = op.DotLeft + op.DotSize; - m_pt_dot_down.Y = op.DotTop + 5; - } - m_ca = CanvasAction.ConnectOption; - m_option_down = op; - } - - private void MoveNode(Point pt) { - int nX = (int)((pt.X - m_pt_down_in_control.X) / this._CanvasScale); - int nY = (int)((pt.Y - m_pt_down_in_control.Y) / this._CanvasScale); - foreach (STNode v in m_hs_node_selected) { - v.Left = m_dic_pt_selected[v].X + nX; - v.Top = m_dic_pt_selected[v].Y + nY; - } - if (this._Magnet) { - MagnetInfo mi = this.CheckMagnet(this._ActiveNode); - if (mi.XMatched) { - foreach (STNode v in m_hs_node_selected) v.Left -= mi.OffsetX; - } - if (mi.YMatched) { - foreach (STNode v in m_hs_node_selected) v.Top -= mi.OffsetY; - } - } - this.Invalidate(); - } - - protected internal virtual void BuildBounds() { - if (this._Nodes.Count == 0) { - this._CanvasValidBounds = this.ControlToCanvas(this.DisplayRectangle); - return; - } - int x = int.MaxValue; - int y = int.MaxValue; - int r = int.MinValue; - int b = int.MinValue; - foreach (STNode n in this._Nodes) { - if (x > n.Left) x = n.Left; - if (y > n.Top) y = n.Top; - if (r < n.Right) r = n.Right; - if (b < n.Bottom) b = n.Bottom; - } - this._CanvasValidBounds.X = x - 60; - this._CanvasValidBounds.Y = y - 60; - this._CanvasValidBounds.Width = r - x + 120; - this._CanvasValidBounds.Height = b - y + 120; - } - - private bool PointInRectangle(Rectangle rect, float x, float y) { - if (x < rect.Left) return false; - if (x > rect.Right) return false; - if (y < rect.Top) return false; - if (y > rect.Bottom) return false; - return true; - } - - private void BuildMagnetLocation() { - m_lst_magnet_x.Clear(); - m_lst_magnet_y.Clear(); - foreach (STNode v in this._Nodes) { - if (v.IsSelected) continue; - m_lst_magnet_x.Add(v.Left); - m_lst_magnet_x.Add(v.Left + v.Width / 2); - m_lst_magnet_x.Add(v.Left + v.Width); - m_lst_magnet_y.Add(v.Top); - m_lst_magnet_y.Add(v.Top + v.Height / 2); - m_lst_magnet_y.Add(v.Top + v.Height); - } - } - - private MagnetInfo CheckMagnet(STNode node) { - m_mi.XMatched = m_mi.YMatched = false; - m_lst_magnet_mx.Clear(); - m_lst_magnet_my.Clear(); - m_lst_magnet_mx.Add(node.Left + node.Width / 2); - m_lst_magnet_mx.Add(node.Left); - m_lst_magnet_mx.Add(node.Left + node.Width); - m_lst_magnet_my.Add(node.Top + node.Height / 2); - m_lst_magnet_my.Add(node.Top); - m_lst_magnet_my.Add(node.Top + node.Height); - - bool bFlag = false; - foreach (var mx in m_lst_magnet_mx) { - foreach (var x in m_lst_magnet_x) { - if (Math.Abs(mx - x) <= 5) { - bFlag = true; - m_mi.X = x; - m_mi.OffsetX = mx - x; - m_mi.XMatched = true; - break; - } - } - if (bFlag) break; - } - bFlag = false; - foreach (var my in m_lst_magnet_my) { - foreach (var y in m_lst_magnet_y) { - if (Math.Abs(my - y) <= 5) { - bFlag = true; - m_mi.Y = y; - m_mi.OffsetY = my - y; - m_mi.YMatched = true; - break; - } - } - if (bFlag) break; - } - return m_mi; - } - - private void DrawBezier(Graphics g, Pen p, PointF ptStart, PointF ptEnd, float f) { - this.DrawBezier(g, p, ptStart.X, ptStart.Y, ptEnd.X, ptEnd.Y, f); - } - - private void DrawBezier(Graphics g, Pen p, float x1, float y1, float x2, float y2, float f) { - float n = (Math.Abs(x1 - x2) * f); - if (this._Curvature != 0 && n < 30) n = 30; - g.DrawBezier(p, - x1, y1, - x1 + n, y1, - x2 - n, y2, - x2, y2); - } - - private GraphicsPath CreateBezierPath(float x1, float y1, float x2, float y2, float f) { - GraphicsPath gp = new GraphicsPath(); - float n = (Math.Abs(x1 - x2) * f); - if (this._Curvature != 0 && n < 30) n = 30; - gp.AddBezier( - x1, y1, - x1 + n, y1, - x2 - n, y2, - x2, y2 - ); - return gp; - } - - private void RenderBorder(Graphics g, Rectangle rect, Image img) { - //填充四个角 - g.DrawImage(img, new Rectangle(rect.X - 5, rect.Y - 5, 5, 5), - new Rectangle(0, 0, 5, 5), GraphicsUnit.Pixel); - g.DrawImage(img, new Rectangle(rect.Right, rect.Y - 5, 5, 5), - new Rectangle(img.Width - 5, 0, 5, 5), GraphicsUnit.Pixel); - g.DrawImage(img, new Rectangle(rect.X - 5, rect.Bottom, 5, 5), - new Rectangle(0, img.Height - 5, 5, 5), GraphicsUnit.Pixel); - g.DrawImage(img, new Rectangle(rect.Right, rect.Bottom, 5, 5), - new Rectangle(img.Width - 5, img.Height - 5, 5, 5), GraphicsUnit.Pixel); - //四边 - g.DrawImage(img, new Rectangle(rect.X - 5, rect.Y, 5, rect.Height), - new Rectangle(0, 5, 5, img.Height - 10), GraphicsUnit.Pixel); - g.DrawImage(img, new Rectangle(rect.X, rect.Y - 5, rect.Width, 5), - new Rectangle(5, 0, img.Width - 10, 5), GraphicsUnit.Pixel); - g.DrawImage(img, new Rectangle(rect.Right, rect.Y, 5, rect.Height), - new Rectangle(img.Width - 5, 5, 5, img.Height - 10), GraphicsUnit.Pixel); - g.DrawImage(img, new Rectangle(rect.X, rect.Bottom, rect.Width, 5), - new Rectangle(5, img.Height - 5, img.Width - 10, 5), GraphicsUnit.Pixel); - } - - #endregion private - - #region public -------------------------------------------------------------------------------------------------------- - /// - /// 通过画布坐标进行寻找 - /// - /// 画布中的坐标 - /// 寻找到的数据 - public NodeFindInfo FindNodeFromPoint(PointF pt) { - m_find.Node = null; m_find.NodeOption = null; m_find.Mark = null; - for (int i = this._Nodes.Count - 1; i >= 0; i--) { - if (!string.IsNullOrEmpty(this._Nodes[i].Mark) && this.PointInRectangle(this._Nodes[i].MarkRectangle, pt.X, pt.Y)) { - m_find.Mark = this._Nodes[i].Mark; - m_find.MarkLines = this._Nodes[i].MarkLines; - return m_find; - } - foreach (STNodeOption v in this._Nodes[i].InputOptions) { - if (this.PointInRectangle(v.DotRectangle, pt.X, pt.Y)) m_find.NodeOption = v; - } - foreach (STNodeOption v in this._Nodes[i].OutputOptions) { - if (this.PointInRectangle(v.DotRectangle, pt.X, pt.Y)) m_find.NodeOption = v; - } - if (this.PointInRectangle(this._Nodes[i].Rectangle, pt.X, pt.Y)) { - m_find.Node = this._Nodes[i]; - } - if (m_find.NodeOption != null || m_find.Node != null) return m_find; - } - return m_find; - } - /// - /// 获取已经被选择的 Node 集合 - /// - /// Node 集合 - public STNode[] GetSelectedNode() { - return m_hs_node_selected.ToArray(); - } - /// - /// 将画布坐标转换为控件坐标 - /// - /// 参数 - /// 是否为 X 坐标 - /// 转换后的坐标 - public float CanvasToControl(float number, bool isX) { - return (number * this._CanvasScale) + (isX ? this._CanvasOffsetX : this._CanvasOffsetY); - } - /// - /// 将画布坐标转换为控件坐标 - /// - /// 坐标 - /// 转换后的坐标 - public PointF CanvasToControl(PointF pt) { - pt.X = (pt.X * this._CanvasScale) + this._CanvasOffsetX; - pt.Y = (pt.Y * this._CanvasScale) + this._CanvasOffsetY; - return pt; - } - /// - /// 将画布坐标转换为控件坐标 - /// - /// 坐标 - /// 转换后的坐标 - public Point CanvasToControl(Point pt) { - pt.X = (int)(pt.X * this._CanvasScale + this._CanvasOffsetX); - pt.Y = (int)(pt.Y * this._CanvasScale + this._CanvasOffsetY); - return pt; - } - /// - /// 将画布坐标转换为控件坐标 - /// - /// 矩形区域 - /// 转换后的矩形区域 - public Rectangle CanvasToControl(Rectangle rect) { - rect.X = (int)((rect.X * this._CanvasScale) + this._CanvasOffsetX); - rect.Y = (int)((rect.Y * this._CanvasScale) + this._CanvasOffsetY); - rect.Width = (int)(rect.Width * this._CanvasScale); - rect.Height = (int)(rect.Height * this._CanvasScale); - return rect; - } - /// - /// 将画布坐标转换为控件坐标 - /// - /// 矩形区域 - /// 转换后的矩形区域 - public RectangleF CanvasToControl(RectangleF rect) { - rect.X = (rect.X * this._CanvasScale) + this._CanvasOffsetX; - rect.Y = (rect.Y * this._CanvasScale) + this._CanvasOffsetY; - rect.Width = (rect.Width * this._CanvasScale); - rect.Height = (rect.Height * this._CanvasScale); - return rect; - } - /// - /// 将控件坐标转换为画布坐标 - /// - /// 参数 - /// 是否为 X 坐标 - /// 转换后的坐标 - public float ControlToCanvas(float number, bool isX) { - return (number - (isX ? this._CanvasOffsetX : this._CanvasOffsetY)) / this._CanvasScale; - } - /// - /// 将控件坐标转换为画布坐标 - /// - /// 坐标 - /// 转换后的坐标 - public Point ControlToCanvas(Point pt) { - pt.X = (int)((pt.X - this._CanvasOffsetX) / this._CanvasScale); - pt.Y = (int)((pt.Y - this._CanvasOffsetY) / this._CanvasScale); - return pt; - } - /// - /// 将控件坐标转换为画布坐标 - /// - /// 坐标 - /// 转换后的坐标 - public PointF ControlToCanvas(PointF pt) { - pt.X = ((pt.X - this._CanvasOffsetX) / this._CanvasScale); - pt.Y = ((pt.Y - this._CanvasOffsetY) / this._CanvasScale); - return pt; - } - /// - /// 将控件坐标转换为画布坐标 - /// - /// 矩形区域 - /// 转换后的区域 - public Rectangle ControlToCanvas(Rectangle rect) { - rect.X = (int)((rect.X - this._CanvasOffsetX) / this._CanvasScale); - rect.Y = (int)((rect.Y - this._CanvasOffsetY) / this._CanvasScale); - rect.Width = (int)(rect.Width / this._CanvasScale); - rect.Height = (int)(rect.Height / this._CanvasScale); - return rect; - } - /// - /// 将控件坐标转换为画布坐标 - /// - /// 矩形区域 - /// 转换后的区域 - public RectangleF ControlToCanvas(RectangleF rect) { - rect.X = ((rect.X - this._CanvasOffsetX) / this._CanvasScale); - rect.Y = ((rect.Y - this._CanvasOffsetY) / this._CanvasScale); - rect.Width = (rect.Width / this._CanvasScale); - rect.Height = (rect.Height / this._CanvasScale); - return rect; - } - /// - /// 移动画布原点坐标到指定的控件坐标位置 - /// 当不存在 Node 时候 无法移动 - /// - /// X 坐标 - /// Y 坐标 - /// 移动过程中是否启动动画效果 - /// 指定需要修改的坐标参数 - public void MoveCanvas(float x, float y, bool bAnimation, CanvasMoveArgs ma) { - if (this._Nodes.Count == 0) { - m_real_canvas_x = m_real_canvas_y = 10; - return; - } - int l = (int)((this._CanvasValidBounds.Left + 50) * this._CanvasScale); - int t = (int)((this._CanvasValidBounds.Top + 50) * this._CanvasScale); - int r = (int)((this._CanvasValidBounds.Right - 50) * this._CanvasScale); - int b = (int)((this._CanvasValidBounds.Bottom - 50) * this._CanvasScale); - if (r + x < 0) x = -r; - if (this.Width - l < x) x = this.Width - l; - if (b + y < 0) y = -b; - if (this.Height - t < y) y = this.Height - t; - if (bAnimation) { - if ((ma & CanvasMoveArgs.Left) == CanvasMoveArgs.Left) - m_real_canvas_x = x; - if ((ma & CanvasMoveArgs.Top) == CanvasMoveArgs.Top) - m_real_canvas_y = y; - } else { - m_real_canvas_x = this._CanvasOffsetX = x; - m_real_canvas_y = this._CanvasOffsetY = y; - } - this.OnCanvasMoved(new EventArgs()); - } - /// - /// 缩放画布 - /// 当不存在 Node 时候 无法缩放 - /// - /// 缩放比例 - /// 以指定控件坐标 X 为中心进行缩放 - /// 以指定控件坐标 Y 为中心进行缩放 - public void ScaleCanvas(float f, float x, float y) { - if (this._Nodes.Count == 0) { - this._CanvasScale = 1F; - return; - } - if (this._CanvasScale == f) return; - if (f < 0.5) f = 0.5f; else if (f > 3) f = 3; - float x_c = this.ControlToCanvas(x, true); - float y_c = this.ControlToCanvas(y, false); - this._CanvasScale = f; - this._CanvasOffsetX = m_real_canvas_x -= this.CanvasToControl(x_c, true) - x; - this._CanvasOffsetY = m_real_canvas_y -= this.CanvasToControl(y_c, false) - y; - this.OnCanvasScaled(new EventArgs()); - this.Invalidate(); - } - /// - /// 获取当前已连接的 Option 对应关系 - /// - /// 连接信息集合 - public ConnectionInfo[] GetConnectionInfo() { - return m_dic_gp_info.Values.ToArray(); - } - /// - /// 判断两个 Node 之间是否存在连接路径 - /// - /// 起始 Node - /// 目标 Node - /// 若存在路径返回true 否则false - public static bool CanFindNodePath(STNode nodeStart, STNode nodeFind) { - HashSet hs = new HashSet(); - return STNodeEditor.CanFindNodePath(nodeStart, nodeFind, hs); - } - private static bool CanFindNodePath(STNode nodeStart, STNode nodeFind, HashSet hs) { - foreach (STNodeOption op_1 in nodeStart.OutputOptions) { - foreach (STNodeOption op_2 in op_1.ConnectedOption) { - if (op_2.Owner == nodeFind) return true; - if (hs.Add(op_2.Owner)) { - if (STNodeEditor.CanFindNodePath(op_2.Owner, nodeFind)) return true; - } - } - } - return false; - } - /// - /// 获取画布中指定矩形区域图像 - /// - /// 画布中指定的矩形区域 - /// 图像 - public Image GetCanvasImage(Rectangle rect) { return this.GetCanvasImage(rect, 1f); } - /// - /// 获取画布中指定矩形区域图像 - /// - /// 画布中指定的矩形区域 - /// 缩放比例 - /// 图像 - public Image GetCanvasImage(Rectangle rect, float fScale) { - if (fScale < 0.5) fScale = 0.5f; else if (fScale > 3) fScale = 3; - Image img = new Bitmap((int)(rect.Width * fScale), (int)(rect.Height * fScale)); - using (Graphics g = Graphics.FromImage(img)) { - g.Clear(this.BackColor); - g.ScaleTransform(fScale, fScale); - m_drawing_tools.Graphics = g; - - if (this._ShowGrid) this.OnDrawGrid(m_drawing_tools, rect.Width, rect.Height); - g.TranslateTransform(-rect.X, -rect.Y); //移动坐标系 - this.OnDrawNode(m_drawing_tools, rect); - this.OnDrawConnectedLine(m_drawing_tools); - - g.ResetTransform(); - - if (this._ShowLocation) this.OnDrawNodeOutLocation(m_drawing_tools, img.Size, m_lst_node_out); - } - return img; - } - /// - /// 保存画布中的类容到文件中 - /// - /// 文件路径 - public void SaveCanvas(string strFileName) { - using (FileStream fs = new FileStream(strFileName, FileMode.Create, FileAccess.Write)) { - this.SaveCanvas(fs); - } - } - /// - /// 保存画布中的类容到数据流 - /// - /// 数据流对象 - public void SaveCanvas(Stream s) { - Dictionary dic = new Dictionary(); - s.Write(new byte[] { (byte)'S', (byte)'T', (byte)'N', (byte)'D' }, 0, 4); //file head - s.WriteByte(1); //ver - using (GZipStream gs = new GZipStream(s, CompressionMode.Compress)) { - gs.Write(BitConverter.GetBytes(this._CanvasOffsetX), 0, 4); - gs.Write(BitConverter.GetBytes(this._CanvasOffsetY), 0, 4); - gs.Write(BitConverter.GetBytes(this._CanvasScale), 0, 4); - gs.Write(BitConverter.GetBytes(this._Nodes.Count), 0, 4); - foreach (STNode node in this._Nodes) { - try { - byte[] byNode = node.GetSaveData(); - gs.Write(BitConverter.GetBytes(byNode.Length), 0, 4); - gs.Write(byNode, 0, byNode.Length); - foreach (STNodeOption op in node.InputOptions) dic.Add(op, dic.Count); - foreach (STNodeOption op in node.OutputOptions) dic.Add(op, dic.Count); - } catch (Exception ex) { - throw new Exception("获取节点数据出错-" + node.Title, ex); - } - } - gs.Write(BitConverter.GetBytes(m_dic_gp_info.Count), 0, 4); - foreach (var v in m_dic_gp_info.Values) - gs.Write(BitConverter.GetBytes(((dic[v.Output] << 32) | dic[v.Input])), 0, 8); - } - } - /// - /// 获取画布中内容二进制数据 - /// - /// 二进制数据 - public byte[] GetCanvasData() { - using (MemoryStream ms = new MemoryStream()) { - this.SaveCanvas(ms); - return ms.ToArray(); - } - } - /// - /// 加载程序集 - /// - /// 程序集集合 - /// 存在STNode类型的文件的个数 - public int LoadAssembly(string[] strFiles) { - int nCount = 0; - foreach (var v in strFiles) { - try { - if (this.LoadAssembly(v)) nCount++; - } catch { } - } - return nCount; - } - /// - /// 加载程序集 - /// - /// 指定需要加载的文件 - /// 是否加载成功 - public bool LoadAssembly(string strFile) { - bool bFound = false; - Assembly asm = Assembly.LoadFrom(strFile); - if (asm == null) return false; - foreach (var t in asm.GetTypes()) { - if (t.IsAbstract) continue; - if (t == m_type_node || t.IsSubclassOf(m_type_node)) { - if (m_dic_type.ContainsKey(t.GUID.ToString())) continue; - m_dic_type.Add(t.GUID.ToString(), t); - bFound = true; - } - } - return bFound; - } - /// - /// 获取当前编辑器中已加载的Node类型 - /// - /// 类型集合 - public Type[] GetTypes() { - return m_dic_type.Values.ToArray(); - } - /// - /// 从文件中加载数据 - /// 注意: 此方法并不会清空画布中数据 而是数据叠加 - /// - /// 文件路径 - public void LoadCanvas(string strFileName) { - using (MemoryStream ms = new MemoryStream(File.ReadAllBytes(strFileName))) - this.LoadCanvas(ms); - } - /// - /// 从二进制加载数据 - /// 注意: 此方法并不会清空画布中数据 而是数据叠加 - /// - /// 二进制数据 - public void LoadCanvas(byte[] byData) { - using (MemoryStream ms = new MemoryStream(byData)) - this.LoadCanvas(ms); - } - /// - /// 从数据流中加载数据 - /// 注意: 此方法并不会清空画布中数据 而是数据叠加 - /// - /// 数据流对象 - public void LoadCanvas(Stream s) { - int nLen = 0; - byte[] byLen = new byte[4]; - s.Read(byLen, 0, 4); - if (BitConverter.ToInt32(byLen, 0) != BitConverter.ToInt32(new byte[] { (byte)'S', (byte)'T', (byte)'N', (byte)'D' }, 0)) - throw new InvalidDataException("无法识别的文件类型"); - if (s.ReadByte() != 1) throw new InvalidDataException("无法识别的文件版本号"); - using (GZipStream gs = new GZipStream(s, CompressionMode.Decompress)) { - gs.Read(byLen, 0, 4); - float x = BitConverter.ToSingle(byLen, 0); - gs.Read(byLen, 0, 4); - float y = BitConverter.ToSingle(byLen, 0); - gs.Read(byLen, 0, 4); - float scale = BitConverter.ToSingle(byLen, 0); - gs.Read(byLen, 0, 4); - int nCount = BitConverter.ToInt32(byLen, 0); - Dictionary dic = new Dictionary(); - byte[] byData = null; - for (int i = 0; i < nCount; i++) { - gs.Read(byLen, 0, byLen.Length); - nLen = BitConverter.ToInt32(byLen, 0); - byData = new byte[nLen]; - gs.Read(byData, 0, byData.Length); - STNode node = null; - try { node = this.GetNodeFromData(byData); } catch (Exception ex) { - throw new Exception("加载节点时发生错误可能数据已损坏\r\n" + ex.Message, ex); - } - try { this._Nodes.Add(node); } catch (Exception ex) { - throw new Exception("加载节点出错-" + node.Title, ex); - } - foreach (STNodeOption op in node.InputOptions) dic.Add(dic.Count, op); - foreach (STNodeOption op in node.OutputOptions) dic.Add(dic.Count, op); - } - gs.Read(byLen, 0, 4); - nCount = BitConverter.ToInt32(byLen, 0); - byData = new byte[8]; - for (int i = 0; i < nCount; i++) { - gs.Read(byData, 0, byData.Length); - long id = BitConverter.ToInt64(byData, 0); - long op_out = id >> 32; - long op_in = (int)id; - dic[op_out].ConnectOption(dic[op_in]); - } - this.ScaleCanvas(scale, 0, 0); - this.MoveCanvas(x, y, false, CanvasMoveArgs.All); - } - this.BuildBounds(); - foreach (STNode node in this._Nodes) node.OnEditorLoadCompleted(); - } - - private STNode GetNodeFromData(byte[] byData) { - int nIndex = 0; - string strModel = Encoding.UTF8.GetString(byData, nIndex + 1, byData[nIndex]); - nIndex += byData[nIndex] + 1; - string strGUID = Encoding.UTF8.GetString(byData, nIndex + 1, byData[nIndex]); - nIndex += byData[nIndex] + 1; - - int nLen = 0; - - Dictionary dic = new Dictionary(); - while (nIndex < byData.Length) { - nLen = BitConverter.ToInt32(byData, nIndex); - nIndex += 4; - string strKey = Encoding.UTF8.GetString(byData, nIndex, nLen); - nIndex += nLen; - nLen = BitConverter.ToInt32(byData, nIndex); - nIndex += 4; - byte[] byValue = new byte[nLen]; - Array.Copy(byData, nIndex, byValue, 0, nLen); - nIndex += nLen; - dic.Add(strKey, byValue); - } - if (!m_dic_type.ContainsKey(strGUID)) throw new TypeLoadException("无法找到类型 {" + strGUID + "} 所在程序集 确保程序集{" + strModel + "}已被编辑器正确加载 可通过调用LoadAssembly()加载程序集"); - Type t = m_dic_type[strGUID]; ; - STNode node = (STNode)Activator.CreateInstance(t); - node.OnLoadNode(dic); - return node; - } - /// - /// 在画布中显示提示信息 - /// - /// 要显示的信息 - /// 信息前景色 - /// 信息背景色 - public void ShowAlert(string strText, Color foreColor, Color backColor) { - this.ShowAlert(strText, foreColor, backColor, 1000, AlertLocation.LeftBottom, true); - } - /// - /// 在画布中显示提示信息 - /// - /// 要显示的信息 - /// 信息前景色 - /// 信息背景色 - /// 信息要显示的位置 - public void ShowAlert(string strText, Color foreColor, Color backColor, AlertLocation al) { - this.ShowAlert(strText, foreColor, backColor, 1000, al, true); - } - /// - /// 在画布中显示提示信息 - /// - /// 要显示的信息 - /// 信息前景色 - /// 信息背景色 - /// 信息持续时间 - /// 信息要显示的位置 - /// 是否立即重绘 - public void ShowAlert(string strText, Color foreColor, Color backColor, int nTime, AlertLocation al, bool bRedraw) { - m_str_alert = strText; - m_forecolor_alert = foreColor; - m_backcolor_alert = backColor; - m_time_alert = nTime; - m_dt_alert = DateTime.Now; - m_alpha_alert = 255; - m_al = al; - if (bRedraw) this.Invalidate(); - } - /// - /// 设置画布中活动的节点 - /// - /// 需要被设置为活动的节点 - /// 设置前的活动节点 - public STNode SetActiveNode(STNode node) { - STNode ret = this._ActiveNode; - if (this._ActiveNode != node) { //重置活动选择节点 - if (node != null) { - node.IsSelected = node.IsActive = true; - node.OnGotFocus(new EventArgs()); - } - if (this._ActiveNode != null) { - this._ActiveNode.IsActive = this._ActiveNode.IsSelected = false; - this._ActiveNode.OnLostFocus(new EventArgs()); - } - this._ActiveNode = node; - this.Invalidate(); - this.OnSelectedChanged(new EventArgs()); - } - return ret; - } - /// - /// 向编辑器中添加默认数据类型颜色 - /// - /// 数据类型 - /// 对应颜色 - public void SetTypeColor(Type t, Color clr) { - if (this._TypeColor.ContainsKey(t)) - this._TypeColor[t] = clr; - else - this._TypeColor.Add(t, clr); - } - - #endregion public - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.IO; +using System.Windows.Forms; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Threading; +using System.ComponentModel; +using System.Reflection; +using System.IO.Compression; +/* +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: 2020-12-08 + * modify: 2021-04-12 + * Author: Crystal_lz + * blog: http://st233.com + * Gitee: https://gitee.com/DebugST + * Github: https://github.com/DebugST + */ +namespace ST.Library.UI.NodeEditor +{ + public class STNodeEditor : Control + { + private const UInt32 WM_MOUSEHWHEEL = 0x020E; + protected static readonly Type m_type_node = typeof(STNode); + + #region protected enum,struct -------------------------------------------------------------------------------------- + + protected enum CanvasAction //当前鼠标移动操作表示进行下列哪一个行为 + { + None, //无 + MoveNode, //正在移动 Node + ConnectOption, //正在连接 Option + SelectRectangle, //正在选择矩形区域 + DrawMarkDetails //正在绘制标记信息详情 + } + + protected struct MagnetInfo + { + public bool XMatched; //X轴是否有磁铁匹配上 + public bool YMatched; + public int X; //与X轴那个数字匹配上 + public int Y; + public int OffsetX; //当前节点X位置与匹配上的X的相对偏移 + public int OffsetY; + } + + #endregion + + #region Properties ------------------------------------------------------------------------------------------------------ + + private float _CanvasOffsetX; + /// + /// 获取画布原点相对于控件 X 方向上的偏移位置 + /// + [Browsable(false)] + public float CanvasOffsetX { + get { return _CanvasOffsetX; } + } + + private float _CanvasOffsetY; + /// + /// 获取画布原点相对于控件 Y 方向上的偏移位置 + /// + [Browsable(false)] + public float CanvasOffsetY { + get { return _CanvasOffsetY; } + } + + private PointF _CanvasOffset; + /// + /// 获取画布原点相对于控件偏移位置 + /// + [Browsable(false)] + public PointF CanvasOffset { + get { + _CanvasOffset.X = _CanvasOffsetX; + _CanvasOffset.Y = _CanvasOffsetY; + return _CanvasOffset; + } + } + + private Rectangle _CanvasValidBounds; + /// + /// 获取画布中的有被用到的有效区域 + /// + [Browsable(false)] + public Rectangle CanvasValidBounds { + get { return _CanvasValidBounds; } + } + + private float _CanvasScale = 1; + /// + /// 获取画布的缩放比例 + /// + [Browsable(false)] + public float CanvasScale { + get { return _CanvasScale; } + } + + private float _Curvature = 0.3F; + /// + /// 获取或设置 Option 之间连线的曲度 + /// + [Browsable(false)] + public float Curvature { + get { return _Curvature; } + set { + if (value < 0) value = 0; + if (value > 1) value = 1; + _Curvature = value; + if (m_dic_gp_info.Count != 0) this.BuildLinePath(); + } + } + + private bool _ShowMagnet = true; + /// + /// 获取或设置移动画布中 Node 时候 是否启用磁铁效果 + /// + [Description("获取或设置移动画布中 Node 时候 是否启用磁铁效果"), DefaultValue(true)] + public bool ShowMagnet { + get { return _ShowMagnet; } + set { _ShowMagnet = value; } + } + + private bool _ShowBorder = true; + /// + /// 获取或设置 移动画布中是否显示 Node 边框 + /// + [Description("获取或设置 移动画布中是否显示 Node 边框"), DefaultValue(true)] + public bool ShowBorder { + get { return _ShowBorder; } + set { + _ShowBorder = value; + this.Invalidate(); + } + } + + private bool _ShowGrid = true; + /// + /// 获取或设置画布中是否绘制背景网格线条 + /// + [Description("获取或设置画布中是否绘制背景网格线条"), DefaultValue(true)] + public bool ShowGrid { + get { return _ShowGrid; } + set { + _ShowGrid = value; + this.Invalidate(); + } + } + + private bool _ShowLocation = true; + /// + /// 获取或设置是否在画布边缘显示超出视角的 Node 位置信息 + /// + [Description("获取或设置是否在画布边缘显示超出视角的 Node 位置信息"), DefaultValue(true)] + public bool ShowLocation { + get { return _ShowLocation; } + set { + _ShowLocation = value; + this.Invalidate(); + } + } + + private STNodeCollection _Nodes; + /// + /// 获取画布中 Node 集合 + /// + [Browsable(false)] + public STNodeCollection Nodes { + get { + return _Nodes; + } + } + + private STNode _ActiveNode; + /// + /// 获取当前画布中被选中的活动 Node + /// + [Browsable(false)] + public STNode ActiveNode { + get { return _ActiveNode; } + //set { + // if (value == _ActiveSelectedNode) return; + // if (_ActiveSelectedNode != null) _ActiveSelectedNode.OnLostFocus(EventArgs.Empty); + // _ActiveSelectedNode = value; + // _ActiveSelectedNode.IsActive = true; + // this.Invalidate(); + // this.OnSelectedChanged(EventArgs.Empty); + //} + } + + private STNode _HoverNode; + /// + /// 获取当前画布中鼠标悬停的 Node + /// + [Browsable(false)] + public STNode HoverNode { + get { return _HoverNode; } + } + //========================================color================================ + private Color _GridColor = Color.Black; + /// + /// 获取或设置绘制画布背景时 网格线条颜色 + /// + [Description("获取或设置绘制画布背景时 网格线条颜色"), DefaultValue(typeof(Color), "Black")] + public Color GridColor { + get { return _GridColor; } + set { + _GridColor = value; + this.Invalidate(); + } + } + + private Color _BorderColor = Color.Black; + /// + /// 获取或设置画布中 Node 边框颜色 + /// + [Description("获取或设置画布中 Node 边框颜色"), DefaultValue(typeof(Color), "Black")] + public Color BorderColor { + get { return _BorderColor; } + set { + _BorderColor = value; + if (m_img_border != null) m_img_border.Dispose(); + m_img_border = this.CreateBorderImage(value); + this.Invalidate(); + } + } + + private Color _BorderHoverColor = Color.Gray; + /// + /// 获取或设置画布中悬停 Node 边框颜色 + /// + [Description("获取或设置画布中悬停 Node 边框颜色"), DefaultValue(typeof(Color), "Gray")] + public Color BorderHoverColor { + get { return _BorderHoverColor; } + set { + _BorderHoverColor = value; + if (m_img_border_hover != null) m_img_border_hover.Dispose(); + m_img_border_hover = this.CreateBorderImage(value); + this.Invalidate(); + } + } + + private Color _BorderSelectedColor = Color.Orange; + /// + /// 获取或设置画布中选中 Node 边框颜色 + /// + [Description("获取或设置画布中选中 Node 边框颜色"), DefaultValue(typeof(Color), "Orange")] + public Color BorderSelectedColor { + get { return _BorderSelectedColor; } + set { + _BorderSelectedColor = value; + if (m_img_border_selected != null) m_img_border_selected.Dispose(); + m_img_border_selected = this.CreateBorderImage(value); + this.Invalidate(); + } + } + + private Color _BorderActiveColor = Color.OrangeRed; + /// + /// 获取或设置画布中活动 Node 边框颜色 + /// + [Description("获取或设置画布中活动 Node 边框颜色"), DefaultValue(typeof(Color), "OrangeRed")] + public Color BorderActiveColor { + get { return _BorderActiveColor; } + set { + _BorderActiveColor = value; + if (m_img_border_active != null) m_img_border_active.Dispose(); + m_img_border_active = this.CreateBorderImage(value); + this.Invalidate(); + } + } + + private Color _MarkForeColor = Color.White; + /// + /// 获取或设置画布绘制 Node 标记详情采用的前景色 + /// + [Description("获取或设置画布绘制 Node 标记详情采用的前景色"), DefaultValue(typeof(Color), "White")] + public Color MarkForeColor { + get { return _MarkBackColor; } + set { + _MarkBackColor = value; + this.Invalidate(); + } + } + + private Color _MarkBackColor = Color.FromArgb(180, Color.Black); + /// + /// 获取或设置画布绘制 Node 标记详情采用的背景色 + /// + [Description("获取或设置画布绘制 Node 标记详情采用的背景色")] + public Color MarkBackColor { + get { return _MarkBackColor; } + set { + _MarkBackColor = value; + this.Invalidate(); + } + } + + private Color _MagnetColor = Color.Lime; + /// + /// 获取或设置画布中移动 Node 时候 磁铁标记颜色 + /// + [Description("获取或设置画布中移动 Node 时候 磁铁标记颜色"), DefaultValue(typeof(Color), "Lime")] + public Color MagnetColor { + get { return _MagnetColor; } + set { _MagnetColor = value; } + } + + private Color _SelectedRectangleColor = Color.DodgerBlue; + /// + /// 获取或设置画布中选择矩形区域的颜色 + /// + [Description("获取或设置画布中选择矩形区域的颜色"), DefaultValue(typeof(Color), "DodgerBlue")] + public Color SelectedRectangleColor { + get { return _SelectedRectangleColor; } + set { _SelectedRectangleColor = value; } + } + + private Color _HighLineColor = Color.Cyan; + /// + /// 获取或设置画布中高亮连线的颜色 + /// + [Description("获取或设置画布中高亮连线的颜色"), DefaultValue(typeof(Color), "Cyan")] + public Color HighLineColor { + get { return _HighLineColor; } + set { _HighLineColor = value; } + } + + private Color _LocationForeColor = Color.Red; + /// + /// 获取或设置画布中边缘位置提示区域前景色 + /// + [Description("获取或设置画布中边缘位置提示区域前景色"), DefaultValue(typeof(Color), "Red")] + public Color LocationForeColor { + get { return _LocationForeColor; } + set { + _LocationForeColor = value; + this.Invalidate(); + } + } + + private Color _LocationBackColor = Color.FromArgb(120, Color.Black); + /// + /// 获取或设置画布中边缘位置提示区域背景色 + /// + [Description("获取或设置画布中边缘位置提示区域背景色")] + public Color LocationBackColor { + get { return _LocationBackColor; } + set { + _LocationBackColor = value; + this.Invalidate(); + } + } + + private Color _UnknownTypeColor = Color.Gray; + /// + /// 获取或设置画布中当 Node 中 Option 数据类型无法确定时应当使用的颜色 + /// + [Description("获取或设置画布中当 Node 中 Option 数据类型无法确定时应当使用的颜色"), DefaultValue(typeof(Color), "Gray")] + public Color UnknownTypeColor { + get { return _UnknownTypeColor; } + set { + _UnknownTypeColor = value; + this.Invalidate(); + } + } + + private Dictionary _TypeColor = new Dictionary(); + /// + /// 获取或设置画布中 Node 中 Option 数据类型预设颜色 + /// + [Browsable(false)] + public Dictionary TypeColor { + get { return _TypeColor; } + } + + #endregion + + #region protected properties ---------------------------------------------------------------------------------------- + /// + /// 当前鼠标在控件中的实时位置 + /// + protected Point m_pt_in_control; + /// + /// 当前鼠标在画布中的实时位置 + /// + protected PointF m_pt_in_canvas; + /// + /// 鼠标点击时在控件上的位置 + /// + protected Point m_pt_down_in_control; + /// + /// 鼠标点击时在画布中的位置 + /// + protected PointF m_pt_down_in_canvas; + /// + /// 用于鼠标点击移动画布时候 鼠标点下时候的画布坐标位置 + /// + protected PointF m_pt_canvas_old; + /// + /// 用于保存连线过程中保存点下 Option 的起点坐标 + /// + protected Point m_pt_dot_down; + /// + /// 用于保存连线过程中鼠标点下的起点Option 当MouseUP时候 确定是否连接此节点 + /// + protected STNodeOption m_option_down; + /// + /// 当前鼠标点下的 STNode + /// + protected STNode m_node_down; + /// + /// 当前鼠标是否位于控件中 + /// + protected bool m_mouse_in_control; + + #endregion + + public STNodeEditor() { + 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._Nodes = new STNodeCollection(this); + this.BackColor = Color.FromArgb(255, 34, 34, 34); + this.MinimumSize = new Size(100, 100); + this.Size = new Size(200, 200); + this.AllowDrop = true; + + m_real_canvas_x = this._CanvasOffsetX = 10; + m_real_canvas_y = this._CanvasOffsetY = 10; + } + + #region private fields -------------------------------------------------------------------------------------- + + private DrawingTools m_drawing_tools; + private NodeFindInfo m_find = new NodeFindInfo(); + private MagnetInfo m_mi = new MagnetInfo(); + + private RectangleF m_rect_select = new RectangleF(); + //节点边框预设图案 + private Image m_img_border; + private Image m_img_border_hover; + private Image m_img_border_selected; + private Image m_img_border_active; + //用于鼠标滚动或者触摸板移动画布时候的动画效果 该值为需要移动到的真实坐标地址 查看->MoveCanvasThread() + private float m_real_canvas_x; + private float m_real_canvas_y; + //用于移动节点时候 保存鼠标点下时候选中的节点初始坐标 + private Dictionary m_dic_pt_selected = new Dictionary(); + //用于磁铁效果 移动节点时候 非选择节点的统计出来的需要参与磁铁效果的坐标 查看->BuildMagnetLocation() + private List m_lst_magnet_x = new List(); + private List m_lst_magnet_y = new List(); + //用于磁铁效果 移动节点时候 活动选择节点统计出来需要参与磁铁效果的坐标 查看->CheckMagnet() + private List m_lst_magnet_mx = new List(); + private List m_lst_magnet_my = new List(); + //用于鼠标滚动中计算时间触发间隔 根据间隔不同 画布产生的位移不同 查看->OnMouseWheel(),OnMouseHWheel() + private DateTime m_dt_vw = DateTime.Now; + private DateTime m_dt_hw = DateTime.Now; + //移动鼠标过程中的当前行为 + private CanvasAction m_ca; + //保存已选中的节点 + private HashSet m_hs_node_selected = new HashSet(); + + private bool m_is_process_mouse_event = true; //是否向下(Node or NodeControls)传递鼠标相关事件 如断开连接相关操作不应向下传递 + private bool m_is_buildpath; //用于重绘过程中 判断该次是否要重新建立缓存连线的路径 + private Pen m_p_line = new Pen(Color.Cyan, 2f); //用于绘制已经连接的线条 + private Pen m_p_line_hover = new Pen(Color.Cyan, 4f); //用于绘制鼠标悬停时候的线条 + private GraphicsPath m_gp_hover; //当前鼠标悬停的连线路径 + private StringFormat m_sf = new StringFormat(); //文本格式 用于Mark绘制时候 设置文本格式 + //保存每个连接线条与之对应的节点关系 + private Dictionary m_dic_gp_info = new Dictionary(); + //保存超出视觉区域的 Node 的位置 + private List m_lst_node_out = new List(); + //当前编辑器已加载的 Node 类型 用于从文件或者数据中加载节点使用 + private Dictionary m_dic_type = new Dictionary(); + + private int m_time_alert; + private int m_alpha_alert; + private string m_str_alert; + private Color m_forecolor_alert; + private Color m_backcolor_alert; + private DateTime m_dt_alert; + private Rectangle m_rect_alert; + private AlertLocation m_al; + + #endregion + + #region event ---------------------------------------------------------------------------------------------------- + /// + /// 活动的节点发生变化时候发生 + /// + [Description("活动的节点发生变化时候发生")] + public event EventHandler ActiveChanged; + /// + /// 选择的节点发生变化时候发生 + /// + [Description("选择的节点发生变化时候发生")] + public event EventHandler SelectedChanged; + /// + /// 悬停的节点发生变化时候发生 + /// + [Description("悬停的节点发生变化时候发生")] + public event EventHandler HoverChanged; + /// + /// 当节点被添加时候发生 + /// + [Description("当节点被添加时候发生")] + public event STNodeEditorEventHandler NodeAdded; + /// + /// 当节点被移除时候发生 + /// + [Description("当节点被移除时候发生")] + public event STNodeEditorEventHandler NodeRemoved; + /// + /// 移动画布原点时候发生 + /// + [Description("移动画布原点时候发生")] + public event EventHandler CanvasMoved; + /// + /// 缩放画布时候发生 + /// + [Description("缩放画布时候发生")] + public event EventHandler CanvasScaled; + /// + /// 连接节点选项时候发生 + /// + [Description("连接节点选项时候发生")] + public event STNodeEditorOptionEventHandler OptionConnected; + /// + /// 正在连接节点选项时候发生 + /// + [Description("正在连接节点选项时候发生")] + public event STNodeEditorOptionEventHandler OptionConnecting; + /// + /// 断开节点选项时候发生 + /// + [Description("断开节点选项时候发生")] + public event STNodeEditorOptionEventHandler OptionDisConnected; + /// + /// 正在断开节点选项时候发生 + /// + [Description("正在断开节点选项时候发生")] + public event STNodeEditorOptionEventHandler OptionDisConnecting; + + protected virtual internal void OnSelectedChanged(EventArgs e) { + if (this.SelectedChanged != null) this.SelectedChanged(this, e); + } + protected virtual void OnActiveChanged(EventArgs e) { + if (this.ActiveChanged != null) this.ActiveChanged(this, e); + } + protected virtual void OnHoverChanged(EventArgs e) { + if (this.HoverChanged != null) this.HoverChanged(this, e); + } + protected internal virtual void OnNodeAdded(STNodeEditorEventArgs e) { + if (this.NodeAdded != null) this.NodeAdded(this, e); + } + protected internal virtual void OnNodeRemoved(STNodeEditorEventArgs e) { + if (this.NodeRemoved != null) this.NodeRemoved(this, e); + } + protected virtual void OnCanvasMoved(EventArgs e) { + if (this.CanvasMoved != null) this.CanvasMoved(this, e); + } + protected virtual void OnCanvasScaled(EventArgs e) { + if (this.CanvasScaled != null) this.CanvasScaled(this, e); + } + protected internal virtual void OnOptionConnected(STNodeEditorOptionEventArgs e) { + if (this.OptionConnected != null) this.OptionConnected(this, e); + } + protected internal virtual void OnOptionDisConnected(STNodeEditorOptionEventArgs e) { + if (this.OptionDisConnected != null) this.OptionDisConnected(this, e); + } + protected internal virtual void OnOptionConnecting(STNodeEditorOptionEventArgs e) { + if (this.OptionConnecting != null) this.OptionConnecting(this, e); + } + protected internal virtual void OnOptionDisConnecting(STNodeEditorOptionEventArgs e) { + if (this.OptionDisConnecting != null) this.OptionDisConnecting(this, e); + } + + #endregion event + + #region override ----------------------------------------------------------------------------------------------------- + + protected override void OnCreateControl() { + m_drawing_tools = new DrawingTools() { + Pen = new Pen(Color.Black, 1), + SolidBrush = new SolidBrush(Color.Black) + }; + m_img_border = this.CreateBorderImage(this._BorderColor); + m_img_border_active = this.CreateBorderImage(this._BorderActiveColor); + m_img_border_hover = this.CreateBorderImage(this._BorderHoverColor); + m_img_border_selected = this.CreateBorderImage(this._BorderSelectedColor); + base.OnCreateControl(); + new Thread(this.MoveCanvasThread) { IsBackground = true }.Start(); + new Thread(this.ShowAlertThread) { IsBackground = true }.Start(); + m_sf = new StringFormat(); + m_sf.Alignment = StringAlignment.Near; + m_sf.FormatFlags = StringFormatFlags.NoWrap; + m_sf.SetTabStops(0, new float[] { 40 }); + } + + protected override void WndProc(ref Message m) { + base.WndProc(ref m); + try { + Point pt = new Point(((int)m.LParam) >> 16, (ushort)m.LParam); + pt = this.PointToClient(pt); + if (m.Msg == WM_MOUSEHWHEEL) { //获取水平滚动消息 + MouseButtons mb = MouseButtons.None; + int n = (ushort)m.WParam; + if ((n & 0x0001) == 0x0001) mb |= MouseButtons.Left; + if ((n & 0x0010) == 0x0010) mb |= MouseButtons.Middle; + if ((n & 0x0002) == 0x0002) mb |= MouseButtons.Right; + if ((n & 0x0020) == 0x0020) mb |= MouseButtons.XButton1; + if ((n & 0x0040) == 0x0040) mb |= MouseButtons.XButton2; + this.OnMouseHWheel(new MouseEventArgs(mb, 0, pt.X, pt.Y, ((int)m.WParam) >> 16)); + } + } catch { /*add code*/ } + } + + protected override void OnPaint(PaintEventArgs e) { + base.OnPaint(e); + Graphics g = e.Graphics; + g.Clear(this.BackColor); + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + m_drawing_tools.Graphics = g; + SolidBrush brush = m_drawing_tools.SolidBrush; + + if (this._ShowGrid) this.OnDrawGrid(m_drawing_tools, this.Width, this.Height); + + g.TranslateTransform(this._CanvasOffsetX, this._CanvasOffsetY); //移动坐标系 + g.ScaleTransform(this._CanvasScale, this._CanvasScale); //缩放绘图表面 + + this.OnDrawConnectedLine(m_drawing_tools); + this.OnDrawNode(m_drawing_tools, this.ControlToCanvas(this.ClientRectangle)); + + if (m_ca == CanvasAction.ConnectOption) { //如果正在连线 + m_drawing_tools.Pen.Color = this._HighLineColor; + g.SmoothingMode = SmoothingMode.HighQuality; + if (m_option_down.IsInput) + this.DrawBezier(g, m_drawing_tools.Pen, m_pt_in_canvas, m_pt_dot_down, this._Curvature); + else + this.DrawBezier(g, m_drawing_tools.Pen, m_pt_dot_down, m_pt_in_canvas, this._Curvature); + } + //重置绘图坐标 我认为除了节点以外的其它 修饰相关的绘制不应该在Canvas坐标系中绘制 而应该使用控件的坐标进行绘制 不然会受到缩放比影响 + g.ResetTransform(); + + switch (m_ca) { + case CanvasAction.MoveNode: //移动过程中 绘制对齐参考线 + if (this._ShowMagnet && this._ActiveNode != null) this.OnDrawMagnet(m_drawing_tools, m_mi); + break; + case CanvasAction.SelectRectangle: //绘制矩形选取 + this.OnDrawSelectedRectangle(m_drawing_tools, this.CanvasToControl(m_rect_select)); + break; + case CanvasAction.DrawMarkDetails: //绘制标记信息详情 + if (!string.IsNullOrEmpty(m_find.Mark)) this.OnDrawMark(m_drawing_tools); + break; + } + + if (this._ShowLocation) this.OnDrawNodeOutLocation(m_drawing_tools, this.Size, m_lst_node_out); + this.OnDrawAlert(g); + } + + protected override void OnMouseDown(MouseEventArgs e) { + base.OnMouseDown(e); + this.Focus(); + m_ca = CanvasAction.None; + m_mi.XMatched = m_mi.YMatched = false; + m_pt_down_in_control = e.Location; + m_pt_down_in_canvas.X = ((e.X - this._CanvasOffsetX) / this._CanvasScale); + m_pt_down_in_canvas.Y = ((e.Y - this._CanvasOffsetY) / this._CanvasScale); + m_pt_canvas_old.X = this._CanvasOffsetX; + m_pt_canvas_old.Y = this._CanvasOffsetY; + + if (m_gp_hover != null && e.Button == MouseButtons.Right) { //断开连接 + this.DisConnectionHover(); + m_is_process_mouse_event = false; //终止MouseClick与MouseUp向下传递 + return; + } + + NodeFindInfo nfi = this.FindNodeFromPoint(m_pt_down_in_canvas); + if (!string.IsNullOrEmpty(nfi.Mark)) { //如果点下的是标记信息 + m_ca = CanvasAction.DrawMarkDetails; + this.Invalidate(); + return; + } + + if (nfi.NodeOption != null) { //如果点下的Option的连接点 + this.StartConnect(nfi.NodeOption); + return; + } + + if (nfi.Node != null) { + nfi.Node.OnMouseDown(new MouseEventArgs(e.Button, e.Clicks, (int)m_pt_down_in_canvas.X - nfi.Node.Left, (int)m_pt_down_in_canvas.Y - nfi.Node.Top, e.Delta)); + bool bCtrlDown = (Control.ModifierKeys & Keys.Control) == Keys.Control; + if (bCtrlDown) { + if (nfi.Node.IsSelected) { + if (nfi.Node == this._ActiveNode) { + this.SetActiveNode(null); + } + } else { + nfi.Node.SetSelected(true, true); + } + return; + } else if (!nfi.Node.IsSelected) { + foreach (var n in m_hs_node_selected.ToArray()) n.SetSelected(false, false); + } + nfi.Node.SetSelected(true, false); //添加到已选择节点 + this.SetActiveNode(nfi.Node); + if (this.PointInRectangle(nfi.Node.TitleRectangle, m_pt_down_in_canvas.X, m_pt_down_in_canvas.Y)) { + if (e.Button == MouseButtons.Right) { + if (nfi.Node.ContextMenuStrip != null) { + nfi.Node.ContextMenuStrip.Show(this.PointToScreen(e.Location)); + } + } else { + m_dic_pt_selected.Clear(); + lock (m_hs_node_selected) { + foreach (STNode n in m_hs_node_selected) //记录已选择节点位置 如果需要移动已选中节点时候 将会有用 + m_dic_pt_selected.Add(n, n.Location); + } + m_ca = CanvasAction.MoveNode; //如果点下的是节点的标题 则可以移动该节点 + if (this._ShowMagnet && this._ActiveNode != null) this.BuildMagnetLocation(); //建立磁铁需要的坐标 如果需要移动已选中节点时候 将会有用 + } + } else + m_node_down = nfi.Node; + } else { + this.SetActiveNode(null); + foreach (var n in m_hs_node_selected.ToArray()) n.SetSelected(false, false);//没有点下任何东西 清空已经选择节点 + m_ca = CanvasAction.SelectRectangle; //进入矩形区域选择模式 + m_rect_select.Width = m_rect_select.Height = 0; + m_node_down = null; + } + //this.SetActiveNode(nfi.Node); + } + + protected override void OnMouseMove(MouseEventArgs e) { + base.OnMouseMove(e); + m_pt_in_control = e.Location; + m_pt_in_canvas.X = ((e.X - this._CanvasOffsetX) / this._CanvasScale); + m_pt_in_canvas.Y = ((e.Y - this._CanvasOffsetY) / this._CanvasScale); + + if (m_node_down != null) { + m_node_down.OnMouseMove(new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_in_canvas.X - m_node_down.Left, + (int)m_pt_in_canvas.Y - m_node_down.Top, e.Delta)); + return; + } + + if (e.Button == MouseButtons.Middle) { //鼠标中键移动画布 + this._CanvasOffsetX = m_real_canvas_x = m_pt_canvas_old.X + (e.X - m_pt_down_in_control.X); + this._CanvasOffsetY = m_real_canvas_y = m_pt_canvas_old.Y + (e.Y - m_pt_down_in_control.Y); + this.Invalidate(); + return; + } + if (e.Button == MouseButtons.Left) { //如果鼠标左键点下 判断行为 + m_gp_hover = null; + switch (m_ca) { + case CanvasAction.MoveNode: this.MoveNode(e.Location); return; //当前移动节点 + case CanvasAction.ConnectOption: this.Invalidate(); return; //当前正在连线 + case CanvasAction.SelectRectangle: //当前正在选取 + m_rect_select.X = m_pt_down_in_canvas.X < m_pt_in_canvas.X ? m_pt_down_in_canvas.X : m_pt_in_canvas.X; + m_rect_select.Y = m_pt_down_in_canvas.Y < m_pt_in_canvas.Y ? m_pt_down_in_canvas.Y : m_pt_in_canvas.Y; + m_rect_select.Width = Math.Abs(m_pt_in_canvas.X - m_pt_down_in_canvas.X); + m_rect_select.Height = Math.Abs(m_pt_in_canvas.Y - m_pt_down_in_canvas.Y); + foreach (STNode n in this._Nodes) { + n.SetSelected(m_rect_select.IntersectsWith(n.Rectangle), false); + } + this.Invalidate(); + return; + } + } + //若不存在行为 则判断鼠标下方是否存在其他对象 + NodeFindInfo nfi = this.FindNodeFromPoint(m_pt_in_canvas); + bool bRedraw = false; + if (this._HoverNode != nfi.Node) { //鼠标悬停到Node上 + if (nfi.Node != null) nfi.Node.OnMouseEnter(EventArgs.Empty); + if (this._HoverNode != null) + this._HoverNode.OnMouseLeave(new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_in_canvas.X - this._HoverNode.Left, + (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta)); + this._HoverNode = nfi.Node; + this.OnHoverChanged(EventArgs.Empty); + bRedraw = true; + } + if (this._HoverNode != null) { + this._HoverNode.OnMouseMove(new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_in_canvas.X - this._HoverNode.Left, + (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta)); + m_gp_hover = null; + } else { + GraphicsPath gp = null; + foreach (var v in m_dic_gp_info) { //判断鼠标是否悬停到连线路径上 + if (v.Key.IsOutlineVisible(m_pt_in_canvas, m_p_line_hover)) { + gp = v.Key; + break; + } + } + if (m_gp_hover != gp) { + m_gp_hover = gp; + bRedraw = true; + } + } + if (bRedraw) this.Invalidate(); + } + + protected override void OnMouseUp(MouseEventArgs e) { + base.OnMouseUp(e); + var nfi = this.FindNodeFromPoint(m_pt_in_canvas); + switch (m_ca) { //鼠标抬起时候 判断行为 + case CanvasAction.MoveNode: //若正在移动Node 则重新记录当前位置 + foreach (STNode n in m_dic_pt_selected.Keys.ToList()) m_dic_pt_selected[n] = n.Location; + break; + case CanvasAction.ConnectOption: //若正在连线 则结束连接 + if (e.Location == m_pt_down_in_control) break; + if (nfi.NodeOption != null) { + if (m_option_down.IsInput) + nfi.NodeOption.ConnectOption(m_option_down); + else + m_option_down.ConnectOption(nfi.NodeOption); + } + break; + } + if (m_is_process_mouse_event && this._ActiveNode != null) { + var mea = new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_in_canvas.X - this._ActiveNode.Left, + (int)m_pt_in_canvas.Y - this._ActiveNode.Top, e.Delta); + this._ActiveNode.OnMouseUp(mea); + m_node_down = null; + } + m_is_process_mouse_event = true; //当前为断开连接操作不进行事件传递 下次将接受事件 + m_ca = CanvasAction.None; + this.Invalidate(); + } + + protected override void OnMouseEnter(EventArgs e) { + base.OnMouseEnter(e); + m_mouse_in_control = true; + } + + protected override void OnMouseLeave(EventArgs e) { + base.OnMouseLeave(e); + m_mouse_in_control = false; + if (this._HoverNode != null) this._HoverNode.OnMouseLeave(e); + this._HoverNode = null; + this.Invalidate(); + } + + protected override void OnMouseWheel(MouseEventArgs e) { + base.OnMouseWheel(e); + if ((Control.ModifierKeys & Keys.Control) == Keys.Control) { + float f = this._CanvasScale + (e.Delta < 0 ? -0.1f : 0.1f); + this.ScaleCanvas(f, this.Width / 2, this.Height / 2); + } else { + if (!m_mouse_in_control) return; + var nfi = this.FindNodeFromPoint(m_pt_in_canvas); + if (this._HoverNode != null) { + this._HoverNode.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_in_canvas.X - this._HoverNode.Left, + (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta)); + return; + } + int t = (int)DateTime.Now.Subtract(m_dt_vw).TotalMilliseconds; + if (t <= 30) t = 40; + else if (t <= 100) t = 20; + else if (t <= 150) t = 10; + else if (t <= 300) t = 4; + else t = 2; + this.MoveCanvas(this._CanvasOffsetX, m_real_canvas_y + (e.Delta < 0 ? -t : t), true, CanvasMoveArgs.Top);//process mouse mid + m_dt_vw = DateTime.Now; + } + } + + protected virtual void OnMouseHWheel(MouseEventArgs e) { + if ((Control.ModifierKeys & Keys.Control) == Keys.Control) return; + if (!m_mouse_in_control) return; + if (this._HoverNode != null) { + this._HoverNode.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_in_canvas.X - this._HoverNode.Left, + (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta)); + return; + } + int t = (int)DateTime.Now.Subtract(m_dt_hw).TotalMilliseconds; + if (t <= 30) t = 40; + else if (t <= 100) t = 20; + else if (t <= 150) t = 10; + else if (t <= 300) t = 4; + else t = 2; + this.MoveCanvas(m_real_canvas_x + (e.Delta > 0 ? -t : t), this._CanvasOffsetY, true, CanvasMoveArgs.Left); + m_dt_hw = DateTime.Now; + } + //===========================for node other event================================== + protected override void OnMouseClick(MouseEventArgs e) { + base.OnMouseClick(e); + if (this._ActiveNode != null && m_is_process_mouse_event) { + if (!this.PointInRectangle(this._ActiveNode.Rectangle, m_pt_in_canvas.X, m_pt_in_canvas.Y)) return; + this._ActiveNode.OnMouseClick(new MouseEventArgs(e.Button, e.Clicks, + (int)m_pt_down_in_canvas.X - this._ActiveNode.Left, + (int)m_pt_down_in_canvas.Y - this._ActiveNode.Top, e.Delta)); + } + } + + protected override void OnKeyDown(KeyEventArgs e) { + base.OnKeyDown(e); + if (this._ActiveNode != null) this._ActiveNode.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyEventArgs e) { + base.OnKeyUp(e); + if (this._ActiveNode != null) this._ActiveNode.OnKeyUp(e); + m_node_down = null; + } + + protected override void OnKeyPress(KeyPressEventArgs e) { + base.OnKeyPress(e); + if (this._ActiveNode != null) this._ActiveNode.OnKeyPress(e); + } + + #endregion + + protected override void OnDragEnter(DragEventArgs drgevent) { + base.OnDragEnter(drgevent); + if (this.DesignMode) return; + if (drgevent.Data.GetDataPresent("STNodeType")) + drgevent.Effect = DragDropEffects.Copy; + else + drgevent.Effect = DragDropEffects.None; + + } + + protected override void OnDragDrop(DragEventArgs drgevent) { + base.OnDragDrop(drgevent); + if (this.DesignMode) return; + if (drgevent.Data.GetDataPresent("STNodeType")) { + object data = drgevent.Data.GetData("STNodeType"); + if (!(data is Type)) return; + var t = (Type)data; + if (!t.IsSubclassOf(typeof(STNode))) return; + STNode node = (STNode)Activator.CreateInstance((t)); + Point pt = new Point(drgevent.X, drgevent.Y); + pt = this.PointToClient(pt); + pt = this.ControlToCanvas(pt); + node.Left = pt.X; node.Top = pt.Y; + this.Nodes.Add(node); + } + } + + #region protected ---------------------------------------------------------------------------------------------------- + /// + /// 当绘制背景网格线时候发生 + /// + /// 绘制工具 + /// 需要绘制宽度 + /// 需要绘制高度 + protected virtual void OnDrawGrid(DrawingTools dt, int nWidth, int nHeight) { + Graphics g = dt.Graphics; + using (Pen p_2 = new Pen(Color.FromArgb(65, this._GridColor))) { + using (Pen p_1 = new Pen(Color.FromArgb(30, this._GridColor))) { + float nIncrement = (20 * this._CanvasScale);             //网格间的间隔 根据比例绘制 + int n = 5 - (int)(this._CanvasOffsetX / nIncrement); + for (float f = this._CanvasOffsetX % nIncrement; f < nWidth; f += nIncrement) + g.DrawLine((n++ % 5 == 0 ? p_2 : p_1), f, 0, f, nHeight); + n = 5 - (int)(this._CanvasOffsetY / nIncrement); + for (float f = this._CanvasOffsetY % nIncrement; f < nHeight; f += nIncrement) + g.DrawLine((n++ % 5 == 0 ? p_2 : p_1), 0, f, nWidth, f); + //原点两天线 + p_1.Color = Color.FromArgb(this._Nodes.Count == 0 ? 255 : 120, this._GridColor); + g.DrawLine(p_1, this._CanvasOffsetX, 0, this._CanvasOffsetX, nHeight); + g.DrawLine(p_1, 0, this._CanvasOffsetY, nWidth, this._CanvasOffsetY); + } + } + } + /// + /// 当绘制 Node 时候发生 + /// + /// 绘制工具 + /// 可视画布区域大小 + protected virtual void OnDrawNode(DrawingTools dt, Rectangle rect) { + m_lst_node_out.Clear(); //清空超出视觉区域的 Node 的坐标 + foreach (STNode n in this._Nodes) { + if (this._ShowBorder) this.OnDrawNodeBorder(dt, n); + n.OnDrawNode(dt); //调用 Node 进行自身绘制主体部分 + if (!string.IsNullOrEmpty(n.Mark)) n.OnDrawMark(dt); //调用 Node 进行自身绘制 Mark 区域 + if (!rect.IntersectsWith(n.Rectangle)) { + m_lst_node_out.Add(n.Location); //判断此 Node 是否超出视觉区域 + } + } + } + /// + /// 当绘制 Node 边框时候发生 + /// + /// 绘制工具 + /// 目标node + protected virtual void OnDrawNodeBorder(DrawingTools dt, STNode node) { + Image img_border = null; + if (this._ActiveNode == node) img_border = m_img_border_active; + else if (node.IsSelected) img_border = m_img_border_selected; + else if (this._HoverNode == node) img_border = m_img_border_hover; + else img_border = m_img_border; + this.RenderBorder(dt.Graphics, node.Rectangle, img_border); + if (!string.IsNullOrEmpty(node.Mark)) this.RenderBorder(dt.Graphics, node.MarkRectangle, img_border); + } + /// + /// 当绘制已连接路径时候发生 + /// + /// 绘制工具 + protected virtual void OnDrawConnectedLine(DrawingTools dt) { + Graphics g = dt.Graphics; + g.SmoothingMode = SmoothingMode.HighQuality; + m_p_line_hover.Color = Color.FromArgb(50, 0, 0, 0); + var t = typeof(object); + foreach (STNode n in this._Nodes) { + foreach (STNodeOption op in n.OutputOptions) { + if (op == STNodeOption.Empty) continue; + if (op.DotColor != Color.Transparent) //确定线条颜色 + m_p_line.Color = op.DotColor; + else { + if (op.DataType == t) + m_p_line.Color = this._UnknownTypeColor; + else + m_p_line.Color = this._TypeColor.ContainsKey(op.DataType) ? this._TypeColor[op.DataType] : this._UnknownTypeColor;//value can not be null + } + foreach (var v in op.ConnectedOption) { + this.DrawBezier(g, m_p_line_hover, op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2, + v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature); + this.DrawBezier(g, m_p_line, op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2, + v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature); + if (m_is_buildpath) { //如果当前绘制需要重新建立已连接的路径缓存 + GraphicsPath gp = this.CreateBezierPath(op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2, + v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature); + m_dic_gp_info.Add(gp, new ConnectionInfo() { Output = op, Input = v }); + } + } + } + } + m_p_line_hover.Color = this._HighLineColor; + if (m_gp_hover != null) { //如果当前有被悬停的连接路劲 则高亮绘制 + g.DrawPath(m_p_line_hover, m_gp_hover); + } + m_is_buildpath = false; //重置标志 下次绘制时候 不再重新建立路径缓存 + } + /// + /// 当绘制 Mark 详情信息时候发生 + /// + /// 绘制工具 + protected virtual void OnDrawMark(DrawingTools dt) { + Graphics g = dt.Graphics; + SizeF sz = g.MeasureString(m_find.Mark, this.Font); //确认文字需要的大小 + Rectangle rect = new Rectangle(m_pt_in_control.X + 15, + m_pt_in_control.Y + 10, + (int)sz.Width + 6, + 4 + (this.Font.Height + 4) * m_find.MarkLines.Length); //sz.Height并没有考虑文字的行距 所以这里高度自己计算 + + if (rect.Right > this.Width) rect.X = this.Width - rect.Width; + if (rect.Bottom > this.Height) rect.Y = this.Height - rect.Height; + if (rect.X < 0) rect.X = 0; + if (rect.Y < 0) rect.Y = 0; + + dt.SolidBrush.Color = this._MarkBackColor; + g.SmoothingMode = SmoothingMode.None; + g.FillRectangle(dt.SolidBrush, rect); //绘制背景区域 + rect.Width--; rect.Height--; + dt.Pen.Color = Color.FromArgb(255, this._MarkBackColor); + g.DrawRectangle(dt.Pen, rect); + dt.SolidBrush.Color = this._MarkForeColor; + + m_sf.LineAlignment = StringAlignment.Center; + //g.SmoothingMode = SmoothingMode.HighQuality; + rect.X += 2; rect.Width -= 3; + rect.Height = this.Font.Height + 4; + int nY = rect.Y + 2; + for (int i = 0; i < m_find.MarkLines.Length; i++) { //绘制文字 + rect.Y = nY + i * (this.Font.Height + 4); + g.DrawString(m_find.MarkLines[i], this.Font, dt.SolidBrush, rect, m_sf); + } + } + /// + /// 当移动 Node 时候 需要显示对齐参考线时候发生 + /// + /// 绘制工具 + /// 匹配的磁铁信息 + protected virtual void OnDrawMagnet(DrawingTools dt, MagnetInfo mi) { + if (this._ActiveNode == null) return; + Graphics g = dt.Graphics; + Pen pen = m_drawing_tools.Pen; + SolidBrush brush = dt.SolidBrush; + pen.Color = this._MagnetColor; + brush.Color = Color.FromArgb(this._MagnetColor.A / 3, this._MagnetColor); + g.SmoothingMode = SmoothingMode.None; + int nL = this._ActiveNode.Left, nMX = this._ActiveNode.Left + this._ActiveNode.Width / 2, nR = this._ActiveNode.Right; + int nT = this._ActiveNode.Top, nMY = this._ActiveNode.Top + this._ActiveNode.Height / 2, nB = this._ActiveNode.Bottom; + if (mi.XMatched) g.DrawLine(pen, this.CanvasToControl(mi.X, true), 0, this.CanvasToControl(mi.X, true), this.Height); + if (mi.YMatched) g.DrawLine(pen, 0, this.CanvasToControl(mi.Y, false), this.Width, this.CanvasToControl(mi.Y, false)); + g.TranslateTransform(this._CanvasOffsetX, this._CanvasOffsetY); //移动坐标系 + g.ScaleTransform(this._CanvasScale, this._CanvasScale); //缩放绘图表面 + if (mi.XMatched) { + //g.DrawLine(pen, this.CanvasToControl(mi.X, true), 0, this.CanvasToControl(mi.X, true), this.Height); + foreach (STNode n in this._Nodes) { + if (n.Left == mi.X || n.Right == mi.X || n.Left + n.Width / 2 == mi.X) { + //g.DrawRectangle(pen, n.Left, n.Top, n.Width - 1, n.Height - 1); + g.FillRectangle(brush, n.Rectangle); + } + } + } + if (mi.YMatched) { + //g.DrawLine(pen, 0, this.CanvasToControl(mi.Y, false), this.Width, this.CanvasToControl(mi.Y, false)); + foreach (STNode n in this._Nodes) { + if (n.Top == mi.Y || n.Bottom == mi.Y || n.Top + n.Height / 2 == mi.Y) { + //g.DrawRectangle(pen, n.Left, n.Top, n.Width - 1, n.Height - 1); + g.FillRectangle(brush, n.Rectangle); + } + } + } + g.ResetTransform(); + } + /// + /// 绘制选择的矩形区域 + /// + /// 绘制工具 + /// 位于控件上的矩形区域 + protected virtual void OnDrawSelectedRectangle(DrawingTools dt, RectangleF rectf) { + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + dt.Pen.Color = this._SelectedRectangleColor; + g.DrawRectangle(dt.Pen, rectf.Left, rectf.Y, rectf.Width, rectf.Height); + brush.Color = Color.FromArgb(this._SelectedRectangleColor.A / 3, this._SelectedRectangleColor); + g.FillRectangle(brush, this.CanvasToControl(m_rect_select)); + } + /// + /// 绘制超出视觉区域的 Node 位置提示信息 + /// + /// 绘制工具 + /// 提示框边距 + /// 超出视觉区域的 Node 位置信息 + protected virtual void OnDrawNodeOutLocation(DrawingTools dt, Size sz, List lstPts) { + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + brush.Color = this._LocationBackColor; + g.SmoothingMode = SmoothingMode.None; + if (lstPts.Count == this._Nodes.Count && this._Nodes.Count != 0) { //如果超出个数和集合个数一样多 则全部超出 绘制外切矩形 + g.FillRectangle(brush, this.CanvasToControl(this._CanvasValidBounds)); + } + g.FillRectangle(brush, 0, 0, 4, sz.Height); //绘制四边背景 + g.FillRectangle(brush, sz.Width - 4, 0, 4, sz.Height); + g.FillRectangle(brush, 4, 0, sz.Width - 8, 4); + g.FillRectangle(brush, 4, sz.Height - 4, sz.Width - 8, 4); + brush.Color = this._LocationForeColor; + foreach (var v in lstPts) { //绘制点 + var pt = this.CanvasToControl(v); + if (pt.X < 0) pt.X = 0; + if (pt.Y < 0) pt.Y = 0; + if (pt.X > sz.Width) pt.X = sz.Width - 4; + if (pt.Y > sz.Height) pt.Y = sz.Height - 4; + g.FillRectangle(brush, pt.X, pt.Y, 4, 4); + } + } + /// + /// 绘制提示信息 + /// + /// 绘制工具 + /// 需要绘制区域 + /// 需要绘制文本 + /// 信息前景色 + /// 信息背景色 + /// 信息位置 + protected virtual void OnDrawAlert(DrawingTools dt, Rectangle rect, string strText, Color foreColor, Color backColor, AlertLocation al) { + if (m_alpha_alert == 0) return; + Graphics g = dt.Graphics; + SolidBrush brush = dt.SolidBrush; + + g.SmoothingMode = SmoothingMode.None; + brush.Color = backColor; + dt.Pen.Color = brush.Color; + g.FillRectangle(brush, rect); + g.DrawRectangle(dt.Pen, rect.Left, rect.Top, rect.Width - 1, rect.Height - 1); + + brush.Color = foreColor; + m_sf.Alignment = StringAlignment.Center; + m_sf.LineAlignment = StringAlignment.Center; + g.SmoothingMode = SmoothingMode.HighQuality; + g.DrawString(strText, this.Font, brush, rect, m_sf); + } + /// + /// 获取提示信息需要绘制的矩形区域 + /// + /// 绘图表面 + /// 需要绘制文本 + /// 信息位置 + /// 矩形区域 + protected virtual Rectangle GetAlertRectangle(Graphics g, string strText, AlertLocation al) { + SizeF szf = g.MeasureString(m_str_alert, this.Font); + Size sz = new Size((int)Math.Round(szf.Width + 10), (int)Math.Round(szf.Height + 4)); + Rectangle rect = new Rectangle(4, this.Height - sz.Height - 4, sz.Width, sz.Height); + + switch (al) { + case AlertLocation.Left: + rect.Y = (this.Height - sz.Height) >> 1; + break; + case AlertLocation.Top: + rect.Y = 4; + rect.X = (this.Width - sz.Width) >> 1; + break; + case AlertLocation.Right: + rect.X = this.Width - sz.Width - 4; + rect.Y = (this.Height - sz.Height) >> 1; + break; + case AlertLocation.Bottom: + rect.X = (this.Width - sz.Width) >> 1; + break; + case AlertLocation.Center: + rect.X = (this.Width - sz.Width) >> 1; + rect.Y = (this.Height - sz.Height) >> 1; + break; + case AlertLocation.LeftTop: + rect.X = rect.Y = 4; + break; + case AlertLocation.RightTop: + rect.Y = 4; + rect.X = this.Width - sz.Width - 4; + break; + case AlertLocation.RightBottom: + rect.X = this.Width - sz.Width - 4; + break; + } + return rect; + } + + #endregion protected + + #region internal + + internal void BuildLinePath() { + foreach (var v in m_dic_gp_info) v.Key.Dispose(); + m_dic_gp_info.Clear(); + m_is_buildpath = true; + this.Invalidate(); + } + + internal void OnDrawAlert(Graphics g) { + m_rect_alert = this.GetAlertRectangle(g, m_str_alert, m_al); + Color clr_fore = Color.FromArgb((int)((float)m_alpha_alert / 255 * m_forecolor_alert.A), m_forecolor_alert); + Color clr_back = Color.FromArgb((int)((float)m_alpha_alert / 255 * m_backcolor_alert.A), m_backcolor_alert); + this.OnDrawAlert(m_drawing_tools, m_rect_alert, m_str_alert, clr_fore, clr_back, m_al); + } + + internal void InternalAddSelectedNode(STNode node) { + node.IsSelected = true; + lock (m_hs_node_selected) m_hs_node_selected.Add(node); + } + + internal void InternalRemoveSelectedNode(STNode node) { + node.IsSelected = false; + lock (m_hs_node_selected) m_hs_node_selected.Remove(node); + } + + #endregion internal + + #region private ----------------------------------------------------------------------------------------------------- + + private void MoveCanvasThread() { + bool bRedraw; + while (true) { + bRedraw = false; + if (m_real_canvas_x != this._CanvasOffsetX) { + float nx = m_real_canvas_x - this._CanvasOffsetX; + float n = Math.Abs(nx) / 10; + float nTemp = Math.Abs(nx); + if (nTemp <= 4) n = 1; + else if (nTemp <= 12) n = 2; + else if (nTemp <= 30) n = 3; + if (nTemp < 1) this._CanvasOffsetX = m_real_canvas_x; + else + this._CanvasOffsetX += nx > 0 ? n : -n; + bRedraw = true; + } + if (m_real_canvas_y != this._CanvasOffsetY) { + float ny = m_real_canvas_y - this._CanvasOffsetY; + float n = Math.Abs(ny) / 10; + float nTemp = Math.Abs(ny); + if (nTemp <= 4) n = 1; + else if (nTemp <= 12) n = 2; + else if (nTemp <= 30) n = 3; + if (nTemp < 1) + this._CanvasOffsetY = m_real_canvas_y; + else + this._CanvasOffsetY += ny > 0 ? n : -n; + bRedraw = true; + } + if (bRedraw) { + m_pt_canvas_old.X = this._CanvasOffsetX; + m_pt_canvas_old.Y = this._CanvasOffsetY; + this.Invalidate(); + Thread.Sleep(30); + } else { + Thread.Sleep(100); + } + } + } + + private void ShowAlertThread() { + while (true) { + int nTime = m_time_alert - (int)DateTime.Now.Subtract(m_dt_alert).TotalMilliseconds; + if (nTime > 0) { + Thread.Sleep(nTime); + continue; + } + if (nTime < -1000) { + if (m_alpha_alert != 0) { + m_alpha_alert = 0; + this.Invalidate(); + } + Thread.Sleep(100); + } else { + m_alpha_alert = (int)(255 - (-nTime / 1000F) * 255); + this.Invalidate(m_rect_alert); + Thread.Sleep(50); + } + } + } + + private Image CreateBorderImage(Color clr) { + Image img = new Bitmap(12, 12); + using (Graphics g = Graphics.FromImage(img)) { + g.SmoothingMode = SmoothingMode.HighQuality; + using (GraphicsPath gp = new GraphicsPath()) { + gp.AddEllipse(new Rectangle(0, 0, 11, 11)); + using (PathGradientBrush b = new PathGradientBrush(gp)) { + b.CenterColor = Color.FromArgb(200, clr); + b.SurroundColors = new Color[] { Color.FromArgb(10, clr) }; + g.FillPath(b, gp); + } + } + } + return img; + } + + private ConnectionStatus DisConnectionHover() { + if (!m_dic_gp_info.ContainsKey(m_gp_hover)) return ConnectionStatus.DisConnected; + ConnectionInfo ci = m_dic_gp_info[m_gp_hover]; + var ret = ci.Output.DisConnectOption(ci.Input); + //this.OnOptionDisConnected(new STNodeOptionEventArgs(ci.Output, ci.Input, ret)); + if (ret == ConnectionStatus.DisConnected) { + m_dic_gp_info.Remove(m_gp_hover); + m_gp_hover.Dispose(); + m_gp_hover = null; + this.Invalidate(); + } + return ret; + } + + private void StartConnect(STNodeOption op) { + if (op.IsInput) { + m_pt_dot_down.X = op.DotLeft; + m_pt_dot_down.Y = op.DotTop + 5; + } else { + m_pt_dot_down.X = op.DotLeft + op.DotSize; + m_pt_dot_down.Y = op.DotTop + 5; + } + m_ca = CanvasAction.ConnectOption; + m_option_down = op; + } + + private void MoveNode(Point pt) { + int nX = (int)((pt.X - m_pt_down_in_control.X) / this._CanvasScale); + int nY = (int)((pt.Y - m_pt_down_in_control.Y) / this._CanvasScale); + lock (m_hs_node_selected) { + foreach (STNode v in m_hs_node_selected) { + v.Left = m_dic_pt_selected[v].X + nX; + v.Top = m_dic_pt_selected[v].Y + nY; + } + if (this._ShowMagnet) { + MagnetInfo mi = this.CheckMagnet(this._ActiveNode); + if (mi.XMatched) { + foreach (STNode v in m_hs_node_selected) v.Left -= mi.OffsetX; + } + if (mi.YMatched) { + foreach (STNode v in m_hs_node_selected) v.Top -= mi.OffsetY; + } + } + } + this.Invalidate(); + } + + protected internal virtual void BuildBounds() { + if (this._Nodes.Count == 0) { + this._CanvasValidBounds = this.ControlToCanvas(this.DisplayRectangle); + return; + } + int x = int.MaxValue; + int y = int.MaxValue; + int r = int.MinValue; + int b = int.MinValue; + foreach (STNode n in this._Nodes) { + if (x > n.Left) x = n.Left; + if (y > n.Top) y = n.Top; + if (r < n.Right) r = n.Right; + if (b < n.Bottom) b = n.Bottom; + } + this._CanvasValidBounds.X = x - 60; + this._CanvasValidBounds.Y = y - 60; + this._CanvasValidBounds.Width = r - x + 120; + this._CanvasValidBounds.Height = b - y + 120; + } + + private bool PointInRectangle(Rectangle rect, float x, float y) { + if (x < rect.Left) return false; + if (x > rect.Right) return false; + if (y < rect.Top) return false; + if (y > rect.Bottom) return false; + return true; + } + + private void BuildMagnetLocation() { + m_lst_magnet_x.Clear(); + m_lst_magnet_y.Clear(); + foreach (STNode v in this._Nodes) { + if (v.IsSelected) continue; + m_lst_magnet_x.Add(v.Left); + m_lst_magnet_x.Add(v.Left + v.Width / 2); + m_lst_magnet_x.Add(v.Left + v.Width); + m_lst_magnet_y.Add(v.Top); + m_lst_magnet_y.Add(v.Top + v.Height / 2); + m_lst_magnet_y.Add(v.Top + v.Height); + } + } + + private MagnetInfo CheckMagnet(STNode node) { + m_mi.XMatched = m_mi.YMatched = false; + m_lst_magnet_mx.Clear(); + m_lst_magnet_my.Clear(); + m_lst_magnet_mx.Add(node.Left + node.Width / 2); + m_lst_magnet_mx.Add(node.Left); + m_lst_magnet_mx.Add(node.Left + node.Width); + m_lst_magnet_my.Add(node.Top + node.Height / 2); + m_lst_magnet_my.Add(node.Top); + m_lst_magnet_my.Add(node.Top + node.Height); + + bool bFlag = false; + foreach (var mx in m_lst_magnet_mx) { + foreach (var x in m_lst_magnet_x) { + if (Math.Abs(mx - x) <= 5) { + bFlag = true; + m_mi.X = x; + m_mi.OffsetX = mx - x; + m_mi.XMatched = true; + break; + } + } + if (bFlag) break; + } + bFlag = false; + foreach (var my in m_lst_magnet_my) { + foreach (var y in m_lst_magnet_y) { + if (Math.Abs(my - y) <= 5) { + bFlag = true; + m_mi.Y = y; + m_mi.OffsetY = my - y; + m_mi.YMatched = true; + break; + } + } + if (bFlag) break; + } + return m_mi; + } + + private void DrawBezier(Graphics g, Pen p, PointF ptStart, PointF ptEnd, float f) { + this.DrawBezier(g, p, ptStart.X, ptStart.Y, ptEnd.X, ptEnd.Y, f); + } + + private void DrawBezier(Graphics g, Pen p, float x1, float y1, float x2, float y2, float f) { + float n = (Math.Abs(x1 - x2) * f); + if (this._Curvature != 0 && n < 30) n = 30; + g.DrawBezier(p, + x1, y1, + x1 + n, y1, + x2 - n, y2, + x2, y2); + } + + private GraphicsPath CreateBezierPath(float x1, float y1, float x2, float y2, float f) { + GraphicsPath gp = new GraphicsPath(); + float n = (Math.Abs(x1 - x2) * f); + if (this._Curvature != 0 && n < 30) n = 30; + gp.AddBezier( + x1, y1, + x1 + n, y1, + x2 - n, y2, + x2, y2 + ); + return gp; + } + + private void RenderBorder(Graphics g, Rectangle rect, Image img) { + //填充四个角 + g.DrawImage(img, new Rectangle(rect.X - 5, rect.Y - 5, 5, 5), + new Rectangle(0, 0, 5, 5), GraphicsUnit.Pixel); + g.DrawImage(img, new Rectangle(rect.Right, rect.Y - 5, 5, 5), + new Rectangle(img.Width - 5, 0, 5, 5), GraphicsUnit.Pixel); + g.DrawImage(img, new Rectangle(rect.X - 5, rect.Bottom, 5, 5), + new Rectangle(0, img.Height - 5, 5, 5), GraphicsUnit.Pixel); + g.DrawImage(img, new Rectangle(rect.Right, rect.Bottom, 5, 5), + new Rectangle(img.Width - 5, img.Height - 5, 5, 5), GraphicsUnit.Pixel); + //四边 + g.DrawImage(img, new Rectangle(rect.X - 5, rect.Y, 5, rect.Height), + new Rectangle(0, 5, 5, img.Height - 10), GraphicsUnit.Pixel); + g.DrawImage(img, new Rectangle(rect.X, rect.Y - 5, rect.Width, 5), + new Rectangle(5, 0, img.Width - 10, 5), GraphicsUnit.Pixel); + g.DrawImage(img, new Rectangle(rect.Right, rect.Y, 5, rect.Height), + new Rectangle(img.Width - 5, 5, 5, img.Height - 10), GraphicsUnit.Pixel); + g.DrawImage(img, new Rectangle(rect.X, rect.Bottom, rect.Width, 5), + new Rectangle(5, img.Height - 5, img.Width - 10, 5), GraphicsUnit.Pixel); + } + + #endregion private + + #region public -------------------------------------------------------------------------------------------------------- + /// + /// 通过画布坐标进行寻找 + /// + /// 画布中的坐标 + /// 寻找到的数据 + public NodeFindInfo FindNodeFromPoint(PointF pt) { + m_find.Node = null; m_find.NodeOption = null; m_find.Mark = null; + for (int i = this._Nodes.Count - 1; i >= 0; i--) { + if (!string.IsNullOrEmpty(this._Nodes[i].Mark) && this.PointInRectangle(this._Nodes[i].MarkRectangle, pt.X, pt.Y)) { + m_find.Mark = this._Nodes[i].Mark; + m_find.MarkLines = this._Nodes[i].MarkLines; + return m_find; + } + foreach (STNodeOption v in this._Nodes[i].InputOptions) { + if (v == STNodeOption.Empty) continue; + if (this.PointInRectangle(v.DotRectangle, pt.X, pt.Y)) m_find.NodeOption = v; + } + foreach (STNodeOption v in this._Nodes[i].OutputOptions) { + if (v == STNodeOption.Empty) continue; + if (this.PointInRectangle(v.DotRectangle, pt.X, pt.Y)) m_find.NodeOption = v; + } + if (this.PointInRectangle(this._Nodes[i].Rectangle, pt.X, pt.Y)) { + m_find.Node = this._Nodes[i]; + } + if (m_find.NodeOption != null || m_find.Node != null) return m_find; + } + return m_find; + } + /// + /// 获取已经被选择的 Node 集合 + /// + /// Node 集合 + public STNode[] GetSelectedNode() { + return m_hs_node_selected.ToArray(); + } + /// + /// 将画布坐标转换为控件坐标 + /// + /// 参数 + /// 是否为 X 坐标 + /// 转换后的坐标 + public float CanvasToControl(float number, bool isX) { + return (number * this._CanvasScale) + (isX ? this._CanvasOffsetX : this._CanvasOffsetY); + } + /// + /// 将画布坐标转换为控件坐标 + /// + /// 坐标 + /// 转换后的坐标 + public PointF CanvasToControl(PointF pt) { + pt.X = (pt.X * this._CanvasScale) + this._CanvasOffsetX; + pt.Y = (pt.Y * this._CanvasScale) + this._CanvasOffsetY; + //pt.X += this._CanvasOffsetX; + //pt.Y += this._CanvasOffsetY; + return pt; + } + /// + /// 将画布坐标转换为控件坐标 + /// + /// 坐标 + /// 转换后的坐标 + public Point CanvasToControl(Point pt) { + pt.X = (int)(pt.X * this._CanvasScale + this._CanvasOffsetX); + pt.Y = (int)(pt.Y * this._CanvasScale + this._CanvasOffsetY); + //pt.X += (int)this._CanvasOffsetX; + //pt.Y += (int)this._CanvasOffsetY; + return pt; + } + /// + /// 将画布坐标转换为控件坐标 + /// + /// 矩形区域 + /// 转换后的矩形区域 + public Rectangle CanvasToControl(Rectangle rect) { + rect.X = (int)((rect.X * this._CanvasScale) + this._CanvasOffsetX); + rect.Y = (int)((rect.Y * this._CanvasScale) + this._CanvasOffsetY); + rect.Width = (int)(rect.Width * this._CanvasScale); + rect.Height = (int)(rect.Height * this._CanvasScale); + //rect.X += (int)this._CanvasOffsetX; + //rect.Y += (int)this._CanvasOffsetY; + return rect; + } + /// + /// 将画布坐标转换为控件坐标 + /// + /// 矩形区域 + /// 转换后的矩形区域 + public RectangleF CanvasToControl(RectangleF rect) { + rect.X = (rect.X * this._CanvasScale) + this._CanvasOffsetX; + rect.Y = (rect.Y * this._CanvasScale) + this._CanvasOffsetY; + rect.Width = (rect.Width * this._CanvasScale); + rect.Height = (rect.Height * this._CanvasScale); + //rect.X += this._CanvasOffsetX; + //rect.Y += this._CanvasOffsetY; + return rect; + } + /// + /// 将控件坐标转换为画布坐标 + /// + /// 参数 + /// 是否为 X 坐标 + /// 转换后的坐标 + public float ControlToCanvas(float number, bool isX) { + return (number - (isX ? this._CanvasOffsetX : this._CanvasOffsetY)) / this._CanvasScale; + } + /// + /// 将控件坐标转换为画布坐标 + /// + /// 坐标 + /// 转换后的坐标 + public Point ControlToCanvas(Point pt) { + pt.X = (int)((pt.X - this._CanvasOffsetX) / this._CanvasScale); + pt.Y = (int)((pt.Y - this._CanvasOffsetY) / this._CanvasScale); + return pt; + } + /// + /// 将控件坐标转换为画布坐标 + /// + /// 坐标 + /// 转换后的坐标 + public PointF ControlToCanvas(PointF pt) { + pt.X = ((pt.X - this._CanvasOffsetX) / this._CanvasScale); + pt.Y = ((pt.Y - this._CanvasOffsetY) / this._CanvasScale); + return pt; + } + /// + /// 将控件坐标转换为画布坐标 + /// + /// 矩形区域 + /// 转换后的区域 + public Rectangle ControlToCanvas(Rectangle rect) { + rect.X = (int)((rect.X - this._CanvasOffsetX) / this._CanvasScale); + rect.Y = (int)((rect.Y - this._CanvasOffsetY) / this._CanvasScale); + rect.Width = (int)(rect.Width / this._CanvasScale); + rect.Height = (int)(rect.Height / this._CanvasScale); + return rect; + } + /// + /// 将控件坐标转换为画布坐标 + /// + /// 矩形区域 + /// 转换后的区域 + public RectangleF ControlToCanvas(RectangleF rect) { + rect.X = ((rect.X - this._CanvasOffsetX) / this._CanvasScale); + rect.Y = ((rect.Y - this._CanvasOffsetY) / this._CanvasScale); + rect.Width = (rect.Width / this._CanvasScale); + rect.Height = (rect.Height / this._CanvasScale); + return rect; + } + /// + /// 移动画布原点坐标到指定的控件坐标位置 + /// 当不存在 Node 时候 无法移动 + /// + /// X 坐标 + /// Y 坐标 + /// 移动过程中是否启动动画效果 + /// 指定需要修改的坐标参数 + public void MoveCanvas(float x, float y, bool bAnimation, CanvasMoveArgs ma) { + if (this._Nodes.Count == 0) { + m_real_canvas_x = m_real_canvas_y = 10; + return; + } + int l = (int)((this._CanvasValidBounds.Left + 50) * this._CanvasScale); + int t = (int)((this._CanvasValidBounds.Top + 50) * this._CanvasScale); + int r = (int)((this._CanvasValidBounds.Right - 50) * this._CanvasScale); + int b = (int)((this._CanvasValidBounds.Bottom - 50) * this._CanvasScale); + if (r + x < 0) x = -r; + if (this.Width - l < x) x = this.Width - l; + if (b + y < 0) y = -b; + if (this.Height - t < y) y = this.Height - t; + if (bAnimation) { + if ((ma & CanvasMoveArgs.Left) == CanvasMoveArgs.Left) + m_real_canvas_x = x; + if ((ma & CanvasMoveArgs.Top) == CanvasMoveArgs.Top) + m_real_canvas_y = y; + } else { + m_real_canvas_x = this._CanvasOffsetX = x; + m_real_canvas_y = this._CanvasOffsetY = y; + } + this.OnCanvasMoved(EventArgs.Empty); + } + /// + /// 缩放画布 + /// 当不存在 Node 时候 无法缩放 + /// + /// 缩放比例 + /// 缩放中心X位于控件上的坐标 + /// 缩放中心Y位于控件上的坐标 + public void ScaleCanvas(float f, float x, float y) { + if (this._Nodes.Count == 0) { + this._CanvasScale = 1F; + return; + } + if (this._CanvasScale == f) return; + if (f < 0.5) f = 0.5f; else if (f > 3) f = 3; + float x_c = this.ControlToCanvas(x, true); + float y_c = this.ControlToCanvas(y, false); + this._CanvasScale = f; + this._CanvasOffsetX = m_real_canvas_x -= this.CanvasToControl(x_c, true) - x; + this._CanvasOffsetY = m_real_canvas_y -= this.CanvasToControl(y_c, false) - y; + this.OnCanvasScaled(EventArgs.Empty); + this.Invalidate(); + } + /// + /// 获取当前已连接的 Option 对应关系 + /// + /// 连接信息集合 + public ConnectionInfo[] GetConnectionInfo() { + return m_dic_gp_info.Values.ToArray(); + } + /// + /// 判断两个 Node 之间是否存在连接路径 + /// + /// 起始 Node + /// 目标 Node + /// 若存在路径返回true 否则false + public static bool CanFindNodePath(STNode nodeStart, STNode nodeFind) { + HashSet hs = new HashSet(); + return STNodeEditor.CanFindNodePath(nodeStart, nodeFind, hs); + } + private static bool CanFindNodePath(STNode nodeStart, STNode nodeFind, HashSet hs) { + foreach (STNodeOption op_1 in nodeStart.OutputOptions) { + foreach (STNodeOption op_2 in op_1.ConnectedOption) { + if (op_2.Owner == nodeFind) return true; + if (hs.Add(op_2.Owner)) { + if (STNodeEditor.CanFindNodePath(op_2.Owner, nodeFind)) return true; + } + } + } + return false; + } + /// + /// 获取画布中指定矩形区域图像 + /// + /// 画布中指定的矩形区域 + /// 图像 + public Image GetCanvasImage(Rectangle rect) { return this.GetCanvasImage(rect, 1f); } + /// + /// 获取画布中指定矩形区域图像 + /// + /// 画布中指定的矩形区域 + /// 缩放比例 + /// 图像 + public Image GetCanvasImage(Rectangle rect, float fScale) { + if (fScale < 0.5) fScale = 0.5f; else if (fScale > 3) fScale = 3; + Image img = new Bitmap((int)(rect.Width * fScale), (int)(rect.Height * fScale)); + using (Graphics g = Graphics.FromImage(img)) { + g.Clear(this.BackColor); + g.ScaleTransform(fScale, fScale); + m_drawing_tools.Graphics = g; + + if (this._ShowGrid) this.OnDrawGrid(m_drawing_tools, rect.Width, rect.Height); + g.TranslateTransform(-rect.X, -rect.Y); //移动坐标系 + this.OnDrawNode(m_drawing_tools, rect); + this.OnDrawConnectedLine(m_drawing_tools); + + g.ResetTransform(); + + if (this._ShowLocation) this.OnDrawNodeOutLocation(m_drawing_tools, img.Size, m_lst_node_out); + } + return img; + } + /// + /// 保存画布中的类容到文件中 + /// + /// 文件路径 + public void SaveCanvas(string strFileName) { + using (FileStream fs = new FileStream(strFileName, FileMode.Create, FileAccess.Write)) { + this.SaveCanvas(fs); + } + } + /// + /// 保存画布中的类容到数据流 + /// + /// 数据流对象 + public void SaveCanvas(Stream s) { + Dictionary dic = new Dictionary(); + s.Write(new byte[] { (byte)'S', (byte)'T', (byte)'N', (byte)'D' }, 0, 4); //file head + s.WriteByte(1); //ver + using (GZipStream gs = new GZipStream(s, CompressionMode.Compress)) { + gs.Write(BitConverter.GetBytes(this._CanvasOffsetX), 0, 4); + gs.Write(BitConverter.GetBytes(this._CanvasOffsetY), 0, 4); + gs.Write(BitConverter.GetBytes(this._CanvasScale), 0, 4); + gs.Write(BitConverter.GetBytes(this._Nodes.Count), 0, 4); + foreach (STNode node in this._Nodes) { + try { + byte[] byNode = node.GetSaveData(); + gs.Write(BitConverter.GetBytes(byNode.Length), 0, 4); + gs.Write(byNode, 0, byNode.Length); + foreach (STNodeOption op in node.InputOptions) if (!dic.ContainsKey(op)) dic.Add(op, dic.Count); + foreach (STNodeOption op in node.OutputOptions) if (!dic.ContainsKey(op)) dic.Add(op, dic.Count); + } catch (Exception ex) { + throw new Exception("获取节点数据出错-" + node.Title, ex); + } + } + gs.Write(BitConverter.GetBytes(m_dic_gp_info.Count), 0, 4); + foreach (var v in m_dic_gp_info.Values) + gs.Write(BitConverter.GetBytes(((dic[v.Output] << 32) | dic[v.Input])), 0, 8); + } + } + /// + /// 获取画布中内容二进制数据 + /// + /// 二进制数据 + public byte[] GetCanvasData() { + using (MemoryStream ms = new MemoryStream()) { + this.SaveCanvas(ms); + return ms.ToArray(); + } + } + /// + /// 加载程序集 + /// + /// 程序集集合 + /// 存在STNode类型的文件的个数 + public int LoadAssembly(string[] strFiles) { + int nCount = 0; + foreach (var v in strFiles) { + try { + if (this.LoadAssembly(v)) nCount++; + } catch { } + } + return nCount; + } + /// + /// 加载程序集 + /// + /// 指定需要加载的文件 + /// 是否加载成功 + public bool LoadAssembly(string strFile) { + bool bFound = false; + Assembly asm = Assembly.LoadFrom(strFile); + if (asm == null) return false; + foreach (var t in asm.GetTypes()) { + if (t.IsAbstract) continue; + if (t == m_type_node || t.IsSubclassOf(m_type_node)) { + if (m_dic_type.ContainsKey(t.GUID.ToString())) continue; + m_dic_type.Add(t.GUID.ToString(), t); + bFound = true; + } + } + return bFound; + } + /// + /// 获取当前编辑器中已加载的Node类型 + /// + /// 类型集合 + public Type[] GetTypes() { + return m_dic_type.Values.ToArray(); + } + /// + /// 从文件中加载数据 + /// 注意: 此方法并不会清空画布中数据 而是数据叠加 + /// + /// 文件路径 + public void LoadCanvas(string strFileName) { + using (MemoryStream ms = new MemoryStream(File.ReadAllBytes(strFileName))) + this.LoadCanvas(ms); + } + /// + /// 从二进制加载数据 + /// 注意: 此方法并不会清空画布中数据 而是数据叠加 + /// + /// 二进制数据 + public void LoadCanvas(byte[] byData) { + using (MemoryStream ms = new MemoryStream(byData)) + this.LoadCanvas(ms); + } + /// + /// 从数据流中加载数据 + /// 注意: 此方法并不会清空画布中数据 而是数据叠加 + /// + /// 数据流对象 + public void LoadCanvas(Stream s) { + int nLen = 0; + byte[] byLen = new byte[4]; + s.Read(byLen, 0, 4); + if (BitConverter.ToInt32(byLen, 0) != BitConverter.ToInt32(new byte[] { (byte)'S', (byte)'T', (byte)'N', (byte)'D' }, 0)) + throw new InvalidDataException("无法识别的文件类型"); + if (s.ReadByte() != 1) throw new InvalidDataException("无法识别的文件版本号"); + using (GZipStream gs = new GZipStream(s, CompressionMode.Decompress)) { + gs.Read(byLen, 0, 4); + float x = BitConverter.ToSingle(byLen, 0); + gs.Read(byLen, 0, 4); + float y = BitConverter.ToSingle(byLen, 0); + gs.Read(byLen, 0, 4); + float scale = BitConverter.ToSingle(byLen, 0); + gs.Read(byLen, 0, 4); + int nCount = BitConverter.ToInt32(byLen, 0); + Dictionary dic = new Dictionary(); + HashSet hs = new HashSet(); + byte[] byData = null; + for (int i = 0; i < nCount; i++) { + gs.Read(byLen, 0, byLen.Length); + nLen = BitConverter.ToInt32(byLen, 0); + byData = new byte[nLen]; + gs.Read(byData, 0, byData.Length); + STNode node = null; + try { node = this.GetNodeFromData(byData); } catch (Exception ex) { + throw new Exception("加载节点时发生错误可能数据已损坏\r\n" + ex.Message, ex); + } + try { this._Nodes.Add(node); } catch (Exception ex) { + throw new Exception("加载节点出错-" + node.Title, ex); + } + foreach (STNodeOption op in node.InputOptions) if (hs.Add(op)) dic.Add(dic.Count, op); + foreach (STNodeOption op in node.OutputOptions) if (hs.Add(op)) dic.Add(dic.Count, op); + } + gs.Read(byLen, 0, 4); + nCount = BitConverter.ToInt32(byLen, 0); + byData = new byte[8]; + for (int i = 0; i < nCount; i++) { + gs.Read(byData, 0, byData.Length); + long id = BitConverter.ToInt64(byData, 0); + long op_out = id >> 32; + long op_in = (int)id; + dic[op_out].ConnectOption(dic[op_in]); + } + this.ScaleCanvas(scale, 0, 0); + this.MoveCanvas(x, y, false, CanvasMoveArgs.All); + } + this.BuildBounds(); + foreach (STNode node in this._Nodes) node.OnEditorLoadCompleted(); + } + + private STNode GetNodeFromData(byte[] byData) { + int nIndex = 0; + string strModel = Encoding.UTF8.GetString(byData, nIndex + 1, byData[nIndex]); + nIndex += byData[nIndex] + 1; + string strGUID = Encoding.UTF8.GetString(byData, nIndex + 1, byData[nIndex]); + nIndex += byData[nIndex] + 1; + + int nLen = 0; + + Dictionary dic = new Dictionary(); + while (nIndex < byData.Length) { + nLen = BitConverter.ToInt32(byData, nIndex); + nIndex += 4; + string strKey = Encoding.UTF8.GetString(byData, nIndex, nLen); + nIndex += nLen; + nLen = BitConverter.ToInt32(byData, nIndex); + nIndex += 4; + byte[] byValue = new byte[nLen]; + Array.Copy(byData, nIndex, byValue, 0, nLen); + nIndex += nLen; + dic.Add(strKey, byValue); + } + if (!m_dic_type.ContainsKey(strGUID)) throw new TypeLoadException("无法找到类型 {" + strModel.Split('|')[1] + "} 所在程序集 确保程序集 {" + strModel.Split('|')[0] + "} 已被编辑器正确加载 可通过调用LoadAssembly()加载程序集"); + Type t = m_dic_type[strGUID]; ; + STNode node = (STNode)Activator.CreateInstance(t); + node.OnLoadNode(dic); + return node; + } + /// + /// 在画布中显示提示信息 + /// + /// 要显示的信息 + /// 信息前景色 + /// 信息背景色 + public void ShowAlert(string strText, Color foreColor, Color backColor) { + this.ShowAlert(strText, foreColor, backColor, 1000, AlertLocation.RightBottom, true); + } + /// + /// 在画布中显示提示信息 + /// + /// 要显示的信息 + /// 信息前景色 + /// 信息背景色 + /// 信息要显示的位置 + public void ShowAlert(string strText, Color foreColor, Color backColor, AlertLocation al) { + this.ShowAlert(strText, foreColor, backColor, 1000, al, true); + } + /// + /// 在画布中显示提示信息 + /// + /// 要显示的信息 + /// 信息前景色 + /// 信息背景色 + /// 信息持续时间 + /// 信息要显示的位置 + /// 是否立即重绘 + public void ShowAlert(string strText, Color foreColor, Color backColor, int nTime, AlertLocation al, bool bRedraw) { + m_str_alert = strText; + m_forecolor_alert = foreColor; + m_backcolor_alert = backColor; + m_time_alert = nTime; + m_dt_alert = DateTime.Now; + m_alpha_alert = 255; + m_al = al; + if (bRedraw) this.Invalidate(); + } + /// + /// 设置画布中活动的节点 + /// + /// 需要被设置为活动的节点 + /// 设置前的活动节点 + public STNode SetActiveNode(STNode node) { + if (node != null && !this._Nodes.Contains(node)) return this._ActiveNode; + STNode ret = this._ActiveNode; + if (this._ActiveNode != node) { //重置活动选择节点 + if (node != null) { + this._Nodes.MoveToEnd(node); + node.IsActive = true; + node.SetSelected(true, false); + node.OnGotFocus(EventArgs.Empty); + } + if (this._ActiveNode != null) { + this._ActiveNode.IsActive /*= this._ActiveNode.IsSelected*/ = false; + this._ActiveNode.OnLostFocus(EventArgs.Empty); + } + this._ActiveNode = node; + this.Invalidate(); + this.OnActiveChanged(EventArgs.Empty); + //this.OnSelectedChanged(EventArgs.Empty); + } + return ret; + } + /// + /// 向画布中添加一个被选中的节点 + /// + /// 需要被选中的节点 + /// 是否添加成功 + public bool AddSelectedNode(STNode node) { + if (!this._Nodes.Contains(node)) return false; + bool b = !node.IsSelected; + node.IsSelected = true; + lock (m_hs_node_selected) return m_hs_node_selected.Add(node) || b; + } + /// + /// 向画布中移除一个被选中的节点 + /// + /// 需要被移除的节点 + /// 是移除否成功 + public bool RemoveSelectedNode(STNode node) { + if (!this._Nodes.Contains(node)) return false; + bool b = node.IsSelected; + node.IsSelected = false; + lock (m_hs_node_selected) return m_hs_node_selected.Remove(node) || b; + } + /// + /// 向编辑器中添加默认数据类型颜色 + /// + /// 数据类型 + /// 对应颜色 + /// 被设置后的颜色 + public Color SetTypeColor(Type t, Color clr) { + return this.SetTypeColor(t, clr, false); + } + /// + /// 向编辑器中添加默认数据类型颜色 + /// + /// 数据类型 + /// 对应颜色 + /// 若已经存在是否替换颜色 + /// 被设置后的颜色 + public Color SetTypeColor(Type t, Color clr, bool bReplace) { + if (this._TypeColor.ContainsKey(t)) { + if (bReplace) this._TypeColor[t] = clr; + } else { + this._TypeColor.Add(t, clr); + } + return this._TypeColor[t]; + } + + #endregion public + } +} diff --git a/ST.Library.UI/STNodeEditor/STNodeEditorDataType.cs b/ST.Library.UI/NodeEditor/STNodeEditorDataType.cs old mode 100755 new mode 100644 similarity index 95% rename from ST.Library.UI/STNodeEditor/STNodeEditorDataType.cs rename to ST.Library.UI/NodeEditor/STNodeEditorDataType.cs index 5a8cf03..d84fa18 --- a/ST.Library.UI/STNodeEditor/STNodeEditorDataType.cs +++ b/ST.Library.UI/NodeEditor/STNodeEditorDataType.cs @@ -1,198 +1,203 @@ -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 - { - /// - /// 不存在所有者 - /// - [Description("不存在所有者")] - NoOwner, - /// - /// 相同的所有者 - /// - [Description("相同的所有者")] - SameOwner, - /// - /// 均为输入或者输出选项 - /// - [Description("均为输入或者输出选项")] - SameInputOrOutput, - /// - /// 不同的数据类型 - /// - [Description("不同的数据类型")] - ErrorType, - /// - /// 单连接节点 - /// - [Description("单连接节点")] - SingleOption, - /// - /// 出现环形路径 - /// - [Description("出现环形路径")] - Loop, - /// - /// 已存在的连接 - /// - [Description("已存在的连接")] - Exists, - /// - /// 已经连接 - /// - [Description("已经连接")] - Connected, - /// - /// 连接被断开 - /// - [Description("连接被断开")] - DisConnected, - /// - /// 节点被锁定 - /// - [Description("节点被锁定")] - Locked, - /// - /// 操作被拒绝 - /// - [Description("操作被拒绝")] - Reject, - /// - /// 正在被连接 - /// - [Description("正在被连接")] - Connecting, - /// - /// 正在断开连接 - /// - [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; - /// - /// 触发此事件的对应Option - /// - public STNodeOption TargetOption { - get { return _TargetOption; } - } - - private ConnectionStatus _Status; - /// - /// Option之间的连线状态 - /// - public ConnectionStatus Status { - get { return _Status; } - internal set { _Status = value; } - } - - private bool _IsSponsor; - /// - /// 是否为此次行为的发起者 - /// - 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; - /// - /// 主动触发事件的Option - /// - public STNodeOption CurrentOption { - get { return _CurrentOption; } - } - - private bool _Continue = true; - /// - /// 是否继续向下操作 用于Begin(Connecting/DisConnecting)是否继续向后操作 - /// - 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.NodeEditor +{ + public enum ConnectionStatus + { + /// + /// 不存在所有者 + /// + [Description("不存在所有者")] + NoOwner, + /// + /// 相同的所有者 + /// + [Description("相同的所有者")] + SameOwner, + /// + /// 均为输入或者输出选项 + /// + [Description("均为输入或者输出选项")] + SameInputOrOutput, + /// + /// 不同的数据类型 + /// + [Description("不同的数据类型")] + ErrorType, + /// + /// 单连接节点 + /// + [Description("单连接节点")] + SingleOption, + /// + /// 出现环形路径 + /// + [Description("出现环形路径")] + Loop, + /// + /// 已存在的连接 + /// + [Description("已存在的连接")] + Exists, + /// + /// 空白选项 + /// + [Description("空白选项")] + EmptyOption, + /// + /// 已经连接 + /// + [Description("已经连接")] + Connected, + /// + /// 连接被断开 + /// + [Description("连接被断开")] + DisConnected, + /// + /// 节点被锁定 + /// + [Description("节点被锁定")] + Locked, + /// + /// 操作被拒绝 + /// + [Description("操作被拒绝")] + Reject, + /// + /// 正在被连接 + /// + [Description("正在被连接")] + Connecting, + /// + /// 正在断开连接 + /// + [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; + /// + /// 触发此事件的对应Option + /// + public STNodeOption TargetOption { + get { return _TargetOption; } + } + + private ConnectionStatus _Status; + /// + /// Option之间的连线状态 + /// + public ConnectionStatus Status { + get { return _Status; } + internal set { _Status = value; } + } + + private bool _IsSponsor; + /// + /// 是否为此次行为的发起者 + /// + 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; + /// + /// 主动触发事件的Option + /// + public STNodeOption CurrentOption { + get { return _CurrentOption; } + } + + private bool _Continue = true; + /// + /// 是否继续向下操作 用于Begin(Connecting/DisConnecting)是否继续向后操作 + /// + public bool Continue { + get { return _Continue; } + set { _Continue = value; } + } + + public STNodeEditorOptionEventArgs(STNodeOption opTarget, STNodeOption opCurrent, ConnectionStatus cr) + : base(false, opTarget, cr) { + this._CurrentOption = opCurrent; + } + } +} diff --git a/ST.Library.UI/NodeEditor/STNodeEditorPannel.cs b/ST.Library.UI/NodeEditor/STNodeEditorPannel.cs new file mode 100644 index 0000000..c4a1041 --- /dev/null +++ b/ST.Library.UI/NodeEditor/STNodeEditorPannel.cs @@ -0,0 +1,329 @@ +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; + /// + /// 获取或设置是否是左边布局 + /// + [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; + /// + /// 获取或这是分割线颜色 + /// + [Description("获取或这是分割线颜色"), DefaultValue(typeof(Color), "Black")] + public Color SplitLineColor { + get { return _SplitLineColor; } + set { _SplitLineColor = value; } + } + + private Color _HandleLineColor = Color.Gray; + /// + /// 获取或设置分割线手柄颜色 + /// + [Description("获取或设置分割线手柄颜色"), DefaultValue(typeof(Color), "Gray")] + public Color HandleLineColor { + get { return _HandleLineColor; } + set { _HandleLineColor = value; } + } + + private bool _ShowScale = true; + /// + /// 获取或设置编辑器缩放时候显示比例 + /// + [Description("获取或设置编辑器缩放时候显示比例"), DefaultValue(true)] + public bool ShowScale { + get { return _ShowScale; } + set { _ShowScale = value; } + } + + private bool _ShowConnectionStatus = true; + /// + /// 获取或设置节点连线时候是否显示状态 + /// + [Description("获取或设置节点连线时候是否显示状态"), DefaultValue(true)] + public bool ShowConnectionStatus { + get { return _ShowConnectionStatus; } + set { _ShowConnectionStatus = value; } + } + + private int _X; + /// + /// 获取或设置分割线水平宽度 + /// + [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; + /// + /// 获取或设置分割线垂直高度 + /// + [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(); + } + } + /// + /// 获取面板中的STNodeEditor + /// + [Description("获取面板中的STNodeEditor"), Browsable(false)] + public STNodeEditor Editor { + get { return m_editor; } + } + /// + /// 获取面板中的STNodeTreeView + /// + [Description("获取面板中的STNodeTreeView"), Browsable(false)] + public STNodeTreeView TreeView { + get { return m_tree; } + } + /// + /// 获取面板中的STNodePropertyGrid + /// + [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 m_dic_status_key = new Dictionary(); + + 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; + } + /// + /// 向树控件中添加一个STNode + /// + /// STNode类型 + /// 是否添加成功 + public bool AddSTNode(Type stNodeType) { + return m_tree.AddNode(stNodeType); + } + /// + /// 从程序集中加载STNode + /// + /// 程序集路径 + /// 添加成功个数 + public int LoadAssembly(string strFileName) { + m_editor.LoadAssembly(strFileName); + return m_tree.LoadAssembly(strFileName); + } + /// + /// 设置编辑器显示连接状态的文本 + /// + /// 连接状态 + /// 对应显示文本 + /// 旧文本 + 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; + } + } +} diff --git a/ST.Library.UI/STNodeEditor/STNodeHub.cs b/ST.Library.UI/NodeEditor/STNodeHub.cs old mode 100755 new mode 100644 similarity index 85% rename from ST.Library.UI/STNodeEditor/STNodeHub.cs rename to ST.Library.UI/NodeEditor/STNodeHub.cs index 0ce6efe..32bb171 --- a/ST.Library.UI/STNodeEditor/STNodeHub.cs +++ b/ST.Library.UI/NodeEditor/STNodeHub.cs @@ -1,175 +1,192 @@ -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 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 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; ; - } - } - } -} +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 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 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; ; + } + } + } +} diff --git a/ST.Library.UI/STNodeEditor/STNodeOption.cs b/ST.Library.UI/NodeEditor/STNodeOption.cs old mode 100755 new mode 100644 similarity index 90% rename from ST.Library.UI/STNodeEditor/STNodeOption.cs rename to ST.Library.UI/NodeEditor/STNodeOption.cs index db11c63..bf04cc9 --- a/ST.Library.UI/STNodeEditor/STNodeOption.cs +++ b/ST.Library.UI/NodeEditor/STNodeOption.cs @@ -1,430 +1,456 @@ -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; - /// - /// 获取当前 Option 所属的 Node - /// - public STNode Owner { - get { return _Owner; } - internal set { - if (value == _Owner) return; - if (_Owner != null) this.DisConnectionAll(); //当所有者变更时 断开当前所有连接 - _Owner = value; - } - } - - private bool _IsSingle; - /// - /// 获取当前 Option 是否仅能被连接一次 - /// - public bool IsSingle { - get { return _IsSingle; } - } - - private bool _IsInput; - /// - /// 获取当前 Option 是否是输入选项 - /// - public bool IsInput { - get { return _IsInput; } - internal set { _IsInput = value; } - } - - private Color _TextColor = Color.White; - /// - /// 获取或设置当前 Option 文本颜色 - /// - public Color TextColor { - get { return _TextColor; } - protected set { - if (value == _TextColor) return; - _TextColor = value; - this.Invalidate(); - } - } - - private Color _DotColor = Color.Transparent; - /// - /// 获取或设置当前 Option 连接点的颜色 - /// - public Color DotColor { - get { return _DotColor; } - protected set { - if (value == _DotColor) return; - _DotColor = value; - this.Invalidate(); - } - } - - private string _Text; - /// - /// 获取或设置当前 Option 显示文本 - /// - public string Text { - get { return _Text; } - protected set { - if (value == _Text) return; - _Text = value; - this.Invalidate(); - } - } - - private int _DotLeft; - /// - /// 获取当前 Option 连接点的左边坐标 - /// - public int DotLeft { - get { return _DotLeft; } - internal set { _DotLeft = value; } - } - private int _DotTop; - /// - /// 获取当前 Option 连接点的上边坐标 - /// - public int DotTop { - get { return _DotTop; } - internal set { _DotTop = value; } - } - - private int _DotSize; - /// - /// 获取当前 Option 连接点的宽度 - /// - public int DotSize { - get { return _DotSize; } - protected set { _DotSize = value; } - } - - private Rectangle _TextRectangle; - /// - /// 获取当前 Option 文本区域 - /// - public Rectangle TextRectangle { - get { return _TextRectangle; } - internal set { _TextRectangle = value; } - } - - private object _Data; - /// - /// 获取或者设置当前 Option 所包含的数据 - /// - 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; - /// - /// 获取当前 Option 数据类型 - /// - public Type DataType { - get { return _DataType; } - internal set { _DataType = value; } - } - - //private Rectangle _DotRectangle; - /// - /// 获取当前 Option 连接点的区域 - /// - public Rectangle DotRectangle { - get { - return new Rectangle(this._DotLeft, this._DotTop, this._DotSize, this._DotSize); - } - } - /// - /// 获取当前 Option 被连接的个数 - /// - public int ConnectionCount { - get { return m_hs_connected.Count; } - } - /// - /// 获取当前 Option 所连接的 Option 集合 - /// - internal HashSet ConnectedOption { - get { return m_hs_connected; } - } - - #endregion Properties - /// - /// 保存已经被连接的点 - /// - protected HashSet m_hs_connected; - - #region Constructor - - /// - /// 构造一个 Option - /// - /// 显示文本 - /// 数据类型 - /// 是否为单连接 - public STNodeOption(string strText, Type dataType, bool bSingle) { - if (dataType == null) throw new ArgumentNullException("指定的数据类型不能为空"); - this._DotSize = 10; - m_hs_connected = new HashSet(); - this._DataType = dataType; - this._Text = strText; - this._IsSingle = bSingle; - } - - #endregion Constructor - - #region Event - - /// - /// 当被连接时候发生 - /// - public event STNodeOptionEventHandler Connected; - /// - /// 当连接开始发生时发生 - /// - public event STNodeOptionEventHandler Connecting; - /// - /// 当连接断开时候发生 - /// - public event STNodeOptionEventHandler DisConnected; - /// - /// 当连接开始断开时发生 - /// - public event STNodeOptionEventHandler DisConnecting; - /// - /// 当有数据传递时候发生 - /// - public event STNodeOptionEventHandler DataTransfer; - - #endregion Event - - #region protected - /// - /// 重绘整个控件 - /// - 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); - } - /// - /// 当前 Option 开始连接目标 Option - /// - /// 需要连接的 Option - /// 是否允许继续操作 - 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; - } - /// - /// 当前 Option 开始断开目标 Option - /// - /// 需要断开的 Option - /// 是否允许继续操作 - 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 - /// - /// 当前 Option 连接目标 Option - /// - /// 需要连接的 Option - /// 连接结果 - 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; - } - /// - /// 检测当前 Option 是否可以连接目标 Option - /// - /// 需要连接的 Option - /// 检测结果 - 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; - } - /// - /// 当前 Option 断开目标 Option - /// - /// 需要断开的 Option - /// - 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; - } - /// - /// 断开当前 Option 的所有连接 - /// - public void DisConnectionAll() { - var arr = m_hs_connected.ToArray(); - foreach (var v in arr) { - this.DisConnectOption(v); - } - } - /// - /// 获取当前 Option 所连接的 Option 集合 - /// - /// 如果为null 则表示不存在所有者 否则返回集合 - public List GetConnectedOption() { - if (!this._IsInput) - return m_hs_connected.ToList(); - List lst = new List(); - 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; - } - /// - /// 向当前 Option 所连接的所有 Option 投递数据 - /// - public void TransferData() { - foreach (var v in m_hs_connected) { - v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected)); - } - } - /// - /// 向当前 Option 所连接的所有 Option 投递数据 - /// - /// 需要投递的数据 - 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 - } -} +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; + /// + /// 获取当前 Option 所属的 Node + /// + public STNode Owner { + get { return _Owner; } + internal set { + if (value == _Owner) return; + if (_Owner != null) this.DisConnectionAll(); //当所有者变更时 断开当前所有连接 + _Owner = value; + } + } + + private bool _IsSingle; + /// + /// 获取当前 Option 是否仅能被连接一次 + /// + public bool IsSingle { + get { return _IsSingle; } + } + + private bool _IsInput; + /// + /// 获取当前 Option 是否是输入选项 + /// + public bool IsInput { + get { return _IsInput; } + internal set { _IsInput = value; } + } + + private Color _TextColor = Color.White; + /// + /// 获取或设置当前 Option 文本颜色 + /// + public Color TextColor { + get { return _TextColor; } + internal set { + if (value == _TextColor) return; + _TextColor = value; + this.Invalidate(); + } + } + + private Color _DotColor = Color.Transparent; + /// + /// 获取或设置当前 Option 连接点的颜色 + /// + public Color DotColor { + get { return _DotColor; } + internal set { + if (value == _DotColor) return; + _DotColor = value; + this.Invalidate(); + } + } + + private string _Text; + /// + /// 获取或设置当前 Option 显示文本 + /// 当AutoSize被设置时 无法修改此属性 + /// + 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; + /// + /// 获取当前 Option 连接点的左边坐标 + /// + public int DotLeft { + get { return _DotLeft; } + internal set { _DotLeft = value; } + } + private int _DotTop; + /// + /// 获取当前 Option 连接点的上边坐标 + /// + public int DotTop { + get { return _DotTop; } + internal set { _DotTop = value; } + } + + private int _DotSize; + /// + /// 获取当前 Option 连接点的宽度 + /// + public int DotSize { + get { return _DotSize; } + protected set { _DotSize = value; } + } + + private Rectangle _TextRectangle; + /// + /// 获取当前 Option 文本区域 + /// + public Rectangle TextRectangle { + get { return _TextRectangle; } + internal set { _TextRectangle = value; } + } + + private object _Data; + /// + /// 获取或者设置当前 Option 所包含的数据 + /// + 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; + /// + /// 获取当前 Option 数据类型 + /// + public Type DataType { + get { return _DataType; } + internal set { _DataType = value; } + } + + //private Rectangle _DotRectangle; + /// + /// 获取当前 Option 连接点的区域 + /// + public Rectangle DotRectangle { + get { + return new Rectangle(this._DotLeft, this._DotTop, this._DotSize, this._DotSize); + } + } + /// + /// 获取当前 Option 被连接的个数 + /// + public int ConnectionCount { + get { return m_hs_connected.Count; } + } + /// + /// 获取当前 Option 所连接的 Option 集合 + /// + internal HashSet ConnectedOption { + get { return m_hs_connected; } + } + + #endregion Properties + /// + /// 保存已经被连接的点 + /// + protected HashSet m_hs_connected; + + #region Constructor + + private STNodeOption() { } + + /// + /// 构造一个 Option + /// + /// 显示文本 + /// 数据类型 + /// 是否为单连接 + public STNodeOption(string strText, Type dataType, bool bSingle) { + if (dataType == null) throw new ArgumentNullException("指定的数据类型不能为空"); + this._DotSize = 10; + m_hs_connected = new HashSet(); + this._DataType = dataType; + this._Text = strText; + this._IsSingle = bSingle; + } + + #endregion Constructor + + #region Event + + /// + /// 当被连接时候发生 + /// + public event STNodeOptionEventHandler Connected; + /// + /// 当连接开始发生时发生 + /// + public event STNodeOptionEventHandler Connecting; + /// + /// 当连接断开时候发生 + /// + public event STNodeOptionEventHandler DisConnected; + /// + /// 当连接开始断开时发生 + /// + public event STNodeOptionEventHandler DisConnecting; + /// + /// 当有数据传递时候发生 + /// + public event STNodeOptionEventHandler DataTransfer; + + #endregion Event + + #region protected + /// + /// 重绘整个控件 + /// + 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); + } + /// + /// 当前 Option 开始连接目标 Option + /// + /// 需要连接的 Option + /// 是否允许继续操作 + 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; + } + /// + /// 当前 Option 开始断开目标 Option + /// + /// 需要断开的 Option + /// 是否允许继续操作 + 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 + /// + /// 当前 Option 连接目标 Option + /// + /// 需要连接的 Option + /// 连接结果 + 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; + } + /// + /// 检测当前 Option 是否可以连接目标 Option + /// + /// 需要连接的 Option + /// 检测结果 + 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; + } + /// + /// 当前 Option 断开目标 Option + /// + /// 需要断开的 Option + /// + 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; + } + /// + /// 断开当前 Option 的所有连接 + /// + public void DisConnectionAll() { + if (this._DataType == null) return; + var arr = m_hs_connected.ToArray(); + foreach (var v in arr) { + this.DisConnectOption(v); + } + } + /// + /// 获取当前 Option 所连接的 Option 集合 + /// + /// 如果为null 则表示不存在所有者 否则返回集合 + public List GetConnectedOption() { + if (this._DataType == null) return null; + if (!this._IsInput) + return m_hs_connected.ToList(); + List lst = new List(); + 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; + } + /// + /// 向当前 Option 所连接的所有 Option 投递数据 + /// + public void TransferData() { + if (this._DataType == null) return; + foreach (var v in m_hs_connected) { + v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected)); + } + } + /// + /// 向当前 Option 所连接的所有 Option 投递数据 + /// + /// 需要投递的数据 + 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)); + } + } + /// + /// 向当前 Option 所连接的所有 Option 投递数据 + /// + /// 需要投递的数据 + /// 是否释放旧数据 + 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 + } +} diff --git a/ST.Library.UI/STNodeEditor/STNodeOptionCollection.cs b/ST.Library.UI/NodeEditor/STNodeOptionCollection.cs old mode 100755 new mode 100644 similarity index 91% rename from ST.Library.UI/STNodeEditor/STNodeOptionCollection.cs rename to ST.Library.UI/NodeEditor/STNodeOptionCollection.cs index e0902f5..b074e55 --- a/ST.Library.UI/STNodeEditor/STNodeOptionCollection.cs +++ b/ST.Library.UI/NodeEditor/STNodeOptionCollection.cs @@ -1,231 +1,238 @@ -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(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]; - } - /// - /// 确认空间是否足够 空间不足扩大容量 - /// - /// 需要增加的个数 - 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; - } - } +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(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]; + } + /// + /// 确认空间是否足够 空间不足扩大容量 + /// + /// 需要增加的个数 + 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; + } + } } \ No newline at end of file diff --git a/ST.Library.UI/NodeEditor/STNodePropertyAttribute.cs b/ST.Library.UI/NodeEditor/STNodePropertyAttribute.cs new file mode 100644 index 0000000..7151bfd --- /dev/null +++ b/ST.Library.UI/NodeEditor/STNodePropertyAttribute.cs @@ -0,0 +1,334 @@ +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 +{ + /// + /// STNode节点属性特性 + /// 用于描述STNode节点属性信息 以及在属性编辑器上的行为 + /// + public class STNodePropertyAttribute : Attribute + { + private string _Name; + /// + /// 获取属性需要在属性编辑器上显示的名称 + /// + public string Name { + get { return _Name; } + } + + private string _Description; + /// + /// 获取属性需要在属性编辑器上显示的描述 + /// + public string Description { + get { return _Description; } + } + + private Type _ConverterType = typeof(STNodePropertyDescriptor); + /// + /// 获取属性描述器类型 + /// + public Type DescriptorType { + get { return _ConverterType; } + set { _ConverterType = value; } + } + + /// + /// 构造一个STNode属性特性 + /// + /// 需要显示的名称 + /// 需要显示的描述信息 + public STNodePropertyAttribute(string strKey, string strDesc) { + this._Name = strKey; + this._Description = strDesc; + } + //private Type m_descriptor_type_base = typeof(STNodePropertyDescriptor); + } + /// + /// STNode属性描述器 + /// 用于确定在属性编辑器上如何与属性的值进行交互 以及确定属性值在属性编辑器上将如何绘制并交互 + /// + public class STNodePropertyDescriptor + { + /// + /// 获取目标节点 + /// + public STNode Node { get; internal set; } + /// + /// 获取所属的节点属性编辑器控件 + /// + public STNodePropertyGrid Control { get; internal set; } + /// + /// 获取选项所在区域 + /// + public Rectangle Rectangle { get; internal set; } + /// + /// 获取选项名称所在区域 + /// + public Rectangle RectangleL { get; internal set; } + /// + /// 获取选项值所在区域 + /// + public Rectangle RectangleR { get; internal set; } + /// + /// 获取选项需要显示的名称 + /// + public string Name { get; internal set; } + /// + /// 获取属性对应的描述信息 + /// + public string Description { get; internal set; } + /// + /// 获取属性信息 + /// + 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; + + /// + /// 构造一个描述器 + /// + public STNodePropertyDescriptor() { + m_sf = new StringFormat(); + m_sf.LineAlignment = StringAlignment.Center; + m_sf.FormatFlags = StringFormatFlags.NoWrap; + } + + /// + /// 当确定STNode属性在属性编辑器上的位置时候发生 + /// + protected internal virtual void OnSetItemLocation() { } + /// + /// 将字符串形式的属性值转换为属性目标类型的值 + /// 默认只支持 int float double string bool 以及上述类型的Array + /// 若目标类型不在上述中 请重写此函数自行转换 + /// + /// 字符串形式的属性值 + /// 属性真实目标类型的值 + 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)]"); + } + /// + /// 将属性目标类型的值转换为字符串形式的值 + /// 默认对类型值进行 ToString() 操作 + /// 如需特殊处理 请重写此函数自行转换 + /// + /// 属性值的字符串形式 + 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 lst = new List(); + foreach (var item in (Array)v) lst.Add(item.ToString()); + return string.Join(",", lst.ToArray()); + } + return v.ToString(); + } + /// + /// 将二进制形式的属性值转换为属性目标类型的值 用于从文件存储中的数据还原属性值 + /// 默认将其转换为字符串然后调用 GetValueFromString(string) + /// 此函数与 GetBytesFromValue() 相对应 若需要重写函数应当两个函数一起重写 + /// + /// 二进制数据 + /// 属性真实目标类型的值 + protected internal virtual object GetValueFromBytes(byte[] byData) { + if (byData == null) return null; + string strText = Encoding.UTF8.GetString(byData); + return this.GetValueFromString(strText); + } + /// + /// 将属性目标类型的值转换为二进制形式的值 用于文件存储时候调用 + /// 默认调用 GetStringFromValue() 然后将字符串转换为二进制数据 + /// 如需特殊处理 请重写此函数自行转换 并且重写 GetValueFromBytes() + /// + /// 属性值的二进制形式 + protected internal virtual byte[] GetBytesFromValue() { + string strText = this.GetStringFromValue(); + if (strText == null) return null; + return Encoding.UTF8.GetBytes(strText); + } + /// + /// 此函数对应 System.Reflection.PropertyInfo.GetValue() + /// + /// 索引属性的可选索引值 对于非索引属性 此值应为null + /// 属性值 + protected internal virtual object GetValue(object[] index) { + return this.PropertyInfo.GetValue(this.Node, index); + } + /// + /// 此函数对应 System.Reflection.PropertyInfo.SetValue() + /// + /// 需要设置的属性值 + protected internal virtual void SetValue(object value) { + this.PropertyInfo.SetValue(this.Node, value, null); + } + /// + /// 此函数对应 System.Reflection.PropertyInfo.SetValue() + /// 在调用之前会默认进行 GetValueFromString(strValue) 处理 + /// + /// 需要设置的属性字符串形式的值 + protected internal virtual void SetValue(string strValue) { + this.PropertyInfo.SetValue(this.Node, this.GetValueFromString(strValue), null); + } + /// + /// 此函数对应 System.Reflection.PropertyInfo.SetValue() + /// 在调用之前会默认进行 GetValueFromBytes(byte[]) 处理 + /// + /// 需要设置的属性二进制数据 + protected internal virtual void SetValue(byte[] byData) { + this.PropertyInfo.SetValue(this.Node, this.GetValueFromBytes(byData), null); + } + /// + /// 此函数对应 System.Reflection.PropertyInfo.SetValue() + /// + /// 需要设置的属性值 + /// 索引属性的可选索引值 对于非索引属性 此值应为null + protected internal virtual void SetValue(object value, object[] index) { + this.PropertyInfo.SetValue(this.Node, value, index); + } + /// + /// 此函数对应 System.Reflection.PropertyInfo.SetValue() + /// 在调用之前会默认进行 GetValueFromString(strValue) 处理 + /// + /// 需要设置的属性字符串形式的值 + /// 索引属性的可选索引值 对于非索引属性 此值应为null + protected internal virtual void SetValue(string strValue, object[] index) { + this.PropertyInfo.SetValue(this.Node, this.GetValueFromString(strValue), index); + } + /// + /// 此函数对应 System.Reflection.PropertyInfo.SetValue() + /// 在调用之前会默认进行 GetValueFromBytes(byte[]) 处理 + /// + /// 需要设置的属性二进制数据 + /// 索引属性的可选索引值 对于非索引属性 此值应为null + protected internal virtual void SetValue(byte[] byData, object[] index) { + this.PropertyInfo.SetValue(this.Node, this.GetValueFromBytes(byData), index); + } + /// + /// 当设置属性值发生错误时候发生 + /// + /// 异常信息 + protected internal virtual void OnSetValueError(Exception ex) { + this.Control.SetErrorMessage(ex.Message); + } + /// + /// 当绘制属性在属性编辑器上的值所在区域时候发生 + /// + /// 绘制工具 + 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) + }); + } + } + /// + /// 当鼠标进入属性值所在区域时候发生 + /// + /// 事件参数 + protected internal virtual void OnMouseEnter(EventArgs e) { } + /// + /// 当鼠标在属性值所在区域点击时候发生 + /// + /// 事件参数 + protected internal virtual void OnMouseDown(MouseEventArgs e) { + } + /// + /// 当鼠标在属性值所在区域移动时候发生 + /// + /// 事件参数 + protected internal virtual void OnMouseMove(MouseEventArgs e) { } + /// + /// 当鼠标在属性值所在区域抬起时候发生 + /// + /// 事件参数 + protected internal virtual void OnMouseUp(MouseEventArgs e) { } + /// + /// 当鼠标在属性值所在区域离开时候发生 + /// + /// 事件参数 + protected internal virtual void OnMouseLeave(EventArgs e) { } + /// + /// 当鼠标在属性值所在区域点击时候发生 + /// + /// 事件参数 + 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); + } + /// + /// 重绘选项区域 + /// + public void Invalidate() { + Rectangle rect = this.Rectangle; + rect.X -= this.Control.ScrollOffset; + this.Control.Invalidate(rect); + } + } +} diff --git a/ST.Library.UI/NodeEditor/STNodePropertyGrid.cs b/ST.Library.UI/NodeEditor/STNodePropertyGrid.cs new file mode 100644 index 0000000..9b07e37 --- /dev/null +++ b/ST.Library.UI/NodeEditor/STNodePropertyGrid.cs @@ -0,0 +1,860 @@ +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 +{ + /// + /// STNode节点属性编辑器 + /// + public class STNodePropertyGrid : Control + { + #region properties ========== + + private STNode _STNode; + /// + /// 当前显示的STNode + /// + [Description("当前显示的STNode"), Browsable(false)] + public STNode STNode { + get { return _STNode; } + } + + private Color _ItemHoverColor = Color.FromArgb(50, 125, 125, 125); + /// + /// 获取或设置属性选项被鼠标悬停时候背景色 + /// + [Description("获取或设置属性选项被鼠标悬停时候背景色")] + public Color ItemHoverColor { + get { return _ItemHoverColor; } + set { _ItemHoverColor = value; } + } + + private Color _ItemSelectedColor = Color.DodgerBlue; + /// + /// 获取或设置属性选项被选中时候背景色 当AutoColor被设置时此属性不能被设置 + /// + [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); + /// + /// 获取或设置属性选项值背景色 + /// + [Description("获取或设置属性选项值背景色")] + public Color ItemValueBackColor { + get { return _ItemValueBackColor; } + set { + _ItemValueBackColor = value; + this.Invalidate(); + } + } + + private Color _TitleColor = Color.FromArgb(127, 0, 0, 0); + /// + /// 获取或设置默认标题背景色 + /// + [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); + /// + /// 获取或设置属性设置错误时候提示信息背景色 + /// + [Description("获取或设置属性设置错误时候提示信息背景色")] + public Color ErrorColor { + get { return _ErrorColor; } + set { _ErrorColor = value; } + } + + private Color _DescriptionColor = Color.FromArgb(200, Color.DarkGoldenrod); + /// + /// 获取或设置属性描述信息背景色 + /// + [Description("获取或设置属性描述信息背景色")] + public Color DescriptionColor { + get { return _DescriptionColor; } + set { _DescriptionColor = value; } + } + + private bool _ShowTitle = true; + /// + /// 获取或设置是否显示节点标题 + /// + [Description("获取或设置是否显示节点标题")] + public bool ShowTitle { + get { return _ShowTitle; } + set { + _ShowTitle = value; + this.SetItemRectangle(); + this.Invalidate(); + } + } + + private bool _AutoColor = true; + /// + /// 获取或设置是否根据STNode自动设置控件高亮颜色 + /// + [Description("获取或设置是否根据STNode自动设置控件高亮颜色"), DefaultValue(true)] + public bool AutoColor { + get { return _AutoColor; } + set { _AutoColor = value; } + } + + private bool _InfoFirstOnDraw; + /// + /// 获取或当节点被设置时候 是否优先绘制信息面板 + /// + [Description("获取或设置当节点被设置时候 是否优先绘制信息面板"), DefaultValue(false)] + public bool InfoFirstOnDraw { + get { return _InfoFirstOnDraw; } + set { _InfoFirstOnDraw = value; } + } + + private bool _ReadOnlyModel; + /// + /// 获取或设置当前属性编辑器是否处于只读模式 + /// + [Description("获取或设置当前属性编辑器是否处于只读模式"), DefaultValue(false)] + public bool ReadOnlyModel { + get { return _ReadOnlyModel; } + set { + if (value == _ReadOnlyModel) return; + _ReadOnlyModel = value; + this.Invalidate(m_rect_title); + } + } + /// + /// 获取当前滚动条高度 + /// + [Description("获取当前滚动条高度")] + public int ScrollOffset { get { return m_nOffsetY; } } + + #endregion + + #region protected fields ========== + + /// + /// 作者链接地址区域 + /// + protected Rectangle m_rect_link; + /// + /// 查看帮助按钮区域 + /// + protected Rectangle m_rect_help; + /// + /// 编辑器标题区域 + /// + protected Rectangle m_rect_title; + /// + /// 面板切换按钮区域 + /// + protected Rectangle m_rect_switch; + + /// + /// 控件在绘制过程中使用的垂直滚动偏移 + /// + protected int m_nOffsetY; + /// + /// 保存的信息面板垂直滚动偏移 + /// + protected int m_nInfoOffsetY; + /// + /// 保存的属性面板垂直滚动偏移 + /// + protected int m_nPropertyOffsetY; + + /// + /// 控件在绘制过程中使用的绘图区域总高度 + /// + protected int m_nVHeight; + /// + /// 保存的信息面板需要的总高度 + /// + protected int m_nInfoVHeight; + /// + /// 保存的属性面板需要的总高度 + /// + protected int m_nPropertyVHeight; + /// + /// 信息面板中Key显示需要的水平宽度 + /// + 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 m_lst_item = new List(); + + 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; + + /// + /// 构造一个节点属性编辑器 + /// + 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 GetProperties(STNode node) { + List lst = new List(); + 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 ========== + + /// + /// 当控件重绘时候发生 + /// + /// 事件参数 + 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); + } + /// + /// 当鼠标在控件上移动时候发生 + /// + /// 事件参数 + 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); + } + /// + /// 当鼠标在控件上点下时候发生 + /// + /// 事件参数 + 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(); + } + /// + /// 当鼠标在控件上抬起时候发生 + /// + /// 事件参数 + 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(); + } + /// + /// 当鼠标离开控件时候发生 + /// + /// 事件参数 + 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(); + } + /// + /// 当鼠标在控件上滚动滚轮时候发生 + /// + /// 事件参数 + 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(); + } + /// + /// 当设置控件矩形区域时候发生 + /// + /// x坐标 + /// y坐标 + /// 宽度 + /// 高度 + /// 指定需要设置的标识 + //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); + //} + /// + /// 当控件尺寸发生改变时候发生 + /// + /// 事件参数 + 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 ========== + + /// + /// 当绘制属性选项时候发生 + /// + /// 绘制工具 + /// 目标属性选项描述器 + /// 选项所在索引 + 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); + } + } + /// + /// 绘制属性窗口标题 + /// + /// 绘制工具 + 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 ), + }); + } + /// + /// 当需要绘制属性描述信息时发生 + /// + /// 绘制工具 + 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); + } + /// + /// 当需要绘制错误信息时发生 + /// + /// 绘制工具 + 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); + } + /// + /// 当绘制节点信息时候发生 + /// + /// 绘制工具 + 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; + } + /// + /// 当在属性面板鼠标点下时候发生 + /// + /// 鼠标事件参数 + 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(); + } + /// + /// 当在信息面板鼠标点下时候发生 + /// + /// 鼠标事件参数 + 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); + } + } + /// + /// 当在属性面板鼠标移动时候发生 + /// + /// 鼠标事件参数 + 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(); + } + } + /// + /// 当在信息面板鼠标移动时候发生 + /// + /// 鼠标事件参数 + 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 ========== + + /// + /// 设置需要显示的STNode节点 + /// + /// 目标节点 + 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(); + } + /// + /// 设置信息页面Key的显示文本 + /// + /// 作者 + /// 邮箱 + /// 连接 + /// 查看帮助 + public void SetInfoKey(string strAuthor, string strMail, string strLink, string strHelp) { + m_KeysString = new string[] { strAuthor, strMail, strLink, strHelp }; + } + /// + /// 设置要显示的错误信息 + /// + /// 错误信息 + public void SetErrorMessage(string strText) { + m_str_err = strText; + this.Invalidate(); + } + + #endregion + } +} diff --git a/ST.Library.UI/NodeEditor/STNodeTreeView.cs b/ST.Library.UI/NodeEditor/STNodeTreeView.cs new file mode 100644 index 0000000..6b40ee3 --- /dev/null +++ b/ST.Library.UI/NodeEditor/STNodeTreeView.cs @@ -0,0 +1,907 @@ +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); + /// + /// 获取或设置每行属性选项背景色 + /// + [Description("获取或设置每行属性选项背景色")] + public Color ItemBackColor { + get { return _ItemBackColor; } + set { + _ItemBackColor = value; + } + } + + private Color _ItemHoverColor = Color.FromArgb(50, 125, 125, 125); + /// + /// 获取或设置属性选项被鼠标悬停时候背景色 + /// + [Description("获取或设置属性选项被鼠标悬停时候背景色")] + public Color ItemHoverColor { + get { return _ItemHoverColor; } + set { _ItemHoverColor = value; } + } + + private Color _TitleColor = Color.FromArgb(255, 60, 60, 60); + /// + /// 获取或设置顶部检索区域背景色 + /// + [Description("获取或设置顶部检索区域背景颜色")] + public Color TitleColor { + get { return _TitleColor; } + set { + _TitleColor = value; + this.Invalidate(new Rectangle(0, 0, this.Width, m_nItemHeight)); + } + } + + /// + /// 获取或设置检索文本框的背景色 + /// + [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; + /// + /// 获取或设置检索时候高亮文本颜色 + /// + [Description("获取或设置检索时候高亮文本颜色"), DefaultValue(typeof(Color), "Lime")] + public Color HightLightTextColor { + get { return _HightLightTextColor; } + set { _HightLightTextColor = value; } + } + + private Color _InfoButtonColor = Color.Gray; + /// + /// 获取或设置信息显示按钮颜色 若设置AutoColor无法设置此属性值 + /// + [Description("获取或设置信息显示按钮颜色 若设置AutoColor无法设置此属性值"), DefaultValue(typeof(Color), "Gray")] + public Color InfoButtonColor { + get { return _InfoButtonColor; } + set { _InfoButtonColor = value; } + } + + private Color _FolderCountColor = Color.FromArgb(40, 255, 255, 255); + /// + /// 获取或设置统计个数的文本颜色 + /// + [Description("获取或设置统计个数的文本颜色")] + public Color FolderCountColor { + get { return _FolderCountColor; } + set { _FolderCountColor = value; } + } + + private Color _SwitchColor = Color.LightGray; + + private bool _ShowFolderCount = true; + /// + /// 获取或设置是否统计STNode的个数 + /// + [Description("获取或设置是否统计STNode的个数"), DefaultValue(typeof(Color), "LightGray")] + public bool ShowFolderCount { + get { return _ShowFolderCount; } + set { _ShowFolderCount = value; } + } + + private bool _ShowInfoButton = true; + /// + /// 获取或设置是否显示信息按钮 + /// + [Description("获取或设置是否显示信息按钮"), DefaultValue(true)] + public bool ShowInfoButton { + get { return _ShowInfoButton; } + set { _ShowInfoButton = value; } + } + + private bool _InfoPanelIsLeftLayout = true; + /// + /// 获取或设置预览窗口是否是向左布局 + /// + [Description("获取或设置预览窗口是否是向左布局"), DefaultValue(true)] + public bool InfoPanelIsLeftLayout { + get { return _InfoPanelIsLeftLayout; } + set { _InfoPanelIsLeftLayout = value; } + } + + private bool _AutoColor = true; + /// + /// 获取或设置控件中部分颜色来之对应的STNode的标题颜色 + /// + [Description("获取或设置控件中部分颜色来之对应的STNode的标题颜色"), DefaultValue(true)] + public bool AutoColor { + get { return _AutoColor; } + set { + _AutoColor = value; + this.Invalidate(); + } + } + + private STNodeEditor _Editor; + /// + /// 获取节点预览时候使用的STNodeEditor + /// + [Description("获取节点预览时候使用的STNodeEditor"), Browsable(false)] + public STNodeEditor Editor { + get { return _Editor; } + } + + private STNodePropertyGrid _PropertyGrid; + /// + /// 获取节点预览时候使用的STNodePropertyGrid + /// + [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 m_dic_all_type = new Dictionary(); + + 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(); //检索文本框 + /// + /// 构造一个STNode树控件 + /// + 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(), m_str_search); + this.Invalidate(); + } + + private bool Search(STNodeTreeCollection items, Stack 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 ========== + /// + /// 当绘制检索文本区域时候发生 + /// + /// 绘制工具 + 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; + } + } + /// + /// 当开始绘制树节点的每一个节点时候发生 + /// + /// 绘制工具 + /// 当前需要绘制的集合 + /// 已经绘制个数的计数器 + /// 当前位于第几级子集合 + /// 已经绘制个数 + 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; + } + /// + /// 当绘制树节点每一个节点时候发生 + /// + /// 绘制工具 + /// 当前需要绘制的集合 + /// 已经绘制个数的计数器 + /// 当前位于第几级子集合 + 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); + } + /// + /// 当绘制树节点展开与关闭开关时候发生 + /// + /// 绘制工具 + /// 当前需要绘制的集合 + 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); + //} + } + } + /// + /// 当绘制树节点的文本时候发生 + /// + /// 绘制工具 + /// 当前需要绘制的集合 + /// 文本域所在矩形区域 + 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); + } + /// + /// 当绘制树节点图标时候发生 + /// + /// 绘制工具 + /// 当前需要绘制的集合 + /// 文本域所在矩形区域 + 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 ========== + /// + /// 在控件中检索STNode + /// + /// 需要检索的文本 + public void Search(string strText) { + if (strText == null) return; + if (strText.Trim() == string.Empty) return; + m_tbx.Text = strText.Trim(); + } + /// + /// 向控件中添加一个STNode类型 + /// + /// STNode类型 + /// 是否添加成功 + public bool AddNode(Type stNodeType) { return this.AddSTNode(stNodeType, m_items_source, null, true); } + /// + /// 从文件中向控件添加STNode类型 + /// + /// 指定文件路径 + /// 添加成功个数 + 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; + } + /// + /// 清空控件中所有STNode类型 + /// + public void Clear() { + m_items_source.Clear(); + m_items_draw.Clear(); + m_dic_all_type.Clear(); + this.Invalidate(); + } + /// + /// 向控件中移除一个STNode类型 + /// + /// STNode类型 + /// 是否移除成功 + 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 + //================================================================================================= + /// + /// STNodeTreeView控件中每一项的集合 + /// + protected class STNodeTreeCollection : IEnumerable + { + private string _Name; + /// + /// 获取当前树节点显示名称 + /// + public string Name { + get { + return _Name; + } + } + /// + /// 获取当前树节点显示名称的小写字符串 + /// + public string NameLower { get; private set; } + /// + /// 获取当前树节点对应的STNode类型 + /// + public Type STNodeType { get; internal set; } + /// + /// 获取当前树节点的父级树节点 + /// + public STNodeTreeCollection Parent { get; internal set; } + + /// + /// 获取当前树节点下拥有的STNode类型个数 + /// + public int STNodeCount { get; internal set; } + /// + /// 获取当前树节点对应STNode类型在树控件中对应路径 + /// + public string Path { get; internal set; } + /// + /// 获取当前或设置树节点是否为打开状态 + /// + public bool IsOpen { get; set; } + /// + /// 获取当前树节点是否为加载模块的根路劲节点 + /// + public bool IsLibraryRoot { get; internal set; } + /// + /// 获取当前树节点在控件中的显示区域 + /// + public Rectangle DisplayRectangle { get; internal set; } + /// + /// 获取当前树节点在控件中的开关按钮区域 + /// + public Rectangle SwitchRectangle { get; internal set; } + /// + /// 获取当前树节点在控件中的信息按钮区域 + /// + public Rectangle InfoRectangle { get; internal set; } + /// + /// 获取当前树节点对应STNode类型的标题颜色 + /// + public Color STNodeTypeColor { get; internal set; } + /// + /// 获取当前树节点所包含子节点个数 + /// + public int Count { get { return m_dic.Count; } } + /// + /// 获取或设置指定名称的集合 + /// + /// 指定名称 + /// 集合 + 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 m_dic = new SortedDictionary(); + /// + /// 构造一颗树节点集合 + /// + /// 当前树节点在控件中的显示名称 + public STNodeTreeCollection(string strName) { + if (strName == null || strName.Trim() == string.Empty) + throw new ArgumentNullException("显示名称不能为空"); + this._Name = strName.Trim(); + this.NameLower = this._Name.ToLower(); + } + /// + /// 向当前树节点中添加一个子节点 + /// + /// 节点显示名称 + /// 添加后的子节点集合 + public STNodeTreeCollection Add(string strName) { + if (!m_dic.ContainsKey(strName)) + m_dic.Add(strName, new STNodeTreeCollection(strName) { Parent = this }); + return m_dic[strName]; + } + /// + /// 向当前树节点中删除一个子集合 + /// + /// 子集合名称 + /// 是否递归向上自动清空无用节点 + /// 是否删除成功 + 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; + } + /// + /// 清空当前树节点中所有子节点 + /// + public void Clear() { this.Clear(this); } + + private void Clear(STNodeTreeCollection items) { + foreach (STNodeTreeCollection v in items) v.Clear(v); + m_dic.Clear(); + } + /// + /// 获取当前树节点中所有的名称数组 + /// + /// + public string[] GetKeys() { return m_dic.Keys.ToArray(); } + /// + /// 拷贝当前树节点集合中所有数据 + /// + /// 拷贝的副本 + 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; + } + /// + /// 返回 System.Collections.IEnumerator 的 Array + /// + /// + public IEnumerator GetEnumerator() { + foreach (var v in m_dic.Values) yield return v; + } + + IEnumerator IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + } + } +} diff --git a/ST.Library.UI/Properties/AssemblyInfo.cs b/ST.Library.UI/Properties/AssemblyInfo.cs old mode 100755 new mode 100644 diff --git a/ST.Library.UI/ST.Library.UI.csproj b/ST.Library.UI/ST.Library.UI.csproj old mode 100755 new mode 100644 index f847233..92a545a --- a/ST.Library.UI/ST.Library.UI.csproj +++ b/ST.Library.UI/ST.Library.UI.csproj @@ -21,6 +21,7 @@ DEBUG;TRACE prompt 4 + AnyCPU pdbonly @@ -35,24 +36,45 @@ + - - - - - - + + Form + + + Form + + + Component + + + Component + + + + Form + + + + + + + + Component + + + + + + + Component - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/WinNodeEditerTest/Demo_Image/STNodeImage.cs b/WinNodeEditerTest/Demo_Image/STNodeImage.cs deleted file mode 100755 index 43e7515..0000000 --- a/WinNodeEditerTest/Demo_Image/STNodeImage.cs +++ /dev/null @@ -1,52 +0,0 @@ -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() { } - } -} diff --git a/WinNodeEditerTest/Demo_Image/STNodeImageChannel.cs b/WinNodeEditerTest/Demo_Image/STNodeImageChannel.cs deleted file mode 100755 index 3708fd9..0000000 --- a/WinNodeEditerTest/Demo_Image/STNodeImageChannel.cs +++ /dev/null @@ -1,57 +0,0 @@ -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); - } - } -} diff --git a/WinNodeEditerTest/Demo_Image/STNodeImageInput.cs b/WinNodeEditerTest/Demo_Image/STNodeImageInput.cs deleted file mode 100755 index 940e237..0000000 --- a/WinNodeEditerTest/Demo_Image/STNodeImageInput.cs +++ /dev/null @@ -1,93 +0,0 @@ -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 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 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); - } - } - } -} diff --git a/WinNodeEditerTest/Form1.Designer.cs b/WinNodeEditerTest/Form1.Designer.cs deleted file mode 100755 index f1ed28a..0000000 --- a/WinNodeEditerTest/Form1.Designer.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace ST.Library.UI -{ - partial class Form1 - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) { - if (disposing && (components != null)) { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - 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; - } -} \ No newline at end of file diff --git a/WinNodeEditerTest/Form1.cs b/WinNodeEditerTest/Form1.cs deleted file mode 100755 index 2bdce0f..0000000 --- a/WinNodeEditerTest/Form1.cs +++ /dev/null @@ -1,87 +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 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); - } - } -} diff --git a/WinNodeEditerTest/Form2.Designer.cs b/WinNodeEditerTest/Form2.Designer.cs deleted file mode 100755 index 5392566..0000000 --- a/WinNodeEditerTest/Form2.Designer.cs +++ /dev/null @@ -1,96 +0,0 @@ -namespace ST.Library.UI -{ - partial class Form2 - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) { - if (disposing && (components != null)) { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - 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; - } -} \ No newline at end of file diff --git a/WinNodeEditerTest/Form2.cs b/WinNodeEditerTest/Form2.cs deleted file mode 100755 index c8457af..0000000 --- a/WinNodeEditerTest/Form2.cs +++ /dev/null @@ -1,36 +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; - -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); - } - } -} diff --git a/WinNodeEditerTest/Form2.resx b/WinNodeEditerTest/Form2.resx deleted file mode 100755 index 29dcb1b..0000000 --- a/WinNodeEditerTest/Form2.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/WinNodeEditerTest/NodeNumberAdd.cs b/WinNodeEditerTest/NodeNumberAdd.cs deleted file mode 100755 index 7d30aa9..0000000 --- a/WinNodeEditerTest/NodeNumberAdd.cs +++ /dev/null @@ -1,49 +0,0 @@ -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); - } - } -} diff --git a/WinNodeEditerTest/app.config b/WinNodeEditerTest/app.config deleted file mode 100755 index cb2586b..0000000 --- a/WinNodeEditerTest/app.config +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/WinNodeEditorDemo/AttrTestNode.cs b/WinNodeEditorDemo/AttrTestNode.cs new file mode 100644 index 0000000..b9c7fd2 --- /dev/null +++ b/WinNodeEditorDemo/AttrTestNode.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; +using System.Windows.Forms; + +namespace WinNodeEditorDemo +{ + [STNode("/", "Crystal_lz", "2212233137@qq.com", "www.st233.com", "关于此节点的描述信息\r\n此类为\r\nSTNodeAttribute\r\nSTNodePropertyAttribute\r\n效果演示类")] + public class AttrTestNode : STNode + { + //因为属性编辑器默认并不支持Color类型数据 所以这里重写一个描述器并指定 + [STNodeProperty("颜色", "颜色信息", DescriptorType = typeof(DescriptorForColor))] + public Color Color { get; set; } + + [STNodeProperty("整型数组", "整型数组测试")] + public int[] IntArr { get; set; } + + [STNodeProperty("布尔", "布尔类型测试")] + public bool Bool { get; set; } + + [STNodeProperty("字符串", "字符串类型测试")] + public string String { get; set; } + + [STNodeProperty("整型", "整型测试")] + public int Int { get; set; } + + [STNodeProperty("浮点数", "浮点数类型测试")] + public float Float { get; set; } + + [STNodeProperty("枚举值", "枚举类型测试 -> FormBorderStyle")] + public FormBorderStyle STYLE { get; set; } + + public AttrTestNode() { + this.String = "string"; + IntArr = new int[] { 10, 20 }; + base.InputOptions.Add("string", typeof(string), false); + base.OutputOptions.Add("string", typeof(string), false); + this.Title = "AttrTestNode"; + this.TitleColor = Color.FromArgb(200, Color.Goldenrod); + } + /// + /// 此方法为魔术方法(Magic function) + /// 若存在 static void ShowHelpInfo(string) 且此类被STNodeAttribute标记 + /// 则此方法将作为属性编辑器上 查看帮助 功能 + /// + /// 此类所在的模块所在的文件路径 + 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); + } + } + /// + /// 因为属性编辑器默认并不支持Color类型数据 所以这里重写一个描述器 + /// + 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); + } + } +} diff --git a/WinNodeEditorDemo/Blender/BlenderMixColorNode.cs b/WinNodeEditorDemo/Blender/BlenderMixColorNode.cs new file mode 100644 index 0000000..8de3399 --- /dev/null +++ b/WinNodeEditorDemo/Blender/BlenderMixColorNode.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; + +namespace WinNodeEditorDemo.Blender +{ + /// + /// 此类仅仅是演示 并不包含颜色混合功能 + /// + [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); + } + } +} diff --git a/WinNodeEditorDemo/Blender/FrmEnumSelect.cs b/WinNodeEditorDemo/Blender/FrmEnumSelect.cs new file mode 100644 index 0000000..3c8e87f --- /dev/null +++ b/WinNodeEditorDemo/Blender/FrmEnumSelect.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using System.Windows.Forms; + +namespace WinNodeEditorDemo.Blender +{ + /// + /// 此类仅演示 作为MixRGB节点的下拉选择框弹出菜单 + /// + public class FrmEnumSelect : Form + { + private Point m_pt; + private int m_nWidth; + private float m_scale; + private List m_lst = new List(); + 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(); + } + } +} diff --git a/WinNodeEditorDemo/Blender/STNodeCheckBox.cs b/WinNodeEditorDemo/Blender/STNodeCheckBox.cs new file mode 100644 index 0000000..db3bfeb --- /dev/null +++ b/WinNodeEditorDemo/Blender/STNodeCheckBox.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using ST.Library.UI.NodeEditor; + +namespace WinNodeEditorDemo.Blender +{ + /// + /// 此类仅演示 作为MixRGB节点的复选框控件 + /// + 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); + } + } + } +} diff --git a/WinNodeEditorDemo/Blender/STNodeColorButton.cs b/WinNodeEditorDemo/Blender/STNodeColorButton.cs new file mode 100644 index 0000000..1d7aa70 --- /dev/null +++ b/WinNodeEditorDemo/Blender/STNodeColorButton.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using System.Windows.Forms; +using ST.Library.UI.NodeEditor; + +namespace WinNodeEditorDemo.Blender +{ + /// + /// 此类仅演示 作为MixRGB节点的颜色选择按钮 + /// + 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()); + } + } +} diff --git a/WinNodeEditorDemo/Blender/STNodeProgress.cs b/WinNodeEditorDemo/Blender/STNodeProgress.cs new file mode 100644 index 0000000..5fa5587 --- /dev/null +++ b/WinNodeEditorDemo/Blender/STNodeProgress.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; + +namespace WinNodeEditorDemo.Blender +{ + /// + /// 此类仅演示 作为MixRGB节点的进度条控件 + /// + 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(); + } + } +} diff --git a/WinNodeEditorDemo/Blender/STNodeSelectBox.cs b/WinNodeEditorDemo/Blender/STNodeSelectBox.cs new file mode 100644 index 0000000..9b36e87 --- /dev/null +++ b/WinNodeEditorDemo/Blender/STNodeSelectBox.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using System.Drawing; +using ST.Library.UI.NodeEditor; + +namespace WinNodeEditorDemo.Blender +{ + /// + /// 此类仅演示 作为MixRGB节点的下拉框控件 + /// + 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()); + } + } +} diff --git a/WinNodeEditorDemo/CalcNode.cs b/WinNodeEditorDemo/CalcNode.cs new file mode 100644 index 0000000..01fe541 --- /dev/null +++ b/WinNodeEditorDemo/CalcNode.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; + +namespace WinNodeEditorDemo +{ + /// + /// 此节点仅演示UI自定义以及控件 并不包含功能 + /// + [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); + } + } +} diff --git a/WinNodeEditorDemo/EmptyOptionTestNode.cs b/WinNodeEditorDemo/EmptyOptionTestNode.cs new file mode 100644 index 0000000..1c0449e --- /dev/null +++ b/WinNodeEditorDemo/EmptyOptionTestNode.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; + +namespace WinNodeEditorDemo +{ + [STNode("/")] + public class EmptyOptionTestNode : STNode + { + protected override void OnCreate() { + base.OnCreate(); + this.Title = "EmptyTest"; + this.InputOptions.Add(STNodeOption.Empty); + this.InputOptions.Add("string", typeof(string), false); + } + } +} diff --git a/WinNodeEditorDemo/Form1.Designer.cs b/WinNodeEditorDemo/Form1.Designer.cs new file mode 100644 index 0000000..76a8644 --- /dev/null +++ b/WinNodeEditorDemo/Form1.Designer.cs @@ -0,0 +1,173 @@ +namespace WinNodeEditorDemo +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + + } +} + diff --git a/WinNodeEditorDemo/Form1.cs b/WinNodeEditorDemo/Form1.cs new file mode 100644 index 0000000..1fd3c49 --- /dev/null +++ b/WinNodeEditorDemo/Form1.cs @@ -0,0 +1,88 @@ +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); + } + } +} diff --git a/WinNodeEditerTest/Form1.resx b/WinNodeEditorDemo/Form1.resx old mode 100755 new mode 100644 similarity index 94% rename from WinNodeEditerTest/Form1.resx rename to WinNodeEditorDemo/Form1.resx index 29dcb1b..73b0127 --- a/WinNodeEditerTest/Form1.resx +++ b/WinNodeEditorDemo/Form1.resx @@ -1,120 +1,123 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + \ No newline at end of file diff --git a/WinNodeEditorDemo/ImageNode/ImageBaseNode.cs b/WinNodeEditorDemo/ImageNode/ImageBaseNode.cs new file mode 100644 index 0000000..54010d1 --- /dev/null +++ b/WinNodeEditorDemo/ImageNode/ImageBaseNode.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; + +namespace WinNodeEditorDemo.ImageNode +{ + /// + /// 图片节点基类 用于确定节点风格 标题颜色 以及 数据类型颜色 + /// + public abstract class ImageBaseNode : STNode + { + /// + /// 需要作为显示绘制的图片 + /// + protected Image m_img_draw; + /// + /// 输出节点 + /// + 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); + } + } +} diff --git a/WinNodeEditorDemo/ImageNode/ImageChannelNode.cs b/WinNodeEditorDemo/ImageNode/ImageChannelNode.cs new file mode 100644 index 0000000..15fbbea --- /dev/null +++ b/WinNodeEditorDemo/ImageNode/ImageChannelNode.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; +using System.Drawing.Imaging; + +namespace WinNodeEditorDemo.ImageNode +{ + [STNode("/Image")] + public class ImageChannelNode : ImageBaseNode + { + private STNodeOption m_op_img_in; //输入的节点 + private STNodeOption m_op_img_r; //R图 输出节点 + private STNodeOption m_op_img_g; //G图 输出节点 + private STNodeOption m_op_img_b; //B图 输出节点 + + protected override void OnCreate() { + base.OnCreate(); + this.Title = "ImageChannel"; + + m_op_img_in = this.InputOptions.Add("", typeof(Image), true); + m_op_img_r = this.OutputOptions.Add("R", typeof(Image), false); + m_op_img_g = this.OutputOptions.Add("G", typeof(Image), false); + m_op_img_b = this.OutputOptions.Add("B", typeof(Image), false); + //当输入节点有数据输入时候 + m_op_img_in.DataTransfer += new STNodeOptionEventHandler(m_op_img_in_DataTransfer); + } + + void m_op_img_in_DataTransfer(object sender, STNodeOptionEventArgs e) { + //如果当前不是连接状态 或者 接受到的数据为空 + if (e.Status != ConnectionStatus.Connected || e.TargetOption.Data == null) { + m_op_img_out.TransferData(null); //向所有输出节点输出空数据 + m_op_img_r.TransferData(null); + m_op_img_g.TransferData(null); + m_op_img_b.TransferData(null); + m_img_draw = null; //需要绘制显示的图片置为空 + } else { + Bitmap bmp = (Bitmap)e.TargetOption.Data; //否则计算图片的RGB图像 + Bitmap bmp_r = new Bitmap(bmp.Width, bmp.Height); + Bitmap bmp_g = new Bitmap(bmp.Width, bmp.Height); + Bitmap bmp_b = new Bitmap(bmp.Width, bmp.Height); + BitmapData bmpData = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + BitmapData bmpData_r = bmp_r.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); + BitmapData bmpData_g = bmp_g.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); + BitmapData bmpData_b = bmp_b.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); + byte[] byColor = new byte[bmpData.Height * bmpData.Stride]; + byte[] byColor_r = new byte[byColor.Length]; + byte[] byColor_g = new byte[byColor.Length]; + byte[] byColor_b = new byte[byColor.Length]; + System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, byColor, 0, byColor.Length); + for (int y = 0; y < bmpData.Height; y++) { + int ny = y * bmpData.Stride; + for (int x = 0; x < bmpData.Width; x++) { + int nx = x << 2; + byColor_b[ny + nx] = byColor[ny + nx]; + byColor_g[ny + nx + 1] = byColor[ny + nx + 1]; + byColor_r[ny + nx + 2] = byColor[ny + nx + 2]; + byColor_r[ny + nx + 3] = byColor_g[ny + nx + 3] = byColor_b[ny + nx + 3] = byColor[ny + nx + 3]; + } + } + bmp.UnlockBits(bmpData); + System.Runtime.InteropServices.Marshal.Copy(byColor_r, 0, bmpData_r.Scan0, byColor_r.Length); + System.Runtime.InteropServices.Marshal.Copy(byColor_g, 0, bmpData_g.Scan0, byColor_g.Length); + System.Runtime.InteropServices.Marshal.Copy(byColor_b, 0, bmpData_b.Scan0, byColor_b.Length); + bmp_r.UnlockBits(bmpData_r); + bmp_g.UnlockBits(bmpData_g); + bmp_b.UnlockBits(bmpData_b); + m_op_img_out.TransferData(bmp); //out选项 输出原图 + m_op_img_r.TransferData(bmp_r); //R选项输出R图 + m_op_img_g.TransferData(bmp_g); + m_op_img_b.TransferData(bmp_b); + m_img_draw = bmp; //需要绘制显示的图片 + } + } + + protected override void OnDrawBody(DrawingTools dt) { + base.OnDrawBody(dt); + Graphics g = dt.Graphics; + Rectangle rect = new Rectangle(this.Left + 10, this.Top + 30, 120, 80); + g.FillRectangle(Brushes.Gray, rect); + if (m_img_draw != null) g.DrawImage(m_img_draw, rect); + } + } +} diff --git a/WinNodeEditorDemo/ImageNode/ImageInputNode.cs b/WinNodeEditorDemo/ImageNode/ImageInputNode.cs new file mode 100644 index 0000000..be7fb01 --- /dev/null +++ b/WinNodeEditorDemo/ImageNode/ImageInputNode.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ST.Library.UI.NodeEditor; +using System.Drawing; +using System.Windows.Forms; +using System.Reflection; + +namespace WinNodeEditorDemo.ImageNode +{ + + [STNode("Image", "Crystal_lz", "2212233137@qq.com", "st233.com", "Image Node")] + public class ImageInputNode : ImageBaseNode + { + private string _FileName;//默认的DescriptorType不支持文件路径的选择 所以需要扩展 + [STNodeProperty("InputImage", "Click to select a image", DescriptorType = typeof(OpenFileDescriptor))] + public string FileName { + get { return _FileName; } + set { + Image img = null; //当文件名被设置时 加载图片并 向输出节点输出 + if (!string.IsNullOrEmpty(value)) { + img = Image.FromFile(value); + } + if (m_img_draw != null) m_img_draw.Dispose(); + m_img_draw = img; + _FileName = value; + m_op_img_out.TransferData(m_img_draw, true); + this.Invalidate(); + } + } + + protected override void OnCreate() { + base.OnCreate(); + this.Title = "ImageInput"; + } + + protected override void OnDrawBody(DrawingTools dt) { + base.OnDrawBody(dt); + Graphics g = dt.Graphics; + Rectangle rect = new Rectangle(this.Left + 10, this.Top + 30, 140, 80); + g.FillRectangle(Brushes.Gray, rect); + if (m_img_draw != null) g.DrawImage(m_img_draw, rect); + } + } + /// + /// 对默认Descriptor进行扩展 使得支持文件路径选择 + /// + 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); + } + } +} diff --git a/WinNodeEditorDemo/NumberNode/NumberAddNode.cs b/WinNodeEditorDemo/NumberNode/NumberAddNode.cs new file mode 100644 index 0000000..188be18 --- /dev/null +++ b/WinNodeEditorDemo/NumberNode/NumberAddNode.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; + +namespace WinNodeEditorDemo.NumberNode +{ + [STNode("/Number/", "Crystal_lz", "2212233137@qq.com", "www.st233.com", "This node can get two numbers add result")] + public class NumberAddNode : NumberNode + { + private STNodeOption m_in_num1; + private STNodeOption m_in_num2; + private STNodeOption m_out_num; + private int m_nNum1, m_nNum2; + private StringFormat m_sf; + + protected override void OnCreate() { + base.OnCreate(); + this.Title = "NumberAdd"; + m_sf = new StringFormat(); + m_sf.LineAlignment = StringAlignment.Center; + m_in_num1 = new STNodeOption("", typeof(int), true);//只能有一个连线 + m_in_num2 = new STNodeOption("", typeof(int), true);//只能有一个连线 + m_out_num = new STNodeOption("", typeof(int), false);//可以多个连线 + this.InputOptions.Add(m_in_num1); + this.InputOptions.Add(m_in_num2); + this.OutputOptions.Add(m_out_num); + m_in_num1.DataTransfer += new STNodeOptionEventHandler(m_in_num_DataTransfer); + m_in_num2.DataTransfer += new STNodeOptionEventHandler(m_in_num_DataTransfer); + } + //当有数据传入时 + void m_in_num_DataTransfer(object sender, STNodeOptionEventArgs e) { + //判断连线是否是连接状态(建立连线 断开连线 都会触发该事件) + if (e.Status == ConnectionStatus.Connected) { + if (sender == m_in_num1) { + if (e.TargetOption.Data != null) m_nNum1 = (int)e.TargetOption.Data;//TargetOption为触发此事件的Option + } else { + if (e.TargetOption.Data != null) m_nNum2 = (int)e.TargetOption.Data; + } + } else { + if (sender == m_in_num1) m_nNum1 = 0; else m_nNum2 = 0; + } + //向输出选项上的所有连线传输数据 输出选项上的所有连线都会触发 DataTransfer 事件 + m_out_num.TransferData(m_nNum1 + m_nNum2); //m_out_num.Data 将被自动设置 + this.Invalidate(); + } + /// + /// 当绘制选项文本时候 将数字绘制 因为STNodeOption.Text被protected修饰 STNode无法进行设置 + /// 因为作者并不建议对已经添加在STNode上的选项进行修改 尤其是在AutoSize被设置的情况下 + /// 若有需求 应当采用其他方式 比如:重绘 或者添加STNodeControl来显示变化的文本信息 + /// + /// 绘制工具 + /// 需要绘制的选项 + 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); + } + } +} diff --git a/WinNodeEditorDemo/NumberNode/NumberInputNode.cs b/WinNodeEditorDemo/NumberNode/NumberInputNode.cs new file mode 100644 index 0000000..335babe --- /dev/null +++ b/WinNodeEditorDemo/NumberNode/NumberInputNode.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; + +namespace WinNodeEditorDemo.NumberNode +{ + /// + /// 此节点通过Number属性提供一个整数的输入 + /// + [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; + } + /// + /// 当绘制选项文本时候 将数字绘制 因为STNodeOption.Text被protected修饰 STNode无法进行设置 + /// 因为作者并不建议对已经添加在STNode上的选项进行修改 尤其是在AutoSize被设置的情况下 + /// 若有需求 应当采用其他方式 比如:重绘 或者添加STNodeControl来显示变化的文本信息 + /// + /// 绘制工具 + /// 需要绘制的选项 + 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); + } + } +} diff --git a/WinNodeEditorDemo/NumberNode/NumberNode.cs b/WinNodeEditorDemo/NumberNode/NumberNode.cs new file mode 100644 index 0000000..986fb42 --- /dev/null +++ b/WinNodeEditorDemo/NumberNode/NumberNode.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; +using System.Drawing; + +namespace WinNodeEditorDemo.NumberNode +{ + /// + /// Number节点基类 用于确定节点风格 标题颜色 以及 数据类型颜色 + /// + 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); + } + } +} diff --git a/WinNodeEditerTest/Program.cs b/WinNodeEditorDemo/Program.cs old mode 100755 new mode 100644 similarity index 83% rename from WinNodeEditerTest/Program.cs rename to WinNodeEditorDemo/Program.cs index cee5a20..8393bd3 --- a/WinNodeEditerTest/Program.cs +++ b/WinNodeEditorDemo/Program.cs @@ -1,21 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows.Forms; - -namespace ST.Library.UI -{ - static class Program - { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - new Demo_Image.FrmImage().Show(); - Application.Run(new Form1()); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; + +namespace WinNodeEditorDemo +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/WinNodeEditerTest/Properties/AssemblyInfo.cs b/WinNodeEditorDemo/Properties/AssemblyInfo.cs old mode 100755 new mode 100644 similarity index 82% rename from WinNodeEditerTest/Properties/AssemblyInfo.cs rename to WinNodeEditorDemo/Properties/AssemblyInfo.cs index 49f331d..c6099dd --- a/WinNodeEditerTest/Properties/AssemblyInfo.cs +++ b/WinNodeEditorDemo/Properties/AssemblyInfo.cs @@ -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("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")] +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")] diff --git a/WinNodeEditerTest/Properties/Resources.Designer.cs b/WinNodeEditorDemo/Properties/Resources.Designer.cs old mode 100755 new mode 100644 similarity index 86% rename from WinNodeEditerTest/Properties/Resources.Designer.cs rename to WinNodeEditorDemo/Properties/Resources.Designer.cs index bad3293..307b927 --- a/WinNodeEditerTest/Properties/Resources.Designer.cs +++ b/WinNodeEditorDemo/Properties/Resources.Designer.cs @@ -1,63 +1,64 @@ -//------------------------------------------------------------------------------ -// -// 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. -// -//------------------------------------------------------------------------------ - -namespace ST.Library.UI.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // 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() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [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; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - } -} +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace WinNodeEditorDemo.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // 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() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if ((resourceMan == null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WinNodeEditorDemo.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/WinNodeEditerTest/Properties/Resources.resx b/WinNodeEditorDemo/Properties/Resources.resx old mode 100755 new mode 100644 similarity index 97% rename from WinNodeEditerTest/Properties/Resources.resx rename to WinNodeEditorDemo/Properties/Resources.resx index ffecec8..af7dbeb --- a/WinNodeEditerTest/Properties/Resources.resx +++ b/WinNodeEditorDemo/Properties/Resources.resx @@ -1,117 +1,117 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/WinNodeEditerTest/Properties/Settings.Designer.cs b/WinNodeEditorDemo/Properties/Settings.Designer.cs old mode 100755 new mode 100644 similarity index 88% rename from WinNodeEditerTest/Properties/Settings.Designer.cs rename to WinNodeEditorDemo/Properties/Settings.Designer.cs index 9187fcb..52bf6f2 --- a/WinNodeEditerTest/Properties/Settings.Designer.cs +++ b/WinNodeEditorDemo/Properties/Settings.Designer.cs @@ -1,26 +1,28 @@ -//------------------------------------------------------------------------------ -// -// 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. -// -//------------------------------------------------------------------------------ - -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; - } - } - } -} +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace WinNodeEditorDemo.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/WinNodeEditerTest/Properties/Settings.settings b/WinNodeEditorDemo/Properties/Settings.settings old mode 100755 new mode 100644 similarity index 97% rename from WinNodeEditerTest/Properties/Settings.settings rename to WinNodeEditorDemo/Properties/Settings.settings index abf36c5..3964565 --- a/WinNodeEditerTest/Properties/Settings.settings +++ b/WinNodeEditorDemo/Properties/Settings.settings @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/WinNodeEditorDemo/STNodeHub.cs b/WinNodeEditorDemo/STNodeHub.cs new file mode 100644 index 0000000..70ff160 --- /dev/null +++ b/WinNodeEditorDemo/STNodeHub.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using ST.Library.UI.NodeEditor; + +namespace WinNodeEditorDemo +{ + /// + /// 类库自带的STNodeHub并未被STNodeAttribute标记 无法被STNodeTreeView显示 所以需要扩展 + /// + [STNode("/", "Crystal_lz", "2212233137@qq.com", "st233.com", "This is single Hub")] + public class STNodeHubSingle : STNodeHub + { + public STNodeHubSingle() + : base(true) { + this.Title = "S_HUB"; + } + } + /// + /// 类库自带的STNodeHub并未被STNodeAttribute标记 无法被STNodeTreeView显示 所以需要扩展 + /// + [STNode("/", "Crystal_lz", "2212233137@qq.com", "st233.com", "This multi is Hub")] + public class STNodeHubMulti : STNodeHub + { + public STNodeHubMulti() + : base(false) { + this.Title = "M_HUB"; + } + } +} diff --git a/WinNodeEditorDemo/ToolStripRendererEx.cs b/WinNodeEditorDemo/ToolStripRendererEx.cs new file mode 100644 index 0000000..64943d8 --- /dev/null +++ b/WinNodeEditorDemo/ToolStripRendererEx.cs @@ -0,0 +1,80 @@ +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); + //} + } +} diff --git a/WinNodeEditerTest/WinNodeEditerTest.csproj b/WinNodeEditorDemo/WinNodeEditorDemo.csproj old mode 100755 new mode 100644 similarity index 70% rename from WinNodeEditerTest/WinNodeEditerTest.csproj rename to WinNodeEditorDemo/WinNodeEditorDemo.csproj index b13a2df..6600de5 --- a/WinNodeEditerTest/WinNodeEditerTest.csproj +++ b/WinNodeEditorDemo/WinNodeEditorDemo.csproj @@ -1,120 +1,111 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {8B0A4516-45C9-4DE9-B947-64E8DA8A72A6} - Exe - Properties - ST.Library.UI - WinNodeEditerTest - v4.0 - 512 - - - - x86 - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - x86 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - Form - - - FrmImage.cs - - - - - - Form - - - Form1.cs - - - Form - - - Form2.cs - - - - - - FrmImage.cs - - - Form1.cs - - - Form2.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - True - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - {EFFCC270-4999-4077-A543-56CCCCE92147} - ST.Library.UI - - - - + + + + Debug + x86 + 8.0.30703 + 2.0 + {4E1829B5-2160-48F5-ABD6-11914A6A25DD} + WinExe + Properties + WinNodeEditorDemo + WinNodeEditorDemo + v3.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + Form + + + + + + + + Form + + + Form1.cs + + + + + + + + + + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + {EFFCC270-4999-4077-A543-56CCCCE92147} + ST.Library.UI + + + + + \ No newline at end of file diff --git a/WinNodeEditorTest.7z b/WinNodeEditorTest.7z new file mode 100644 index 0000000..8a22914 Binary files /dev/null and b/WinNodeEditorTest.7z differ diff --git a/WinNodeEditerTest.sln b/WinNodeEditorTest.sln old mode 100755 new mode 100644 similarity index 69% rename from WinNodeEditerTest.sln rename to WinNodeEditorTest.sln index e538a82..aca14ba --- a/WinNodeEditerTest.sln +++ b/WinNodeEditorTest.sln @@ -1,42 +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 + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +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 +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 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/WinNodeEditorTest.suo b/WinNodeEditorTest.suo new file mode 100644 index 0000000..32360ad Binary files /dev/null and b/WinNodeEditorTest.suo differ diff --git a/docs/STNodeEditor.zip b/docs/STNodeEditor.zip deleted file mode 100755 index c8eaf80..0000000 Binary files a/docs/STNodeEditor.zip and /dev/null differ diff --git a/docs/api.html b/docs/api.html new file mode 100644 index 0000000..dd6a627 --- /dev/null +++ b/docs/api.html @@ -0,0 +1,1681 @@ + + + + + +{THIS IS TITLE} + + + + + +
+ +
+
+

STNodeEditor

+

Properties

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
CanvasOffsetXfloat获取画布原点相对于控件 X 方向上的偏移位置
CanvasOffsetYfloat获取画布原点相对于控件 Y 方向上的偏移位置
CanvasOffsetPointF获取画布原点相对于控件偏移位置
CanvasValidBoundsRectangle获取画布中的有被用到的有效区域
CanvasScalefloat获取画布的缩放比例
Curvaturefloat获取或设置 Option 之间连线的曲度
ShowMagnetbool获取或设置移动画布中 Node 时候 是否启用磁铁效果
ShowBorderbool获取或设置 移动画布中是否显示 Node 边框
ShowGridbool获取或设置画布中是否绘制背景网格线条
ShowLocationbool获取或设置是否在画布边缘显示超出视角的 Node 位置信息
NodesSTNodeCollection获取画布中 Node 集合
ActiveNodeSTNode获取当前画布中被选中的活动 Node
HoverNodeSTNode获取当前画布中鼠标悬停的 Node
GridColorColor获取或设置绘制画布背景时 网格线条颜色
BorderColorColor获取或设置画布中 Node 边框颜色
BorderHoverColorColor获取或设置画布中悬停 Node 边框颜色
BorderSelectedColorColor获取或设置画布中选中 Node 边框颜色
BorderActiveColorColor获取或设置画布中活动 Node 边框颜色
MarkForeColorColor获取或设置画布绘制 Node 标记详情采用的前景色
MarkBackColorColor获取或设置画布绘制 Node 标记详情采用的背景色
MagnetColorColor获取或设置画布中移动 Node 时候 磁铁标记颜色
SelectedRectangleColorColor获取或设置画布中选择矩形区域的颜色
HighLineColorColor获取或设置画布中高亮连线的颜色
LocationForeColorColor获取或设置画布中边缘位置提示区域前景色
LocationBackColorColor获取或设置画布中边缘位置提示区域背景色
UnknownTypeColorColor获取或设置画布中当 Node 中 Option 数据类型无法确定时应当使用的颜色
+
+

Protected fields

+
+ + + + + + + + + + + +
NameTypeDescription
m_pt_in_controlPoint当前鼠标在控件中的实时位置
m_pt_in_canvasPointF当前鼠标在画布中的实时位置
m_pt_down_in_controlPoint鼠标点击时在控件上的位置
m_pt_down_in_canvasPointF鼠标点击时在画布中的位置
m_pt_canvas_oldPointF用于鼠标点击移动画布时候 鼠标点下时候的画布坐标位置
m_pt_dot_downPoint用于保存连线过程中保存点下 Option 的起点坐标
m_option_downSTNodeOption用于保存连线过程中鼠标点下的起点Option 当MouseUP时候 确定是否连接此节点
m_node_downSTNode当前鼠标点下的 STNode
m_mouse_in_controlbool当前鼠标是否位于控件中
+
+

Events

+
+ + + + + + + + + + + + + +
NameDescription
ActiveChanged活动的节点发生变化时候发生
SelectedChanged选择的节点发生变化时候发生
HoverChanged悬停的节点发生变化时候发生
NodeAdded当节点被添加时候发生
NodeRemoved当节点被移除时候发生
CanvasMoved移动画布原点时候发生
CanvasScaled缩放画布时候发生
OptionConnected连接节点选项时候发生
OptionConnecting正在连接节点选项时候发生
OptionDisConnected断开节点选项时候发生
OptionDisConnecting正在断开节点选项时候发生
+
+

Virtual method

+OnDrawGrid +
+
/// <summary>
+/// 当绘制背景网格线时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="nWidth">需要绘制宽度</param>
+/// <param name="nHeight">需要绘制高度</param>
+protected virtual void OnDrawGrid(DrawingTools dt, int nWidth, int nHeight);
+
+OnDrawNode +
+
/// <summary>
+/// 当绘制 Node 时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="rect">可视画布区域大小</param>
+protected virtual void OnDrawNode(DrawingTools dt, Rectangle rect);
+
+OnDrawNodeBorder +
+
/// <summary>
+/// 当绘制 Node 边框时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="node">目标node</param>
+protected virtual void OnDrawNodeBorder(DrawingTools dt, STNode node);
+
+OnDrawConnectedLine +
+
/// <summary>
+/// 当绘制已连接路径时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+protected virtual void OnDrawConnectedLine(DrawingTools dt);
+
+OnDrawMark +
+
/// <summary>
+/// 当绘制 Mark 详情信息时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+protected virtual void OnDrawMark(DrawingTools dt);
+
+OnDrawMagnet +
+
/// <summary>
+/// 当移动 Node 时候 需要显示对齐参考线时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="mi">匹配的磁铁信息</param>
+protected virtual void OnDrawMagnet(DrawingTools dt, MagnetInfo mi);
+
+OnDrawSelectedRectangle +
+
/// <summary>
+/// 绘制选择的矩形区域
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="rectf">位于控件上的矩形区域</param>
+protected virtual void OnDrawSelectedRectangle(DrawingTools dt, RectangleF rectf);
+
+OnDrawNodeOutLocation +
+
/// <summary>
+/// 绘制超出视觉区域的 Node 位置提示信息
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="sz">提示框边距</param>
+/// <param name="lstPts">超出视觉区域的 Node 位置信息</param>
+protected virtual void OnDrawNodeOutLocation(DrawingTools dt, Size sz, List<Point> lstPts);
+
+OnDrawAlert +
+
/// <summary>
+/// 绘制提示信息
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="rect">需要绘制区域</param>
+/// <param name="strText">需要绘制文本</param>
+/// <param name="foreColor">信息前景色</param>
+/// <param name="backColor">信息背景色</param>
+/// <param name="al">信息位置</param>
+protected virtual void OnDrawAlert(DrawingTools dt, Rectangle rect, string strText, Color foreColor, Color backColor, AlertLocation al);
+
+GetAlertRectangle +
+
/// <summary>
+/// 获取提示信息需要绘制的矩形区域
+/// </summary>
+/// <param name="g">绘图表面</param>
+/// <param name="strText">需要绘制文本</param>
+/// <param name="al">信息位置</param>
+/// <returns>矩形区域</returns>
+protected virtual Rectangle GetAlertRectangle(Graphics g, string strText, AlertLocation al);
+
+

public method

+FindNodeFromPoint +
+
/// <summary>
+/// 通过画布坐标进行寻找
+/// </summary>
+/// <param name="pt">画布中的坐标</param>
+/// <returns>寻找到的数据</returns>
+public NodeFindInfo FindNodeFromPoint(PointF pt);
+
+GetSelectedNode +
+
/// <summary>
+/// 获取已经被选择的 Node 集合
+/// </summary>
+/// <returns>Node 集合</returns>
+public STNode[] GetSelectedNode();
+
+CanvasToControl +
+
/// <summary>
+/// 将画布坐标转换为控件坐标
+/// </summary>
+/// <param name="number">参数</param>
+/// <param name="isX">是否为 X 坐标</param>
+/// <returns>转换后的坐标</returns>
+public float CanvasToControl(float number, bool isX);
+
+CanvasToControl +
+
/// <summary>
+/// 将画布坐标转换为控件坐标
+/// </summary>
+/// <param name="pt">坐标</param>
+/// <returns>转换后的坐标</returns>
+public PointF CanvasToControl(PointF pt);
+
+CanvasToControl +
+
/// <summary>
+/// 将画布坐标转换为控件坐标
+/// </summary>
+/// <param name="pt">坐标</param>
+/// <returns>转换后的坐标</returns>
+public Point CanvasToControl(Point pt);
+
+CanvasToControl +
+
/// <summary>
+/// 将画布坐标转换为控件坐标
+/// </summary>
+/// <param name="rect">矩形区域</param>
+/// <returns>转换后的矩形区域</returns>
+public Rectangle CanvasToControl(Rectangle rect);
+
+CanvasToControl +
+
/// <summary>
+/// 将画布坐标转换为控件坐标
+/// </summary>
+/// <param name="rect">矩形区域</param>
+/// <returns>转换后的矩形区域</returns>
+public RectangleF CanvasToControl(RectangleF rect);
+
+ControlToCanvas +
+
/// <summary>
+/// 将控件坐标转换为画布坐标
+/// </summary>
+/// <param name="number">参数</param>
+/// <param name="isX">是否为 X 坐标</param>
+/// <returns>转换后的坐标</returns>
+public float ControlToCanvas(float number, bool isX);
+
+ControlToCanvas +
+
/// <summary>
+/// 将控件坐标转换为画布坐标
+/// </summary>
+/// <param name="pt">坐标</param>
+/// <returns>转换后的坐标</returns>
+public Point ControlToCanvas(Point pt);
+
+ControlToCanvas +
+
/// <summary>
+/// 将控件坐标转换为画布坐标
+/// </summary>
+/// <param name="pt">坐标</param>
+/// <returns>转换后的坐标</returns>
+public PointF ControlToCanvas(PointF pt);
+
+ControlToCanvas +
+
/// <summary>
+/// 将控件坐标转换为画布坐标
+/// </summary>
+/// <param name="rect">矩形区域</param>
+/// <returns>转换后的区域</returns>
+public Rectangle ControlToCanvas(Rectangle rect);
+
+ControlToCanvas +
+
/// <summary>
+/// 将控件坐标转换为画布坐标
+/// </summary>
+/// <param name="rect">矩形区域</param>
+/// <returns>转换后的区域</returns>
+public RectangleF ControlToCanvas(RectangleF rect);
+
+MoveCanvas +
+
/// <summary>
+/// 移动画布原点坐标到指定的控件坐标位置
+/// 当不存在 Node 时候 无法移动
+/// </summary>
+/// <param name="x">X 坐标</param>
+/// <param name="y">Y 坐标</param>
+/// <param name="bAnimation">移动过程中是否启动动画效果</param>
+/// <param name="ma">指定需要修改的坐标参数</param>
+public void MoveCanvas(float x, float y, bool bAnimation, CanvasMoveArgs ma);
+
+ScaleCanvas +
+
/// <summary>
+/// 缩放画布
+/// 当不存在 Node 时候 无法缩放
+/// </summary>
+/// <param name="f">缩放比例</param>
+/// <param name="x">缩放中心X位于控件上的坐标</param>
+/// <param name="y">缩放中心Y位于控件上的坐标</param>
+public void ScaleCanvas(float f, float x, float y);
+
+GetConnectionInfo +
+
/// <summary>
+/// 获取当前已连接的 Option 对应关系
+/// </summary>
+/// <returns>连接信息集合</returns>
+public ConnectionInfo[] GetConnectionInfo();
+
+GetCanvasImage +
+
/// <summary>
+/// 获取画布中指定矩形区域图像
+/// </summary>
+/// <param name="rect">画布中指定的矩形区域</param>
+/// <returns>图像</returns>
+public Image GetCanvasImage(Rectangle rect);
+
+GetCanvasImage +
+
/// <summary>
+/// 获取画布中指定矩形区域图像
+/// </summary>
+/// <param name="rect">画布中指定的矩形区域</param>
+/// <param name="fScale">缩放比例</param>
+/// <returns>图像</returns>
+public Image GetCanvasImage(Rectangle rect, float fScale);
+
+SaveCanvas +
+
/// <summary>
+/// 保存画布中的类容到文件中
+/// </summary>
+/// <param name="strFileName">文件路径</param>
+public void SaveCanvas(string strFileName);
+
+SaveCanvas +
+
/// <summary>
+/// 保存画布中的类容到数据流
+/// </summary>
+/// <param name="s">数据流对象</param>
+public void SaveCanvas(Stream s);
+
+GetCanvasData +
+
/// <summary>
+/// 获取画布中内容二进制数据
+/// </summary>
+/// <returns>二进制数据</returns>
+public byte[] GetCanvasData();
+
+LoadAssembly +
+
/// <summary>
+/// 加载程序集
+/// </summary>
+/// <param name="strFiles">程序集集合</param>
+/// <returns>存在STNode类型的文件的个数</returns>
+public int LoadAssembly(string[] strFiles);
+
+LoadAssembly +
+
/// <summary>
+/// 加载程序集
+/// </summary>
+/// <param name="strFile">指定需要加载的文件</param>
+/// <returns>是否加载成功</returns>
+public bool LoadAssembly(string strFile);
+
+GetTypes +
+
/// <summary>
+/// 获取当前编辑器中已加载的Node类型
+/// </summary>
+/// <returns>类型集合</returns>
+public Type[] GetTypes();
+
+LoadCanvas +
+
/// <summary>
+/// 从文件中加载数据
+/// 注意: 此方法并不会清空画布中数据 而是数据叠加
+/// </summary>
+/// <param name="strFileName">文件路径</param>
+public void LoadCanvas(string strFileName);
+
+LoadCanvas +
+
/// <summary>
+/// 从二进制加载数据
+/// 注意: 此方法并不会清空画布中数据 而是数据叠加
+/// </summary>
+/// <param name="byData">二进制数据</param>
+public void LoadCanvas(byte[] byData);
+
+LoadCanvas +
+
/// <summary>
+/// 从数据流中加载数据
+/// 注意: 此方法并不会清空画布中数据 而是数据叠加
+/// </summary>
+/// <param name="s">数据流对象</param>
+public void LoadCanvas(Stream s);
+
+ShowAlert +
+
/// <summary>
+/// 在画布中显示提示信息
+/// </summary>
+/// <param name="strText">要显示的信息</param>
+/// <param name="foreColor">信息前景色</param>
+/// <param name="backColor">信息背景色</param>
+public void ShowAlert(string strText, Color foreColor, Color backColor);
+
+ShowAlert +
+
/// <summary>
+/// 在画布中显示提示信息
+/// </summary>
+/// <param name="strText">要显示的信息</param>
+/// <param name="foreColor">信息前景色</param>
+/// <param name="backColor">信息背景色</param>
+/// <param name="al">信息要显示的位置</param>
+public void ShowAlert(string strText, Color foreColor, Color backColor, AlertLocation al);
+
+ShowAlert +
+
/// <summary>
+/// 在画布中显示提示信息
+/// </summary>
+/// <param name="strText">要显示的信息</param>
+/// <param name="foreColor">信息前景色</param>
+/// <param name="backColor">信息背景色</param>
+/// <param name="nTime">信息持续时间</param>
+/// <param name="al">信息要显示的位置</param>
+/// <param name="bRedraw">是否立即重绘</param>
+public void ShowAlert(string strText, Color foreColor, Color backColor, int nTime, AlertLocation al, bool bRedraw);
+
+SetActiveNode +
+
/// <summary>
+/// 设置画布中活动的节点
+/// </summary>
+/// <param name="node">需要被设置为活动的节点</param>
+/// <returns>设置前的活动节点</returns>
+public STNode SetActiveNode(STNode node);
+
+AddSelectedNode +
+
/// <summary>
+/// 向画布中添加一个被选中的节点
+/// </summary>
+/// <param name="node">需要被选中的节点</param>
+/// <returns>是否添加成功</returns>
+public bool AddSelectedNode(STNode node);
+
+RemoveSelectedNode +
+
/// <summary>
+/// 向画布中移除一个被选中的节点
+/// </summary>
+/// <param name="node">需要被移除的节点</param>
+/// <returns>是移除否成功</returns>
+public bool RemoveSelectedNode(STNode node);
+
+SetTypeColor +
+
/// <summary>
+/// 向编辑器中添加默认数据类型颜色
+/// </summary>
+/// <param name="t">数据类型</param>
+/// <param name="clr">对应颜色</param>
+/// <returns>被设置后的颜色</returns>
+public Color SetTypeColor(Type t, Color clr);
+
+SetTypeColor +
+
/// <summary>
+/// 向编辑器中添加默认数据类型颜色
+/// </summary>
+/// <param name="t">数据类型</param>
+/// <param name="clr">对应颜色</param>
+/// <param name="bReplace">若已经存在是否替换颜色</param>
+/// <returns>被设置后的颜色</returns>
+public Color SetTypeColor(Type t, Color clr, bool bReplace);
+
+

STNode

+

Properties

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
OwnerSTNodeEditor获取当前 Node 所有者
IsSelectedbool获取或设置 Node 是否处于被选中状态
IsActivebool获取 Node 是否处于活动状态
TitleColorColor获取或设置标题背景颜色
MarkColorColor获取或设置标记信息背景颜色
ForeColorColor获取或设置当前 Node 前景色
BackColorColor获取或设置当前 Node 背景色
Titlestring获取或设置 Node 标题
Markstring获取或设置 Node 标记信息
Leftint获取或设置 Node 左边坐标
Topint获取或设置 Node 上边坐标
Widthint获取或设置 Node 宽度 当AutoSize被设置时 无法设置此值
Heightint获取或设置 Node 高度 当AutoSize被设置时 无法设置此值
ItemHeightint获取或设置 Node 每个选项的高度
AutoSizebool获取或设置 Node 是否自动计算宽高
Rightint获取 Node 右边边坐标
Bottomint获取 Node 下边坐标
RectangleRectangle获取 Node 矩形区域
TitleRectangleRectangle获取 Node 标题矩形区域
MarkRectangleRectangle获取 Node 标记矩形区域
TitleHeightint获取或设置 Node 标题高度
InputOptionsCountint获取输入选项集合个数
OutputOptionsCountint获取输出选项个数
ControlsCountint获取 Node 所包含的控件集合个数
LocationPoint获取 Node 坐标位置
SizeSize获取 Node 大小
LockOptionbool获取或设置是否锁定Option选项 锁定后不在接受连接
LockLocationbool获取或设置是否锁定Node位置 锁定后不可移动
ContextMenuStripContextMenuStrip获取或设置当前Node 上下文菜单
Tagobject获取或设置用户自定义保存的数据
GuidGuid获取全局唯一标识
LetGetOptionsbool获取或设置是否允许外部访问STNodeOption
+
+

Protected fields

+
+ + + + + +
NameTypeDescription
m_ctrl_activeSTNodeControl当前Node中 活动的控件
m_ctrl_hoverSTNodeControl当前Node中 悬停的控件
m_ctrl_downSTNodeControl当前Node中 鼠标点下的控件
+
+

Virtual method

+OnCreate +
+
/// <summary>
+/// 当Node被构造时候发生
+/// </summary>
+protected virtual void OnCreate();
+
+OnDrawNode +
+
/// <summary>
+/// 绘制整个Node
+/// </summary>
+/// <param name="dt">绘制工具</param>
+protected internal virtual void OnDrawNode(DrawingTools dt);
+
+OnDrawTitle +
+
/// <summary>
+/// 绘制Node标题部分
+/// </summary>
+/// <param name="dt">绘制工具</param>
+protected virtual void OnDrawTitle(DrawingTools dt);
+
+OnDrawBody +
+
/// <summary>
+/// 绘制Node主体部分 除去标题部分
+/// </summary>
+/// <param name="dt">绘制工具</param>
+protected virtual void OnDrawBody(DrawingTools dt);
+
+OnDrawMark +
+
/// <summary>
+/// 绘制标记信息
+/// </summary>
+/// <param name="dt">绘制工具</param>
+protected internal virtual void OnDrawMark(DrawingTools dt);
+
+OnDrawOptionDot +
+
/// <summary>
+/// 绘制选项连线的点
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="op">指定的选项</param>
+protected virtual void OnDrawOptionDot(DrawingTools dt, STNodeOption op);
+
+OnDrawOptionText +
+
/// <summary>
+/// 绘制选项的文本
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="op">指定的选项</param>
+protected virtual void OnDrawOptionText(DrawingTools dt, STNodeOption op);
+
+OnSetOptionDotLocation +
+
/// <summary>
+/// 当计算Option连线点位置时候发生
+/// </summary>
+/// <param name="op">需要计算的Option</param>
+/// <param name="pt">自动计算出的位置</param>
+/// <param name="nIndex">当前Option的索引</param>
+/// <returns>新的位置</returns>
+protected virtual Point OnSetOptionDotLocation(STNodeOption op, Point pt, int nIndex);
+
+OnSetOptionTextRectangle +
+
/// <summary>
+/// 当计算Option文本区域时候发生
+/// </summary>
+/// <param name="op">需要计算的Option</param>
+/// <param name="rect">自动计算出的区域</param>
+/// <param name="nIndex">当前Option的索引</param>
+/// <returns>新的区域</returns>
+protected virtual Rectangle OnSetOptionTextRectangle(STNodeOption op, Rectangle rect, int nIndex);
+
+GetDefaultNodeSize +
+
/// <summary>
+/// 获取当前STNode所需要的默认大小
+/// 返回的大小并不会限制绘制区域 任然可以在此区域之外绘制
+/// 但是并不会被STNodeEditor所接受 并触发对应事件
+/// </summary>
+/// <param name="g">绘图面板</param>
+/// <returns>计算出来的大小</returns>
+protected virtual Size GetDefaultNodeSize(Graphics g);
+
+OnBuildMarkRectangle +
+
/// <summary>
+/// 计算当前Mark所需要的矩形区域
+/// 返回的大小并不会限制绘制区域 任然可以在此区域之外绘制
+/// 但是并不会被STNodeEditor所接受 并触发对应事件
+/// </summary>
+/// <param name="g">绘图面板</param>
+/// <returns>计算后的区域</returns>
+protected virtual Rectangle OnBuildMarkRectangle(Graphics g);
+
+OnSaveNode +
+
/// <summary>
+/// 当需要保存时候 此Node有哪些需要额外保存的数据
+/// 注意: 保存时并不会进行序列化 还原时候仅重新通过空参数构造器创建此Node
+/// 然后调用 OnLoadNode() 将保存的数据进行还原
+/// </summary>
+/// <param name="dic">需要保存的数据</param>
+protected virtual void OnSaveNode(Dictionary<string, byte[]> dic);
+
+OnLoadNode +
+
/// <summary>
+/// 当还原该节点时候会将 OnSaveNode() 所返回的数据重新传入此函数
+/// </summary>
+/// <param name="dic">保存时候的数据</param>
+protected internal virtual void OnLoadNode(Dictionary<string, byte[]> dic);
+
+OnEditorLoadCompleted +
+
/// <summary>
+/// 当编辑器加载完成所有的节点时候发生
+/// </summary>
+protected internal virtual void OnEditorLoadCompleted();
+
+SetOptionText +
+
/// <summary>
+/// 设置Option的文本信息
+/// </summary>
+/// <param name="op">目标Option</param>
+/// <param name="strText">文本</param>
+/// <returns>是否成功</returns>
+protected bool SetOptionText(STNodeOption op, string strText);
+
+SetOptionTextColor +
+
/// <summary>
+/// 设置Option文本信息颜色
+/// </summary>
+/// <param name="op">目标Option</param>
+/// <param name="clr">颜色</param>
+/// <returns>是否成功</returns>
+protected bool SetOptionTextColor(STNodeOption op, Color clr);
+
+SetOptionDotColor +
+
/// <summary>
+/// 设置Option连线点颜色
+/// </summary>
+/// <param name="op">目标Option</param>
+/// <param name="clr">颜色</param>
+/// <returns>是否成功</returns>
+protected bool SetOptionDotColor(STNodeOption op, Color clr);
+
+OnOwnerChanged +
+
/// <summary>
+/// 当所有者发生改变时候发生
+/// </summary>
+protected virtual void OnOwnerChanged();
+
+OnSelectedChanged +
+
/// <summary>
+/// 当选中状态改变时候发生
+/// </summary>
+protected virtual void OnSelectedChanged();
+
+OnActiveChanged +
+
/// <summary>
+/// 当活动状态改变时候发生
+/// </summary>
+protected virtual void OnActiveChanged();
+
+SetOptionsLocation +
+
/// <summary>
+/// 计算每个Option的位置
+/// </summary>
+protected virtual void SetOptionsLocation();
+
+

public method

+Invalidate +
+
/// <summary>
+/// 重绘Node
+/// </summary>
+public void Invalidate();
+
+Invalidate +
+
/// <summary>
+/// 重绘 Node 指定区域
+/// </summary>
+/// <param name="rect">Node 指定区域</param>
+public void Invalidate(Rectangle rect);
+
+GetInputOptions +
+
/// <summary>
+/// 获取此Node所包含的输入Option集合
+/// </summary>
+/// <returns>Option集合</returns>
+public STNodeOption[] GetInputOptions();
+
+GetOutputOptions +
+
/// <summary>
+/// 获取此Node所包含的输出Option集合
+/// </summary>
+/// <returns>Option集合</returns>
+public STNodeOption[] GetOutputOptions();
+
+SetSelected +
+
/// <summary>
+/// 设置Node的选中状态
+/// </summary>
+/// <param name="bSelected">是否选中</param>
+/// <param name="bRedraw">是否重绘</param>
+public void SetSelected(bool bSelected, bool bRedraw);
+
+

STNodeOption

+

Properties

+
+ + + + + + + + + + + + + + + + +
NameTypeDescription
OwnerSTNode获取当前 Option 所属的 Node
IsSinglebool获取当前 Option 是否仅能被连接一次
IsInputbool获取当前 Option 是否是输入选项
TextColorColor获取或设置当前 Option 文本颜色
DotColorColor获取或设置当前 Option 连接点的颜色
Textstring获取或设置当前 Option 显示文本 当AutoSize被设置时 无法修改此属性
DotLeftint获取当前 Option 连接点的左边坐标
DotTopint获取当前 Option 连接点的上边坐标
DotSizeint获取当前 Option 连接点的宽度
TextRectangleRectangle获取当前 Option 文本区域
Dataobject获取或者设置当前 Option 所包含的数据
DataTypeType获取当前 Option 数据类型
DotRectangleRectangle获取当前 Option 连接点的区域
ConnectionCountint获取当前 Option 被连接的个数
+
+

Events

+
+ + + + + + + +
NameDescription
Connected当被连接时候发生
Connecting当连接开始发生时发生
DisConnected当连接断开时候发生
DisConnecting当连接开始断开时发生
DataTransfer当有数据传递时候发生
+
+

Constructor

+Constructor +
+
/// <summary>
+/// 构造一个 Option
+/// </summary>
+/// <param name="strText">显示文本</param>
+/// <param name="dataType">数据类型</param>
+/// <param name="bSingle">是否为单连接</param>
+public STNodeOption(string strText, Type dataType, bool bSingle);
+
+

Virtual method

+Invalidate +
+
/// <summary>
+/// 重绘整个控件
+/// </summary>
+protected void Invalidate();
+
+ConnectingOption +
+
/// <summary>
+/// 当前 Option 开始连接目标 Option
+/// </summary>
+/// <param name="op">需要连接的 Option</param>
+/// <returns>是否允许继续操作</returns>
+protected virtual bool ConnectingOption(STNodeOption op);
+
+DisConnectingOption +
+
/// <summary>
+/// 当前 Option 开始断开目标 Option
+/// </summary>
+/// <param name="op">需要断开的 Option</param>
+/// <returns>是否允许继续操作</returns>
+protected virtual bool DisConnectingOption(STNodeOption op);
+
+

public method

+DisConnectionAll +
+
/// <summary>
+/// 断开当前 Option 的所有连接
+/// </summary>
+public void DisConnectionAll();
+
+GetConnectedOption +
+
/// <summary>
+/// 获取当前 Option 所连接的 Option 集合
+/// </summary>
+/// <returns>如果为null 则表示不存在所有者 否则返回集合</returns>
+public List<STNodeOption> GetConnectedOption();
+
+TransferData +
+
/// <summary>
+/// 向当前 Option 所连接的所有 Option 投递数据
+/// </summary>
+public void TransferData();
+
+TransferData +
+
/// <summary>
+/// 向当前 Option 所连接的所有 Option 投递数据
+/// </summary>
+/// <param name="data">需要投递的数据</param>
+public void TransferData(object data);
+
+TransferData +
+
/// <summary>
+/// 向当前 Option 所连接的所有 Option 投递数据
+/// </summary>
+/// <param name="data">需要投递的数据</param>
+/// <param name="bDisposeOld">是否释放旧数据</param>
+public void TransferData(object data, bool bDisposeOld);
+
+

STNodePropertyGrid

+

Properties

+
+ + + + + + + + + + + + + + +
NameTypeDescription
STNodeSTNode当前显示的STNode
ItemHoverColorColor获取或设置属性选项被鼠标悬停时候背景色
ItemSelectedColorColor获取或设置属性选项被选中时候背景色 当AutoColor被设置时此属性不能被设置
ItemValueBackColorColor获取或设置属性选项值背景色
TitleColorColor获取或设置默认标题背景色
ErrorColorColor获取或设置属性设置错误时候提示信息背景色
DescriptionColorColor获取或设置属性描述信息背景色
ShowTitlebool获取或设置是否显示节点标题
AutoColorbool获取或设置是否根据STNode自动设置控件高亮颜色
InfoFirstOnDrawbool获取或当节点被设置时候 是否优先绘制信息面板
ReadOnlyModelbool获取或设置当前属性编辑器是否处于只读模式
ScrollOffsetint获取当前滚动条高度
+
+

Protected fields

+
+ + + + + + + + + + + + + +
NameTypeDescription
m_rect_linkRectangle作者链接地址区域
m_rect_helpRectangle查看帮助按钮区域
m_rect_titleRectangle编辑器标题区域
m_rect_switchRectangle面板切换按钮区域
m_nOffsetYint控件在绘制过程中使用的垂直滚动偏移
m_nInfoOffsetYint保存的信息面板垂直滚动偏移
m_nPropertyOffsetYint保存的属性面板垂直滚动偏移
m_nVHeightint控件在绘制过程中使用的绘图区域总高度
m_nInfoVHeightint保存的信息面板需要的总高度
m_nPropertyVHeightint保存的属性面板需要的总高度
m_nInfoLeftint信息面板中Key显示需要的水平宽度
+
+

Virtual method

+OnPaint +
+
/// <summary>
+/// 当控件重绘时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected override void OnPaint(PaintEventArgs e);
+
+OnMouseMove +
+
/// <summary>
+/// 当鼠标在控件上移动时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected override void OnMouseMove(MouseEventArgs e);
+
+OnMouseDown +
+
/// <summary>
+/// 当鼠标在控件上点下时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected override void OnMouseDown(MouseEventArgs e);
+
+OnMouseUp +
+
/// <summary>
+/// 当鼠标在控件上抬起时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected override void OnMouseUp(MouseEventArgs e);
+
+OnMouseLeave +
+
/// <summary>
+/// 当鼠标离开控件时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected override void OnMouseLeave(EventArgs e);
+
+OnMouseWheel +
+
/// <summary>
+/// 当鼠标在控件上滚动滚轮时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected override void OnMouseWheel(MouseEventArgs e);
+
+OnResize +
+
/// <summary>
+/// 当控件尺寸发生改变时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected override void OnResize(EventArgs e);
+
+OnDrawPropertyItem +
+
/// <summary>
+/// 当绘制属性选项时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="item">目标属性选项描述器</param>
+/// <param name="nIndex">选项所在索引</param>
+protected virtual void OnDrawPropertyItem(DrawingTools dt, STNodePropertyDescriptor item, int nIndex);
+
+OnDrawTitle +
+
/// <summary>
+/// 绘制属性窗口标题
+/// </summary>
+/// <param name="dt">绘制工具</param>
+protected virtual void OnDrawTitle(DrawingTools dt);
+
+OnDrawDescription +
+
/// <summary>
+/// 当需要绘制属性描述信息时发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+protected virtual void OnDrawDescription(DrawingTools dt);
+
+OnDrawErrorInfo +
+
/// <summary>
+/// 当需要绘制错误信息时发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+protected virtual void OnDrawErrorInfo(DrawingTools dt);
+
+OnDrawInfo +
+
/// <summary>
+/// 当绘制节点信息时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+protected virtual void OnDrawInfo(DrawingTools dt);
+
+OnProcessPropertyMouseDown +
+
/// <summary>
+/// 当在属性面板鼠标点下时候发生
+/// </summary>
+/// <param name="e">鼠标事件参数</param>
+protected virtual void OnProcessPropertyMouseDown(MouseEventArgs e);
+
+OnProcessInfoMouseDown +
+
/// <summary>
+/// 当在信息面板鼠标点下时候发生
+/// </summary>
+/// <param name="e">鼠标事件参数</param>
+protected virtual void OnProcessInfoMouseDown(MouseEventArgs e);
+
+OnProcessPropertyMouseMove +
+
/// <summary>
+/// 当在属性面板鼠标移动时候发生
+/// </summary>
+/// <param name="e">鼠标事件参数</param>
+protected virtual void OnProcessPropertyMouseMove(MouseEventArgs e);
+
+OnProcessHelpMouseMove +
+
/// <summary>
+/// 当在信息面板鼠标移动时候发生
+/// </summary>
+/// <param name="e">鼠标事件参数</param>
+protected virtual void OnProcessHelpMouseMove(MouseEventArgs e);
+
+

public method

+SetNode +
+
/// <summary>
+/// 设置需要显示的STNode节点
+/// </summary>
+/// <param name="node">目标节点</param>
+public void SetNode(STNode node);
+
+SetInfoKey +
+
/// <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);
+
+SetErrorMessage +
+
/// <summary>
+/// 设置要显示的错误信息
+/// </summary>
+/// <param name="strText">错误信息</param>
+public void SetErrorMessage(string strText);
+
+

STNodeTreeView

+

Properties

+
+ + + + + + + + + + + + + + + +
NameTypeDescription
ItemBackColorColor获取或设置每行属性选项背景色
ItemHoverColorColor获取或设置属性选项被鼠标悬停时候背景色
TitleColorColor获取或设置顶部检索区域背景色
TextBoxColorColor获取或设置检索文本框的背景色
HightLightTextColorColor获取或设置检索时候高亮文本颜色
InfoButtonColorColor获取或设置信息显示按钮颜色 若设置AutoColor无法设置此属性值
FolderCountColorColor获取或设置统计个数的文本颜色
ShowFolderCountbool获取或设置是否统计STNode的个数
ShowInfoButtonbool获取或设置是否显示信息按钮
InfoPanelIsLeftLayoutbool获取或设置预览窗口是否是向左布局
AutoColorbool获取或设置控件中部分颜色来之对应的STNode的标题颜色
EditorSTNodeEditor获取节点预览时候使用的STNodeEditor
PropertyGridSTNodePropertyGrid获取节点预览时候使用的STNodePropertyGrid
+
+

Virtual method

+OnDrawSearch +
+
/// <summary>
+/// 当绘制检索文本区域时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+protected virtual void OnDrawSearch(DrawingTools dt);
+
+OnStartDrawItem +
+
/// <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);
+
+OnDrawItem +
+
/// <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);
+
+OnDrawSwitch +
+
/// <summary>
+/// 当绘制树节点展开与关闭开关时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="items">当前需要绘制的集合</param>
+protected virtual void OnDrawSwitch(DrawingTools dt, STNodeTreeCollection items);
+
+OnDrawItemText +
+
/// <summary>
+/// 当绘制树节点的文本时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="items">当前需要绘制的集合</param>
+/// <param name="rect">文本域所在矩形区域</param>
+protected virtual void OnDrawItemText(DrawingTools dt, STNodeTreeCollection items, Rectangle rect);
+
+OnDrawItemIcon +
+
/// <summary>
+/// 当绘制树节点图标时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+/// <param name="items">当前需要绘制的集合</param>
+/// <param name="rect">文本域所在矩形区域</param>
+protected virtual void OnDrawItemIcon(DrawingTools dt, STNodeTreeCollection items, Rectangle rect);
+
+

public method

+Search +
+
/// <summary>
+/// 在控件中检索STNode
+/// </summary>
+/// <param name="strText">需要检索的文本</param>
+public void Search(string strText);
+
+AddNode +
+
/// <summary>
+/// 向控件中添加一个STNode类型
+/// </summary>
+/// <param name="stNodeType">STNode类型</param>
+/// <returns>是否添加成功</returns>
+public bool AddNode(Type stNodeType);
+
+LoadAssembly +
+
/// <summary>
+/// 从文件中向控件添加STNode类型
+/// </summary>
+/// <param name="strFile">指定文件路径</param>
+/// <returns>添加成功个数</returns>
+public int LoadAssembly(string strFile);
+
+Clear +
+
/// <summary>
+/// 清空控件中所有STNode类型
+/// </summary>
+public void Clear();
+
+RemoveNode +
+
/// <summary>
+/// 向控件中移除一个STNode类型
+/// </summary>
+/// <param name="stNodeType">STNode类型</param>
+/// <returns>是否移除成功</returns>
+public bool RemoveNode(Type stNodeType);
+
+

STNodeTreeCollection

+

STNodeTreeCollection类被protected修饰 在STNodeTreeView内部 仅继承STNodeTreeView才能访问

+

Properties

+
+ + + + + + + + + + + + + + + +
NameTypeDescription
Namestring获取当前树节点显示名称
NameLowerstring获取当前树节点显示名称的小写字符串
STNodeTypeType获取当前树节点对应的STNode类型
ParentSTNodeTreeCollection获取当前树节点的父级树节点
STNodeCountint获取当前树节点下拥有的STNode类型个数
Pathstring获取当前树节点对应STNode类型在树控件中对应路径
IsOpenbool获取当前或设置树节点是否为打开状态
IsLibraryRootbool获取当前树节点是否为加载模块的根路劲节点
DisplayRectangleRectangle获取当前树节点在控件中的显示区域
SwitchRectangleRectangle获取当前树节点在控件中的开关按钮区域
InfoRectangleRectangle获取当前树节点在控件中的信息按钮区域
STNodeTypeColorColor获取当前树节点对应STNode类型的标题颜色
Countint获取当前树节点所包含子节点个数
+
+

Constructor

+Constructor +
+
/// <summary>
+/// 构造一颗树节点集合
+/// </summary>
+/// <param name="strName">当前树节点在控件中的显示名称</param>
+public STNodeTreeCollection(string strName);
+
+

public method

+Add +
+
/// <summary>
+/// 向当前树节点中添加一个子节点
+/// </summary>
+/// <param name="strName">节点显示名称</param>
+/// <returns>添加后的子节点集合</returns>
+public STNodeTreeCollection Add(string strName);
+
+Remove +
+
/// <summary>
+/// 向当前树节点中删除一个子集合
+/// </summary>
+/// <param name="strName">子集合名称</param>
+/// <param name="isAutoDelFolder">是否递归向上自动清空无用节点</param>
+/// <returns>是否删除成功</returns>
+public bool Remove(string strName, bool isAutoDelFolder);
+
+Clear +
+
/// <summary>
+/// 清空当前树节点中所有子节点
+/// </summary>
+public void Clear();
+
+GetKeys +
+
/// <summary>
+/// 获取当前树节点中所有的名称数组
+/// </summary>
+/// <returns></returns>
+public string[] GetKeys();
+
+Copy +
+
/// <summary>
+/// 拷贝当前树节点集合中所有数据
+/// </summary>
+/// <returns>拷贝的副本</returns>
+public STNodeTreeCollection Copy();
+
+GetEnumerator +
+
/// <summary>
+/// 返回 System.Collections.IEnumerator 的 Array
+/// </summary>
+/// <returns></returns>
+public IEnumerator GetEnumerator();
+
+

STNodeEditorPannel

+

Properties

+
+ + + + + + + + + + + + +
NameTypeDescription
LeftLayoutbool获取或设置是否是左边布局
SplitLineColorColor获取或这是分割线颜色
HandleLineColorColor获取或设置分割线手柄颜色
ShowScalebool获取或设置编辑器缩放时候显示比例
ShowConnectionStatusbool获取或设置节点连线时候是否显示状态
Xint获取或设置分割线水平宽度
Yint获取或设置分割线垂直高度
EditorSTNodeEditor获取面板中的STNodeEditor
TreeViewSTNodeTreeView获取面板中的STNodeTreeView
PropertyGridSTNodePropertyGrid获取面板中的STNodePropertyGrid
+
+

public method

+AddSTNode +
+
/// <summary>
+/// 向树控件中添加一个STNode
+/// </summary>
+/// <param name="stNodeType">STNode类型</param>
+/// <returns>是否添加成功</returns>
+public bool AddSTNode(Type stNodeType);
+
+LoadAssembly +
+
/// <summary>
+/// 从程序集中加载STNode
+/// </summary>
+/// <param name="strFileName">程序集路径</param>
+/// <returns>添加成功个数</returns>
+public int LoadAssembly(string strFileName);
+
+SetConnectionStatusText +
+
/// <summary>
+/// 设置编辑器显示连接状态的文本
+/// </summary>
+/// <param name="status">连接状态</param>
+/// <param name="strText">对应显示文本</param>
+/// <returns>旧文本</returns>
+public string SetConnectionStatusText(ConnectionStatus status, string strText);
+
+

STNodeAttribute

+

Properties

+
+ + + + + + + +
NameTypeDescription
Pathstring获取STNode节点期望在树形控件的路径
Authorstring获取STNode节点的作者名称
Mailstring获取STNode节点的作者邮箱
Linkstring获取STNode节点的作者链接
Descriptionstring获取STNode节点的描述信息
+
+

Constructor

+Constructor +
+
/// <summary>
+/// 构造一个STNode特性
+/// </summary>
+/// <param name="strPath">期望路径</param>
+public STNodeAttribute(string strPath) : this(strPath, null, null, null, null);
+
+Constructor +
+
/// <summary>
+/// 构造一个STNode特性
+/// </summary>
+/// <param name="strPath">期望路径</param>
+/// <param name="strDescription">描述信息</param>
+public STNodeAttribute(string strPath, string strDescription) : this(strPath, null, null, null, strDescription);
+
+Constructor +
+
/// <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);
+
+

STNodePropertyAttribute

+

Properties

+
+ + + + + +
NameTypeDescription
Namestring获取属性需要在属性编辑器上显示的名称
Descriptionstring获取属性需要在属性编辑器上显示的描述
DescriptorTypeType获取属性描述器类型
+
+

Constructor

+Constructor +
+
/// <summary>
+/// 构造一个STNode属性特性
+/// </summary>
+/// <param name="strKey">需要显示的名称</param>
+/// <param name="strDesc">需要显示的描述信息</param>
+public STNodePropertyAttribute(string strKey, string strDesc);
+
+

STNodePropertyDescriptor

+

Properties

+
+ + + + + + + + + + +
NameTypeDescription
NodeSTNode获取目标节点
ControlSTNodePropertyGrid获取所属的节点属性编辑器控件
RectangleRectangle获取选项所在区域
RectangleLRectangle获取选项名称所在区域
RectangleRRectangle获取选项值所在区域
Namestring获取选项需要显示的名称
Descriptionstring获取属性对应的描述信息
PropertyInfoPropertyInfo获取属性信息
+
+

Virtual method

+OnSetItemLocation +
+
/// <summary>
+/// 当确定STNode属性在属性编辑器上的位置时候发生
+/// </summary>
+protected internal virtual void OnSetItemLocation();
+
+GetValueFromString +
+
/// <summary>
+/// 将字符串形式的属性值转换为属性目标类型的值
+/// 默认只支持 int float double string bool 以及上述类型的Array
+/// 若目标类型不在上述中 请重写此函数自行转换
+/// </summary>
+/// <param name="strText">字符串形式的属性值</param>
+/// <returns>属性真实目标类型的值</returns>
+protected internal virtual object GetValueFromString(string strText);
+
+GetStringFromValue +
+
/// <summary>
+/// 将属性目标类型的值转换为字符串形式的值
+/// 默认对类型值进行 ToString() 操作
+/// 如需特殊处理 请重写此函数自行转换
+/// </summary>
+/// <returns>属性值的字符串形式</returns>
+protected internal virtual string GetStringFromValue();
+
+GetValueFromBytes +
+
/// <summary>
+/// 将二进制形式的属性值转换为属性目标类型的值 用于从文件存储中的数据还原属性值
+/// 默认将其转换为字符串然后调用 GetValueFromString(string)
+/// 此函数与 GetBytesFromValue() 相对应 若需要重写函数应当两个函数一起重写
+/// </summary>
+/// <param name="byData">二进制数据</param>
+/// <returns>属性真实目标类型的值</returns>
+protected internal virtual object GetValueFromBytes(byte[] byData);
+
+GetBytesFromValue +
+
/// <summary>
+/// 将属性目标类型的值转换为二进制形式的值 用于文件存储时候调用
+/// 默认调用 GetStringFromValue() 然后将字符串转换为二进制数据
+/// 如需特殊处理 请重写此函数自行转换 并且重写 GetValueFromBytes()
+/// </summary>
+/// <returns>属性值的二进制形式</returns>
+protected internal virtual byte[] GetBytesFromValue();
+
+GetValue +
+
/// <summary>
+/// 此函数对应 System.Reflection.PropertyInfo.GetValue()
+/// </summary>
+/// <param name="index">索引属性的可选索引值 对于非索引属性 此值应为null</param>
+/// <returns>属性值</returns>
+protected internal virtual object GetValue(object[] index);
+
+SetValue +
+
/// <summary>
+/// 此函数对应 System.Reflection.PropertyInfo.SetValue()
+/// </summary>
+/// <param name="value">需要设置的属性值</param>
+protected internal virtual void SetValue(object value);
+
+SetValue +
+
/// <summary>
+/// 此函数对应 System.Reflection.PropertyInfo.SetValue()
+/// 在调用之前会默认进行 GetValueFromString(strValue) 处理
+/// </summary>
+/// <param name="strValue">需要设置的属性字符串形式的值</param>
+protected internal virtual void SetValue(string strValue);
+
+SetValue +
+
/// <summary>
+/// 此函数对应 System.Reflection.PropertyInfo.SetValue()
+/// 在调用之前会默认进行 GetValueFromBytes(byte[]) 处理
+/// </summary>
+/// <param name="byData">需要设置的属性二进制数据</param>
+protected internal virtual void SetValue(byte[] byData);
+
+SetValue +
+
/// <summary>
+/// 此函数对应 System.Reflection.PropertyInfo.SetValue()
+/// </summary>
+/// <param name="value">需要设置的属性值</param>
+/// <param name="index">索引属性的可选索引值 对于非索引属性 此值应为null</param>
+protected internal virtual void SetValue(object value, object[] index);
+
+SetValue +
+
/// <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);
+
+SetValue +
+
/// <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);
+
+OnSetValueError +
+
/// <summary>
+/// 当设置属性值发生错误时候发生
+/// </summary>
+/// <param name="ex">异常信息</param>
+protected internal virtual void OnSetValueError(Exception ex);
+
+OnDrawValueRectangle +
+
/// <summary>
+/// 当绘制属性在属性编辑器上的值所在区域时候发生
+/// </summary>
+/// <param name="dt">绘制工具</param>
+protected internal virtual void OnDrawValueRectangle(DrawingTools dt);
+
+OnMouseEnter +
+
/// <summary>
+/// 当鼠标进入属性值所在区域时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected internal virtual void OnMouseEnter(EventArgs e);
+
+OnMouseDown +
+
/// <summary>
+/// 当鼠标在属性值所在区域点击时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected internal virtual void OnMouseDown(MouseEventArgs e);
+
+OnMouseMove +
+
/// <summary>
+/// 当鼠标在属性值所在区域移动时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected internal virtual void OnMouseMove(MouseEventArgs e);
+
+OnMouseUp +
+
/// <summary>
+/// 当鼠标在属性值所在区域抬起时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected internal virtual void OnMouseUp(MouseEventArgs e);
+
+OnMouseLeave +
+
/// <summary>
+/// 当鼠标在属性值所在区域离开时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected internal virtual void OnMouseLeave(EventArgs e);
+
+OnMouseClick +
+
/// <summary>
+/// 当鼠标在属性值所在区域点击时候发生
+/// </summary>
+/// <param name="e">事件参数</param>
+protected internal virtual void OnMouseClick(MouseEventArgs e);
+
+

public method

+Invalidate +
+
/// <summary>
+/// 重绘选项区域
+/// </summary>
+public void Invalidate();
+
+
+
+
+ + \ No newline at end of file diff --git a/docs/back.png b/docs/back.png deleted file mode 100755 index c6bd59e..0000000 Binary files a/docs/back.png and /dev/null differ diff --git a/docs/css/doc.css b/docs/css/doc.css deleted file mode 100755 index 11ece03..0000000 --- a/docs/css/doc.css +++ /dev/null @@ -1,185 +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; - /*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; -} \ No newline at end of file diff --git a/docs/css/index.css b/docs/css/index.css old mode 100755 new mode 100644 index b27820b..7104625 --- a/docs/css/index.css +++ b/docs/css/index.css @@ -3,91 +3,336 @@ 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; } -a{ - outline:none; - text-decoration:none; +h1{ + font-size:2.75rem; } -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; +a{ + cursor:pointer; + outline:none; + text-decoration:none; +} +a:hover{ + outline-style:none; +} +img{ + vertical-align: bottom; +} +span{ + display:inline-block; } body{ margin:0px; +} +#h1_title{ + margin:0px; position:relative; - font-size:14px; -} -#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{ +#h1_title:before{ content: 'Copyright© DebugST@Crystal_lz'; position: absolute; - left: 50%; top: 100%; - font-size: 11px; - color: gray; - width:100%; - transform: translate(-50%, 0px); + font-size: 0.75rem; + color: white; + width: 100%; + text-align:center; } -.span_title:after{ - content: '(V 1.0)'; - font-size: 10px; +#h1_title:after{ + content: 'V 2.0'; + font-size: 0.75rem; position: absolute; background-color: hotpink; - border-radius: 5px; - padding: 2px; + border-radius: 3px; + padding: 2px 5px; color: white; display: block; top: 0px; right: 0px; transform: translate(50%, -50%); } -.a_top_btn{ - color:white; - padding: 5px 20px; - font-size: 16px; - border-radius: 15px; - background-color: cornflowerblue; +#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; margin:0px 10px; +} +.quote_symbol{ + color:gray; + position:relative; display:inline-block; } -.span_note{ - color:cornflowerblue; - margin: 2px; - padding: 2px 5px; +.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; border-radius: 5px; - background-color: #EEE; - border:solid 1px #DDD; + 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; + } } \ No newline at end of file diff --git a/docs/css/stdoc.css b/docs/css/stdoc.css new file mode 100644 index 0000000..3e60ba2 --- /dev/null +++ b/docs/css/stdoc.css @@ -0,0 +1,270 @@ +@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:14px; + 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: gray; + padding: 1px 4px; + margin:0px 4px; +} +.p_hightlight{ + color:hotpink; +} +.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; +} +.span_code_title{ + display: inline-block; + background-color: black; + padding: 2px 5px; + margin-top: 10px; +} +.pre_code{ + background-color: #1a1a1a; + font-size: 12px; + overflow: auto; + padding:10px 10px 10px 35px; + display:inline-block; + margin:0px; + min-width:680px; + position:relative; +} +.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; +} \ No newline at end of file diff --git a/docs/doc.html b/docs/doc.html deleted file mode 100755 index ab585a2..0000000 --- a/docs/doc.html +++ /dev/null @@ -1,927 +0,0 @@ - - - - - STNodeEditor Document - - - - - - -
- -
-

概述

-

那是一个冬季 在研究无线电安全的作者接触到了GNURadio 那是作者第一次接触到节点编辑器
  "What? Excuse me... What's this?.. 这是什么鬼东西?..."

-

那是一个春季 不知道为什么 过完年整个世界都变了 大家被迫窝在家里 无聊至极的作者学起了Blender 那是作者第二次接触到节点编辑器
  "Wo...原来这东西可以这么玩...真方便"
  于是一些想法在作者脑中逐渐诞生 让作者有了想做一个这样的东西的想法

-

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

-
-
于是下面的这个东西就诞生了
-
-
或者这样的一个东西
-
-

本案例中只是提供了一个节点编辑器控件 并不包含节点列表 属性编辑器等 若后期有空再做一套完整的框架

-
- 节点编辑器 -
-

或许你未曾接触过节点编辑 但节点编辑的影子越来越多 尤其是在影视相关的一些设计类软件当中 Blender,C4D,Houdini,Davinci 等

-

节点编辑最大的好处就是可视化操作 将单一的功能点封装到节点之中 让用户通过节点布线来组合自己需要的逻辑 让整个流程可视化 而不是将你程序的整个执行流程固化在你的程序之中 当然在这之前你需要定义好节点的数据类型 因为数据类型无法兼容是不允许连线的 通常情况下同颜色的连接点表示数据类型相同

-

另一个好处 让开发者只需要注重单一的功能点开发 使得功能与功能之间的耦合度降低 开发者在节点中开发完成需要的功能 无需知道应该把数据交给谁或者怎么去传递数据 只需要将你的结果数据打包给你所继承的节点类 节点编辑器会自动完成数据的投递过程 -

-

STNodeEditor

-

STNodeEditor为节点容器 其中最重要的一个属性为Nodes里面包含了画布中能看到的所有节点

-

STNodeEditor有两套坐标系统 平常情况下使用最多的就直接是控件的坐标系统 需要在控件的什么位置绘图或者添加东西采用的都是相对控件左上角的坐标 但是在节点编辑器中的情况比较复杂 需要引入一个画布的概念 画布是可以移动缩放的 所以在绘图的时候依旧采用控件的坐标系统将带来很多的麻烦 所以为画布定义了一个坐标原点 在节点以及连线绘制的时候采用的是画布的坐标系统 当需要拖动画布位置的时候 改变画布原点的位置就好了 而内部节点的位置依旧不做变更 因为节点采用的坐标位置是相对画布原点的 -
当画布中Nodes没有元素时候 画布将处于重置状态无法缩放与移动 因为作者觉得这样的操作没有任何意义

-
- 属性 -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
属性名称类型描述
CanvasOffsetXfloat获取画布原点相对于控件 X 方向上的偏移位置
CanvasOffsetYfloat获取画布原点相对于控件 Y 方向上的偏移位置
CanvasOffsetPointF获取画布原点相对于控件偏移位置
CanvasValidBoundsRectangle获取画布中的有被用到的有效区域
CanvasScalefloat获取画布的缩放比例
Curvaturefloat获取或设置 Option 之间连线的曲度
Magnetbool获取或设置移动画布中 Node 时候 是否启用磁铁效果
ShowBorderbool获取或设置移动画布中是否显示 Node 边框
ShowGridbool获取或设置画布中是否绘制背景网格线条
ShowLocationbool获取或设置是否在画布边缘显示超出视角的 Node 位置信息
NodesSTNodeCollection获取画布中 Node 集合
ActiveNodeSTNode获取当前画布中被选中的活动 Node
HoverNodeSTNode获取当前画布中鼠标悬停的 Node
GridColorColor获取或设置绘制画布背景时 网格线条颜色
BorderColorColor获取或设置画布中 Node 边框颜色
BorderHoverColorColor获取或设置画布中悬停 Node 边框颜色
BorderSelectColorColor获取或设置画布中选中 Node 边框颜色
BorderActiveColorColor获取或设置画布中活动 Node 边框颜色
MarkForeColorColor获取或设置画布绘制 Node 标记详情采用的前景色
MarkBackColorColor获取或设置画布绘制 Node 标记详情采用的背景色
MagnetLineColorColor获取或设置画布中移动 Node 时候 磁铁标记线颜色
SelectedRectangleColorColor获取或设置画布中选择矩形区域的颜色
HighLineColorColor获取或设置画布中高亮连线的颜色
LocationForeColorColor获取或设置画布中边缘位置提示区域前景色
LocationBackColorColor获取或设置画布中边缘位置提示区域背景色
UnknownTypeColorColor获取或设置画布中当 Node 中 Option 数据类型无法确定时应当使用的颜色
TypeColorDictionary<Type, Color>获取或设置画布中 Node 中 Option 数据类型预设颜色
- 受保护字段 - - - - - - - - - - - - - - - - - - - - - - - - - -
属性名称类型描述
m_pt_in_controlPoint当前鼠标在控件中的实时位置
m_pt_in_canvasPointF当前鼠标在画布中的实时位置
m_pt_down_in_controlPoint鼠标点击时在控件上的位置
m_pt_down_in_canvasPointF鼠标点击时在画布中的位置
m_pt_canvas_oldPointF用于鼠标点击移动画布时候 鼠标点下时候的画布坐标位置
m_pt_dot_downPoint用于保存连线过程中保存点下 Option 的起点坐标
m_option_downSTNodeOption用于保存连线过程中鼠标点下的起点Option 当MouseUp时候 确定是否连接此节点
-
    -
  • - TypeColor -
      -
    • 此属性为当前STNodeEditor中所有的数据类型所对应的颜色 节点中所有的数据类型都应当包含在此集合中 即便有动态需要加载的未知类型的节点 那么对应的节点也应该在节点的OnOwnerChanged()中向容器提交自己数据类型以及对应颜色 -
      若节点有单独对STNodeOption.DotColor设置值 则忽略该对照表中的颜色 即不为默认颜色Color.Transparent
    • -
    -
  • -
    -
  • - UnknownTypeColor -
      -
    • 若节点中并没有单独设置数据类型颜色且在TypeColor中无法匹配时候采用此值 所以将此特殊类型单独设置
    • -
    -
  • -
-
可重载函数
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
当绘制背景网格线时候发生
void OnDrawGrid(DrawingTools dt, int nWidth, int nHeight)
dt绘制工具
nWidth需要绘制宽度
nHeight需要绘制高度
 
当绘制 Node 时候发生
void OnDrawNode(DrawingTools dt, Rectangle rect)
dt绘制工具
rect可视画布区域大小
 
当绘制已连接路径时候发生
void OnDrawConnectedLine(DrawingTools dt)
dt绘制工具
 
当绘制 Mark 详情信息时候发生
void OnDrawMark(DrawingTools dt)
dt绘制工具
 
当移动 Node 时候 需要显示对齐参考线时候发生
void OnDrawMagnetLine(DrawingTools dt, MagnetInfo mi)
dt绘制工具
mi匹配的磁铁信息
 
绘制选择的矩形区域
void OnDrawSelectedRectangle(DrawingTools dt, RectangleF rectf)
dt绘制工具
rectf位于控件上的矩形区域
 
绘制超出视觉区域的 Node 位置提示信息
void OnDrawNodeOutLocation(DrawingTools dt, Size sz, List<Point> lstPts)
dt绘制工具
sz提示框边距
lstPts超出视觉区域的 Node 位置信息
 
绘制提示信息
void OnDrawAlert(DrawingTools dt, Rectangle rect, string strText, Color foreColor, Color backColor, AlertLocation al)
dt绘制工具
rect需要绘制区域
strText需要绘制文本
foreColor信息前景色
backColor信息背景色
al信息位置
 
获取提示信息需要绘制的矩形区域
Rectangle GetAlertRectangle(Graphics g, string strText, AlertLocation al)
g绘图表面
strText需要绘制文本
al信息位置
- 公开函数 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
通过画布坐标进行寻找
NodeFindInfo FindNodeFromPoint(PointF pt)
pt画布中的坐标
 
获取已经被选择的 Node 集合
STNode[] GetSelectedNode()
 
将画布坐标转换为控件坐标
float CanvasToControl(XXX xxx)
xxxRectangle,RectangleF,Point,PointF...
 
将控件坐标转换为画布坐标
float ControlToCanvas(XXX xxx)
xxxRectangle,RectangleF,Point,PointF...
 
移动画布原点坐标到指定的控件坐标位置
void MoveCanvas(float x, float y, bool bAnimation, CanvasMoveArgs ma)
xX 坐标
yY 坐标
bAnimation移动过程中是否启动动画效果
ma指定需要修改的坐标参数
 
缩放画布
void ScaleCanvas(float f, float x, float y)
f缩放比例
x以指定控件坐标 X 为中心进行缩放
y以指定控件坐标 Y 为中心进行缩放
 
获取当前已连接的 Option 对应关系
ConnectionInfo[] GetConnectionInfo()
 
判断两个 Node 之间是否存在连接路径
static bool CanFindNodePath(STNode nodeStart, STNode nodeFind)
nodeStart起始 Node
nodeFind目标 Node
 
获取画布中指定矩形区域图像
Image GetCanvasImage(Rectangle rect, float fScale)
rect画布中指定的矩形区域
fScale缩放比例
 
保存画布中的类容到文件中
void SaveCanvas(string strFileName)
strFileName文件路径
 
保存画布中的类容到数据流
void SaveCanvas(Stream s)
s数据流对象
 
获取画布中内容二进制数据
byte[] GetCanvasData()
 
加载程序集
bool LoadAssembly(string strFile)
返回值此文件中是否有类型被加载
 
加载程序集
int LoadAssembly(string[] strFiles)
返回值存在STNode类型的文件的个数
编辑器中的节点并不一定来自自身程序集 也可能包含在其他程序集中 这时候需提前加载对应程序集 对应的STNode才能正确的从文件或者数据中动态加载到画布类容
获取当前编辑器中已加载的Node类型
Type[] GetTypes()
 
从文件中加载数据
void LoadCanvas(string strFileName)
strFileName文件路径
 
从二进制加载数据
void LoadCanvas(byte[] byData)
byData二进制数据
 
从数据流中加载数据
void LoadCanvas(Stream s)
s数据流对象
 
在画布中显示提示信息
void ShowAlert(string strText, Color foreColor, Color backColor, int nTime, AlertLocation al, bool bRedraw)
strText要显示的信息
foreColor信息前景色
backColor信息背景色
nTime信息持续时间
al信息要显示的位置
bRedraw是否立即重绘
 
设置画布中活动的节点
STNode SetActiveNode(STNode node)
STNode需要被设置为活动的节点
返回值设置前的活动节点
 
向编辑器中添加默认数据类型颜色
SetTypeColor(Type t, Color clr)
t数据类型
clr对应颜色
-
事件
- - - - - - - - - - - -
SelectedChanged选择的节点发生变化时候发生
HoverChanged悬停的节点发生变化时候发生
NodeAdded当节点被添加时候发生
NodeRemoved当节点被移除时候发生
CanvasMoved移动画布原点时候发生
CanvasScaled缩放画布时候发生
OptionConnected连接节点选项时候发生
OptionDisConnected断开节点选项时候发生
OptionConnecting正在连接节点选项时候发生
OptionDisConnecting正在断开节点选项时候发生
-
    -
  • - Option(Dis)Connected -
      -
    • 此事件触发时并不代表当前画布中的节点连线成功 请通过获取事件参数中的Status字段进行判断 -
      但是STNodeOption.Connected事件是成功后才触发 之所以要这样设计是因为考虑到 对于STNodeEditor来说 它需要知道一个连接的状态 即使连接不成功也应当告知前端用户为什么会连接不成功 所以失败和成功都会触发事件
    • -
    -
  • -
    -
  • - Option(Dis)Connecting -
      -
    • 此事件是在连接或者断开连接正在发生的时候触发 可通过事件参数Continue来决定是否继续或者停止操作
    • -
    -
  • -
-

STNode

-

STNode为抽象类 不可实例化 需要用户自己继承重写

-

STNode有三个比较重要的属性InputOptionsOutputOptionsControls

-

若把STNodeEditor类比成桌面 那么STNode就可以类比成一个窗体 虽然作者在节点中提供了Controls集合 但是作者没有提供任何的一个封装好的控件 只提供了一个STNodeControl基类 若有需要用户可继承此类绘制自己需要的控件

-

继承STNode必须提供空参构造器 否则对节点进行保存后无法还原节点 因为还原过程并非序列化 而是通过反射重新构造对象

-
- 属性 -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
属性名称类型描述
OwnerSTNodeEditor获取当前 Node 所有者
IsSelectedbool获取或设置 Node 是否处于被选中状态
IsActivebool获取 Node 是否处于活动状态
TitleColorColor获取或设置标题背景颜色
MarkColorColor获取或设置标记信息背景颜色
ForeColorColor获取或设置当前 Node 前景色
BackColorColor获取或设置当前 Node 背景色
Titlestring获取或设置 Node 标题
Markstring获取或设置 Node 标记信息
MarkLinesstring[]获取 Node 标记信息行数据
Leftint获取或设置 Node 左边坐标
Topint获取或设置 Node 上边坐标
Widthint获取或设置 Node 宽度
Heightint获取或设置 Node 高度
Rightint获取 Node 右边边坐标
Bottomint获取 Node 下边坐标
RectangleRectangle获取 Node 矩形区域
TitleRectangleRectangle获取 Node 标题矩形区域
MarkRectangleRectangle获取 Node 标记矩形区域
TitleHeightint获取或设置 Node 标题高度
InputOptionsSTNodeOptionCollection获取输入选项集合
InputOptionsCountint获取输入选项集合个数
OutputOptionsSTNodeOptionCollection获取输出选项
OutputOptionsCountint获取输出选项个数
ControlsSTNodeControlCollection获取 Node 所包含的控件集合
ControlsCountint获取 Node 所包含的控件集合个数
LocationPoint获取 Node 坐标位置
SizeSize获取 Node 大小
FontFont获取或设置 Node 字体
LockOptionbool获取或设置是否锁定Option选项 锁定后不在接受连接
LockLocationbool获取或设置是否锁定Node位置 锁定后不可移动
ContextMenuStripContextMenuStrip获取或设置当前Node 上下文菜单
Tagobject获取或设置用户自定义保存的数据
-
    -
  • - Lock(Option/Location) -
      -

    • LockOption被设定后左上角会出现一把锁 表示节点内所有选项不再接受连接 但可以与其他节点选项断开连接 但是其他节点也同为被锁定状态 则无法断开 -
      LockLocation被设定后右上角会出现一枚图钉 表示节点位置不再接受设置
    • -
    -
  • -
-
受保护字段 - - - - - - -
属性名称类型描述
m_ctrl_activeSTNodeControl当前Node中 活动的控件
m_ctrl_hoverSTNodeControl当前Node中 悬停的控件
-

在上述属性中除了MarkLeftTop其余均为只读属性 被protected所修饰 仅继承STNode后才可访问 不然作者觉得太危险了 比如下面这段代码

-

NodeXXX.Owner.Nodes[nIndex].InputOptions.RemoveAt(0);

-

节点都是模块化的 可能会有不同的人来做开发 甚至是以插件的方式提供 如果大家都遵守约定那将很好 可如果一旦有人不遵守约定 或者出现意外情况 那么就可能出现跨节点操作 破坏掉原本一个正常的节点 如果确实有需要请更改源代码

-
- 可重载函数 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
当Node被构造时候发生
void OnCreate()
 
当所有者发生改变时候发生
void OnOwnerChanged()
 
当选中状态改变时候发生
void OnSelectedChanged()
 
当活动状态改变时候发生
void OnActiveChanged()
 
绘制整个Node
void OnDrawNode(DrawingTools dt)
dt绘制工具
 
绘制Node标题部分
void OnDrawTitle(DrawingTools dt)
dt绘制工具
 
绘制Node主体部分 除去标题部分
void OnDrawBody(DrawingTools dt)
dt绘制工具
 
绘制标记信息
void OnDrawMark(DrawingTools dt)
dt绘制工具
 
绘制选项连线的点
void OnDrawOptionDot(DrawingTools dt, STNodeOption op)
dt绘制工具
op需要绘制的Option
 
绘制选项的文本
void OnDrawOptionText(DrawingTools dt, STNodeOption op)
dt绘制工具
op需要绘制的Option
 
当计算Option连线点位置时候发生
Point OnSetOptionDotLocation(STNodeOption op, Point pt)
op需要计算的Option
op自动计算出的位置
 
当计算Option文本区域时候发生
Rectangle OnSetOptionTextRectangle(STNodeOption op, Rectangle rect)
op需要计算的Option
op自动计算出的区域
 
计算当前Node所需要的矩形区域
Size OnBuildNodeSize(DrawingTools dt)
dt绘制工具
若需要自己重绘Node 则应当重写此函数 以确定绘图区域大小 -
返回的大小并不会限制绘制区域 任然可以在此区域之外绘制 -
但是并不会被STNodeEditor所接受 并触发对应事件
计算当前Mark所需要的矩形区域
Rectangle OnBuildMarkRectangle(DrawingTools dt)
dt绘制工具
若需要自己重绘Mark 则应当重写此函数 以确定绘图区域大小 -
返回的大小并不会限制绘制区域 任然可以在此区域之外绘制 -
但是并不会被STNodeEditor所接受 并触发对应事件
当需要保存时候 此Node有哪些需要额外保存的数据
void OnSaveNode(Dictionary<string, byte[]>)
dic需要保存的数据
保存时并不会进行序列化 仅自动保存部分(Guid,Left,Top,Mark,LockOption,LockLocation)属性 还原时候仅重新通过空参数构造器创建此Node -
然后调用OnLoadNode()将保存的数据进行还原
void OnLoadNode(Dictionary<string, byte[]> dic)
dic保存时候的数据
 
当编辑器加载完成所有的节点时候发生
void OnEditorLoadCompleted()
-
protected override void OnSaveNode(Dictionary<string, byte[]>) {
-    dic.Add("count", BitConverter.GetBytes(this.InputOptionsCount));
-}
-
-protected internal override void OnLoadNode(Dictionary<string, byte[]> dic) {
-    int nCount = BitConverter.ToInt32(dic["count"], 0);
-    while (this.InputOptionsCount < nCount && this.InputOptionsCount != nCount) 
-        this.Addhub();
-}
-                
-

-
上面代码片段为STNodeHub重载 当需要保存时候此节点需要保存当前已拥有的行数 还原的时候需要将行数还原 因为新构造的节点只有一行 若上面已有连线关系的时候只有一行可能无法正常还原连线关系

-
-
protected override void OnSaveNode(Dictionary<string, byte[]>) {
-    dic.Add("file", Encoding.UTF8.GetBytes(m_str_file));
-}
-
-protected override void OnLoadNode(Dictionary<string, byte[]> 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));
-    }
-}
-

-
上面代码片段为DEMO中STNodeImageInput重载 因为保存时候 需要把已经打开的文件路劲一起保存 还原的时候再次打开文件并传递数据

-
- 其他重载函数 -

- OnGotFocusOnLostFocusOnMouse***OnKey***OnMove -

- 公开函数 - - - - - - - - - - - - - - - - - - - - - - - - - - - -
重绘Node
void Invalidate()
 
重绘 Node 指定区域
void Invalidate(Rectangle rect)
rectNode 指定区域
 
获取此Node所包含的输入Option集合
STNodeOption[] GetInputOptions()
 
获取此Node所包含的输出Option集合
STNodeOption[] GetOutputOptions()
 
设置Node的选中状态
void SetSelected(bool bSelected, bool bRedraw)
bSelected是否选中
bRedraw是否立即重绘
 
此函数参照System.Windows.Forms.Control
XXX Begin/Invoke(Delegate method, params object[] args)
- 关于重绘 -

-
若无特殊需求 仅仅需要的是输入输出点就足够了 那么无需用户进行重绘操作 上图 "Demo_Node" 就是默认的绘制 用户只需要在节点中加入需要的输入输出选项即可

-
protected override void OnCreate() {
-    base.OnCreate();
-    this.InputOptions.Add(new STNodeOption("Input", typeof(string), false));
-    this.InputOptions.Add(new STNodeOption("SingleNode", typeof(Image), true));
-    this.InputOptions.Add(new STNodeOption("SingleNode", typeof(Icon), true));
-
-    this.OutputOptions.Add(new STNodeOption("output", typeof(string), false));
-    this.OutputOptions.Add(new STNodeOption("Single", typeof(string), true));
-    this.Title = "Demo_Node";
-}
-

上面代码片段为 "Demo_Node" 的OnCreate重载 加入了三个输入节点和两个输出节点 (当然上面的代码并没有绑定STNodeOption的事件) 若需要自定义绘制参考Demo代码

-

STNodeOption

-

STNodeOptionSTNode下选项的连接点

-

TransferData(object data)是关键函数 当被调用时数据将自动传递到与它相连的所有选项中 并触发目标选项的DataTransfer事件 -
此函数是节点选项之间数据传递的核心

-
- 属性 - - - - - - - - - - - - - - - - - -
属性名称类型描述
OwnerSTNode获取当前 Option 所属的 Node
IsSinglebool获取当前 Option 是否仅能被连接一次
IsInputbool获取当前 Option 是否是输入选项
TextColorColor获取或设置当前 Option 文本的颜色
DotColorColor获取或设置当前 Option 连接点的颜色
Textstring获取或设置当前 Option 显示文本
DotLeftint获取当前 Option 连接点的左边坐标
DotTopint获取当前 Option 连接点的上边坐标
DotSizeint获取当前 Option 连接点的宽度
Dataobject获取或者设置当前 Option 所包含的数据
DataTypeType获取当前 Option 数据类型
DotRectangleRectangle获取当前 Option 连接点的区域
ConnectionCountint获取当前 Option 被连接的个数
- 受保护字段 - - - - - -
属性名称类型描述
m_hs_connectedHashSet<STNodeOption>保存已经被连接的点
-

上述中不带 "设置" 二字的都为只读属性 即便是继承至STNodeOption -
因为作者认为STNodeOption最大的用途应该是作为节点之间连线的载体和事件的触发 而对于其他外观需求并不是很重要

-
    -
  • - DotColor -
      -
    • 若设置此值 则绘制时候以此值为准 否则查询STNodeEditor.TypeColor字段进行颜色匹配
    • -
    -
  • -
  • - DataType -
      -
    • 此选项允许被连接或者输出的数据类型 若类型无法被兼容 无法完成连线 -
      对于输入类型的节点 每个与其参与连接的输出节点的数据类型必须与输入节点的数据类型相同 或者为输入节点数据类型的子类 -
      在使用过程中作者并不建议子类和父类混合使用 对于输入或者输出的数据类型应当统一为子类或者父类 若混合使用将在一定程度上产生歧义 比如父类颜色被设定成红色子类颜色被设定成黄色 那么在UI上会给人一种无法被连接的错觉 应当统一数据类型所对应的颜色 -
      当数据类型为object时 则表示可兼容所有数据类型且以 空心 状态绘制 -
      -
      上图中的 "HUB" 节点为内置节点 其节点类型为STNodeHub默认状态下其输入输出点都为object类型 表示可兼容所有类型 且重载了ConnectOption(STNodeOption op)CanConnect(STNodeOption op)函数 一旦被接入或者被接入时则会变更为和对应的数据类型进行连接 若连接成功另则会再增加一行类型为object的新行 -
      但是决定输入类型的DataType属性被internal修饰 所以若你即使继承至STNodeOption也无法改写 只能通过构造器传递 因为处于连线状态突然变更类型会带来一系列的问题 即便编辑器发现变更时候可以选择自动断开所有不相符的连接 但是作者并不建议或者打算这么做 若有此需求请更改源代码
    • -
    -
  • -
-
- 构造器 -
- - - - - - - - - - - - -
构造一个 Option
STNodeOption(string strText, Type dataType, bool bSingle)
strText显示文本
dataType数据类型
bSingle是否为单连接 若为true则以圆形绘制 否则方形
-
- 可重载函数 -

OnConnectedOnConnectingOnDisConnectedOnDisConnectingOnDataTransfer

-
- 公开函数 -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
当前 Option 连接目标 Option
virtual ConnectionStatus ConnectOption(STNodeOption op)
op需要连接的 Option
 
检测当前 Option 是否可以连接目标 Option
virtual ConnectionStatus CanConnect(STNodeOption op)
op需要连接的 Option
当前Option具备连接目标Option的条件 不代表目标Option也具备连当前Option的条件
当前 Option 断开目标 Option
virtual bool DisConnection(STNodeOption op)
op需要断开的 Option
 
断开当前 Option 的所有连接
void DisConnectionAll()
 
获取当前 Option 所连接的 Option 集合
List<STNodeOption> GetConnectedOption()
 
向当前 Option 所连接的所有 Option 投递数据
void TransferData()
 
向当前 Option 所连接的所有 Option 投递数据
void TransferData(object data)
data需要投递的数据
-
事件
- - - - - - -
Connected当被连接时候发生
Connecting当连接正在被连接时发生
Disconnected当被断开时候发生
DisConnecting当连接正在被断开时发生
DataTransfer当有数据传递时候发生
-

ConnectedDisConnected触发的同时DataTransfer也会触发 -
所以通常情况下DataTransfer事件足够使用 通过对事件参数Status可得到 当前事件触发时候目标Option与当前Option的连线关系

-

当一个连线完成或者断开时候 输入和输出STNodeOption都将触发事件 因为无论连接还是断开都应该是相对的需要两个节点共同参与 而通常情况下只需要对输入节点进行事件绑定即可

-

STNodeOptionEventArgs

-

STNodeOptionEventArgs为包含了STNodeOption相关事件的事件参数

-
- 属性 - - - - - - - -
属性名称类型描述
TargetOptionSTNodeOption触发此事件的对应Option
StatusConnectionStatusOption之间的连线状态
IsSponsorbool是否为此次行为的发起者
-
-

STNodeControl

-

关于此类不做过多介绍 可将其视为System.Windows.Forms.Control虽然提供的属性和事件并没有太多 但是作者认为也应当足够了 若还有其他需求请自行修改源代码

-
- Demo -
-
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);
-    }
-}
public class STNodeImageInput : STNode
-{
-    private STNodeOption m_option_out;
-
-    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 = 10; btn.Top = 10;
-        btn.Text = "OpenImage";
-        btn.MouseClick += new MouseEventHandler(btn_MouseClick);
-        this.Controls.Add(btn);
-    }
-    //...other code
-}
-
-

-
上面代码片段为 DEMO 中 "ImageInput" 节点与自定义 Button 控件代码 以上代码演示了如何自定义与使用一个控件 可以看到与System.Windows.Forms.Control差异并不是很大 多以这里并不做过多介绍

-
-
- - \ No newline at end of file diff --git a/docs/doc_cn.html b/docs/doc_cn.html new file mode 100644 index 0000000..b703615 --- /dev/null +++ b/docs/doc_cn.html @@ -0,0 +1,87 @@ + + + + + + STNodeEditor - 文档教程 + + + + + + + + + + \ No newline at end of file diff --git a/docs/doc_en.html b/docs/doc_en.html new file mode 100644 index 0000000..e08a4f8 --- /dev/null +++ b/docs/doc_en.html @@ -0,0 +1,87 @@ + + + + + + STNodeEditor - Document and tutorials + + + + + + + + + + \ No newline at end of file diff --git a/docs/grid.png b/docs/grid.png deleted file mode 100755 index 871eef7..0000000 Binary files a/docs/grid.png and /dev/null differ diff --git a/docs/images/api.png b/docs/images/api.png new file mode 100644 index 0000000..997d434 Binary files /dev/null and b/docs/images/api.png differ diff --git a/docs/images/channel.png b/docs/images/channel.png deleted file mode 100755 index 8c0a410..0000000 Binary files a/docs/images/channel.png and /dev/null differ diff --git a/docs/images/download.png b/docs/images/download.png new file mode 100644 index 0000000..2ef25d0 Binary files /dev/null and b/docs/images/download.png differ diff --git a/docs/images/form1.png b/docs/images/form1.png deleted file mode 100755 index 437f9f8..0000000 Binary files a/docs/images/form1.png and /dev/null differ diff --git a/docs/images/formImage.png b/docs/images/formImage.png deleted file mode 100755 index ae6e482..0000000 Binary files a/docs/images/formImage.png and /dev/null differ diff --git a/docs/images/gdip.png b/docs/images/gdip.png new file mode 100644 index 0000000..16d831d Binary files /dev/null and b/docs/images/gdip.png differ diff --git a/docs/images/gitee.png b/docs/images/gitee.png new file mode 100644 index 0000000..cb70c55 Binary files /dev/null and b/docs/images/gitee.png differ diff --git a/docs/images/github.png b/docs/images/github.png new file mode 100644 index 0000000..a74abd5 Binary files /dev/null and b/docs/images/github.png differ diff --git a/docs/images/layout_l.png b/docs/images/layout_l.png new file mode 100644 index 0000000..1549115 Binary files /dev/null and b/docs/images/layout_l.png differ diff --git a/docs/images/layout_lr.png b/docs/images/layout_lr.png new file mode 100644 index 0000000..0e89af5 Binary files /dev/null and b/docs/images/layout_lr.png differ diff --git a/docs/images/layout_r.png b/docs/images/layout_r.png new file mode 100644 index 0000000..e6f9ed3 Binary files /dev/null and b/docs/images/layout_r.png differ diff --git a/docs/images/lock.gif b/docs/images/lock.gif deleted file mode 100755 index 31c0219..0000000 Binary files a/docs/images/lock.gif and /dev/null differ diff --git a/docs/images/node.gif b/docs/images/node.gif old mode 100755 new mode 100644 diff --git a/docs/images/node.png b/docs/images/node.png deleted file mode 100755 index c3b6f79..0000000 Binary files a/docs/images/node.png and /dev/null differ diff --git a/docs/images/node_add.png b/docs/images/node_add.png deleted file mode 100644 index b94763f..0000000 Binary files a/docs/images/node_add.png and /dev/null differ diff --git a/docs/images/node_demo.png b/docs/images/node_demo.png deleted file mode 100644 index 56123ec..0000000 Binary files a/docs/images/node_demo.png and /dev/null differ diff --git a/docs/images/node_scan.png b/docs/images/node_scan.png new file mode 100644 index 0000000..8a62b89 Binary files /dev/null and b/docs/images/node_scan.png differ diff --git a/docs/images/node_show_back.png b/docs/images/node_show_back.png new file mode 100644 index 0000000..b3e715c Binary files /dev/null and b/docs/images/node_show_back.png differ diff --git a/docs/images/node_show_frm.png b/docs/images/node_show_frm.png new file mode 100644 index 0000000..632b1e2 Binary files /dev/null and b/docs/images/node_show_frm.png differ diff --git a/docs/images/node_show_mix.png b/docs/images/node_show_mix.png new file mode 100644 index 0000000..a62037c Binary files /dev/null and b/docs/images/node_show_mix.png differ diff --git a/docs/images/node_ui.png b/docs/images/node_ui.png new file mode 100644 index 0000000..e759bfe Binary files /dev/null and b/docs/images/node_ui.png differ diff --git a/docs/images/object_type.png b/docs/images/object_type.png deleted file mode 100755 index 6f10d40..0000000 Binary files a/docs/images/object_type.png and /dev/null differ diff --git a/docs/images/open_image.png b/docs/images/open_image.png deleted file mode 100755 index 8b795e8..0000000 Binary files a/docs/images/open_image.png and /dev/null differ diff --git a/docs/images/page_top.png b/docs/images/page_top.png new file mode 100644 index 0000000..c97886c Binary files /dev/null and b/docs/images/page_top.png differ diff --git a/docs/images/sticker.png b/docs/images/sticker.png new file mode 100644 index 0000000..f16e927 Binary files /dev/null and b/docs/images/sticker.png differ diff --git a/docs/images/stnodeeditor.gif b/docs/images/stnodeeditor.gif new file mode 100644 index 0000000..cd49480 Binary files /dev/null and b/docs/images/stnodeeditor.gif differ diff --git a/docs/images/stnodeeditorpannel.gif b/docs/images/stnodeeditorpannel.gif new file mode 100644 index 0000000..a2e7013 Binary files /dev/null and b/docs/images/stnodeeditorpannel.gif differ diff --git a/docs/images/stnodehub.gif b/docs/images/stnodehub.gif new file mode 100644 index 0000000..576621b Binary files /dev/null and b/docs/images/stnodehub.gif differ diff --git a/docs/images/stnodepropertygrid.gif b/docs/images/stnodepropertygrid.gif new file mode 100644 index 0000000..0659133 Binary files /dev/null and b/docs/images/stnodepropertygrid.gif differ diff --git a/docs/images/stnodetreeview.gif b/docs/images/stnodetreeview.gif new file mode 100644 index 0000000..4a979fb Binary files /dev/null and b/docs/images/stnodetreeview.gif differ diff --git a/docs/images/top_bg.jpeg b/docs/images/top_bg.jpeg new file mode 100644 index 0000000..6707708 Binary files /dev/null and b/docs/images/top_bg.jpeg differ diff --git a/docs/images/tu_clocknode_data.gif b/docs/images/tu_clocknode_data.gif new file mode 100644 index 0000000..5719803 Binary files /dev/null and b/docs/images/tu_clocknode_data.gif differ diff --git a/docs/images/tu_clockshowtime.gif b/docs/images/tu_clockshowtime.gif new file mode 100644 index 0000000..56fc8f0 Binary files /dev/null and b/docs/images/tu_clockshowtime.gif differ diff --git a/docs/images/tu_colornode_1.png b/docs/images/tu_colornode_1.png new file mode 100644 index 0000000..aadcc28 Binary files /dev/null and b/docs/images/tu_colornode_1.png differ diff --git a/docs/images/tu_colornode_2.png b/docs/images/tu_colornode_2.png new file mode 100644 index 0000000..2b7bfe1 Binary files /dev/null and b/docs/images/tu_colornode_2.png differ diff --git a/docs/images/tu_colornode_3.png b/docs/images/tu_colornode_3.png new file mode 100644 index 0000000..909017e Binary files /dev/null and b/docs/images/tu_colornode_3.png differ diff --git a/docs/images/tu_colornode_4.png b/docs/images/tu_colornode_4.png new file mode 100644 index 0000000..9b74cb3 Binary files /dev/null and b/docs/images/tu_colornode_4.png differ diff --git a/docs/images/tu_editor_alert.png b/docs/images/tu_editor_alert.png new file mode 100644 index 0000000..eb30cbe Binary files /dev/null and b/docs/images/tu_editor_alert.png differ diff --git a/docs/images/tu_imagenode.png b/docs/images/tu_imagenode.png new file mode 100644 index 0000000..2fee5fa Binary files /dev/null and b/docs/images/tu_imagenode.png differ diff --git a/docs/images/tu_mynode_autosize.png b/docs/images/tu_mynode_autosize.png new file mode 100644 index 0000000..f210190 Binary files /dev/null and b/docs/images/tu_mynode_autosize.png differ diff --git a/docs/images/tu_mynode_autosize_onset.png b/docs/images/tu_mynode_autosize_onset.png new file mode 100644 index 0000000..ff2fc12 Binary files /dev/null and b/docs/images/tu_mynode_autosize_onset.png differ diff --git a/docs/images/tu_mynode_btn.gif b/docs/images/tu_mynode_btn.gif new file mode 100644 index 0000000..e84b249 Binary files /dev/null and b/docs/images/tu_mynode_btn.gif differ diff --git a/docs/images/tu_mynode_ctrl.png b/docs/images/tu_mynode_ctrl.png new file mode 100644 index 0000000..242bd26 Binary files /dev/null and b/docs/images/tu_mynode_ctrl.png differ diff --git a/docs/images/tu_mynode_empty.png b/docs/images/tu_mynode_empty.png new file mode 100644 index 0000000..b4b7055 Binary files /dev/null and b/docs/images/tu_mynode_empty.png differ diff --git a/docs/images/tu_mynode_single.png b/docs/images/tu_mynode_single.png new file mode 100644 index 0000000..e65f190 Binary files /dev/null and b/docs/images/tu_mynode_single.png differ diff --git a/docs/images/tu_property_1.png b/docs/images/tu_property_1.png new file mode 100644 index 0000000..8c56ec0 Binary files /dev/null and b/docs/images/tu_property_1.png differ diff --git a/docs/images/tu_property_2.png b/docs/images/tu_property_2.png new file mode 100644 index 0000000..18e33ad Binary files /dev/null and b/docs/images/tu_property_2.png differ diff --git a/docs/images/tu_property_3.png b/docs/images/tu_property_3.png new file mode 100644 index 0000000..cfc6c7b Binary files /dev/null and b/docs/images/tu_property_3.png differ diff --git a/docs/images/tu_property_4.png b/docs/images/tu_property_4.png new file mode 100644 index 0000000..455ca4f Binary files /dev/null and b/docs/images/tu_property_4.png differ diff --git a/docs/images/tu_property_5.png b/docs/images/tu_property_5.png new file mode 100644 index 0000000..7c4160f Binary files /dev/null and b/docs/images/tu_property_5.png differ diff --git a/docs/images/tu_property_6.png b/docs/images/tu_property_6.png new file mode 100644 index 0000000..23d9632 Binary files /dev/null and b/docs/images/tu_property_6.png differ diff --git a/docs/images/tu_stnodepropertygrid.png b/docs/images/tu_stnodepropertygrid.png new file mode 100644 index 0000000..b0f5c7a Binary files /dev/null and b/docs/images/tu_stnodepropertygrid.png differ diff --git a/docs/images/tu_testnode.png b/docs/images/tu_testnode.png new file mode 100644 index 0000000..0bfacd0 Binary files /dev/null and b/docs/images/tu_testnode.png differ diff --git a/docs/images/tu_testnode_adop.png b/docs/images/tu_testnode_adop.png new file mode 100644 index 0000000..6472a5e Binary files /dev/null and b/docs/images/tu_testnode_adop.png differ diff --git a/docs/images/tu_testnode_adopclr.png b/docs/images/tu_testnode_adopclr.png new file mode 100644 index 0000000..ddc647e Binary files /dev/null and b/docs/images/tu_testnode_adopclr.png differ diff --git a/docs/images/tu_treeview_1.png b/docs/images/tu_treeview_1.png new file mode 100644 index 0000000..3d8f406 Binary files /dev/null and b/docs/images/tu_treeview_1.png differ diff --git a/docs/images/tu_treeview_2.png b/docs/images/tu_treeview_2.png new file mode 100644 index 0000000..db07ef2 Binary files /dev/null and b/docs/images/tu_treeview_2.png differ diff --git a/docs/index.html b/docs/index.html old mode 100755 new mode 100644 index bcf1fa7..61719d3 --- a/docs/index.html +++ b/docs/index.html @@ -4,75 +4,214 @@ - STNodeEditor + STNodeEditor - 主页 + + + -
-
-
- STNodeEditor
- 项目地址开发文档类库下载 +
+
+
+

STNodeEditor

+

STNodeEditor 是一个轻量且功能强大的节点编辑器 使用方式非常简洁 提供了丰富的属性以及事件可以非常方便的完成节点之间数据的交互及通知 大量的重载函数供开发者使用具有很高的自由性

+
+ 下载 STNodeEditor
+ + + +
-
-
-

或许你未曾接触过节点编辑 但节点编辑的影子越来越多 尤其是在影视相关的一些设计类软件当中 Blender,C4D,Houdini,Davinci 等

-

STNodeEditor的使用方式非常简洁 提供了丰富的属性以及事件 可以非常方便的完成节点之间数据的交互及通知 大量的重载函数供开发者使用具有很高的自由性
此控件采用MIT开源协议开源 使开发者能够拥有更大的自由度更少的限制

-

节点编辑最大的好处就是可视化操作 将单一的功能点封装到节点之中 让用户通过节点布线来组合自己需要的逻辑 让整个流程可视化 而不是将你程序的整个执行流程固化在你的程序之中 当然在这之前你需要定义好节点的数据类型 因为数据类型无法兼容是不允许连线的 通常情况下同颜色的连接点表示数据类型相同

-

另一个好处 让开发者只需要注重单一的功能点开发 使得功能与功能之间的耦合度降低 开发者在节点中开发完成需要的功能 无需知道应该把数据交给谁或者怎么去传递数据 只需要将你的结果数据打包给你需要输出的选项点 无论节点间布线多么的复杂 节点编辑器会自动完成数据的投递过程

-
-
-
-
+
+
+
+

STNodeEditor 2.0

+

本案例中只是提供了一个节点编辑器控件 并不包含节点列表 属性编辑器等 若后期有空再做一套完整的框架

+

上面是第一版本作者在文档中提到的 现在都已实现

+

STNodeEditorSTNodeTreeViewSTNodePropertyGridSTNodeEditorPannel

+
+
+
+
GIF
+
+
+
+

STNodeEditor

+

STNodeEditor拥有非常强大的功能 支持画布的移动和缩放 可以对节点位置以及连线进行锁定 连线时候会自动检测数据类型是否兼容 以及连线是否重复或者构成环形线路等问题

    -
  • - 移动画布 -
      -
    • 鼠标中键拖动
    • -
    • MacBook可使用二指拖动触控板
    • -
    -
  • -
    -
  • - 缩放画布 -
    • 按下Control + 鼠标滚轮
    -
  • -
    -
  • - 移动节点 -
    • 鼠标左键按下节点标题拖动
    -
  • -
    -
  • - 删除连线 -
    • 悬停连线 鼠标右键
    -
  • +
  • 拖动标题移动节点
  • +
  • 右击标题弹出菜单 (需要设置ContextMenuStrip)
  • +
  • 拖动连接点进行连线
  • +
  • 右击连线断开连接
  • +
  • 中键拖动移动画布 (若笔记本触摸板支持 可二指拖动)
  • +
  • CTRL+鼠标滚轮 缩放画布
-
-

方形为多连接选项 圆形为单连接选项

-

自定义选项颜色 应当同一颜色为同一数据类型 不同数据类型无法建立连线

-

内置HUB集线器节点 方便布线

+

注:节点Body区域进行的操作编辑器不会响应 因为在节点客户区内部的操作将被转换为节点的事件

+

因为作者将一个节点视为一个Form 而编辑器容器则为Desktop 开发者可以像开发WinForm程序一样去开发一个节点

-
-
-
-

强大的UI自定义能力 可快速帮助开发者定义出需要的节点界面

-

提供的STNodeControl基类与System.Windows.Forms.Control拥有差不多的接口 可使开发者能够像自定义WinForm控件一样定义节点的控件

-

即便用户不进行自定义 选项默认将按照一行一行排列

-

基类STNode抽象类 不可实例化 需开发者继承向其添加选项 并重载需要的函数进行自定义样式

-

具体细节参考项目中的Demo或查看开发文档

+
+
+
+
GIF
+
+
+
+

STNodeHub

+

STNodeHub是一个内置的节点 其主要作用分线 可以将一个输出分散到多个输入或多个输出集中到一个输入点上以避免重复布线 也可在节点布线复杂时用于绕线

+

HUB的输入输出默认为object类型 当一个连接被连入时候将会自动更换数据类型并增加新行

+

注:仅STNodeHub可以修改连接点的数据类型 因为相应字段被internal标记 而作为第三方扩展的STNode中是无法修改已添加连接点的数据类型的

-
-
-
- - - -
.Net版本:.Net Framework 3.5
  VS版本:Visual Studio 2010
- 2021-01-06 +
+
+
+
GIF
+
+
+

STNodeTreeView

+

STNodeTreeView可与STNodeEditor结合使用 STNodeTreeView中的节点可直接拖拽进STNodeEditor中 并且提供预览和检索功能

+

STNodeTreeView的使用简单 无需像System.Windows.Forms.TreeView需要自行去构造树

+

通过使用STNodeAttribute标记继承的STNode可直接设置需要在STNodeTreeView中显示的路径 以及希望在STNodePropertyGrid中显示的信息

+

注:若希望节点能够在STNodeTreeView中显示 必须使用STNodeAttribute标记STNode子类

+
+
+
+
+
+
GIF
+
+
+
+

STNodePropertyGrid

+

STNode中的属性被STNodePropertyAttribute标记则会在STNodePropertyGrid中显示 默认情况下支持int,float,double,bool,string,enum以及上述数据类型的Array 若希望显示的属性数据类型不被支持 可以对DescriptorType进行扩展重写 详细请参考DEMO

+

可以看到在STNodePropertyGrid的面板中可以显示节点的一些信息 作者认为提供给大家的是一套框架 大家可以基于这套框架打造一套自己的框架 而为框架编写节点的Coder应该有权利选择是否留下个人信息

+
+
+
+
+
+
GIF
+
+
+
+

STNodeEditorPannel

+

STNodeEditorPannelSTNodeEditorSTNodeTreeViewSTNodePropertyGrid的一套组合控件

+

可以通过拖动手柄控制布局 +

+
+
+
+

所以这东西什么

+

这东西可以做啥??? 拉流程图??? 自动编排???

+
+ + + +
+

这动画是作者能想到的最好解释方式了

+

当有很多应用程序(模块) 它们之间需要相互调用传递数据来完成一整套流程的工作
+ 开发单一功能的应用程序(模块)相对比较容易 而实现一整套很多功能相互调用的应用程序相对比较繁琐
+ 此套框架开发者只需要定义好传递的数据类型 然后分别实现单一节点功能 至于执行流程交给框架和用户布线即可

+ +

流程即程序 程序即流程 程序功能可视化

+
+
+
+
PNG
+
+
+
+

关于UI自定义

+

由图可见 节点UI定义非常自由

+

STNodeControl提供了与System.Windows.Forms.Control一样的常用函数与属性 所以开发者只需要像开发WinForm控件一样去开发STNode的控件即可

+

如图中AttrTestNode没有特殊的UI定义需求 则只需要向STNode提供需要输入输出的数据类型及显示文本STNode.AutoSize会自动计算大小并布局 STNode.AutoSize默认为true

+

更多详情请参考 DEMO

+
+
+
+
+
+
GIF
+
+
+
+

关于节点编辑器

+

或许你未曾接触过节点编辑 但节点编辑的影子越来越多 尤其是在设计相关的软件 Blender,C4D,Houdini,Davinci 等

+

节点编辑最大的好处就是可视化操作 将单一功能封装到节点中 让用户通过节点布线来组合需要的逻辑 让整个流程可视化 而不是将程序的整个执行流程固定程序中 当然在这之前需要定义好节点的数据类型 因为数据类型无法兼容是不允许连线的 通常情况下同颜色的连接点表示数据类型相同

+

让开发者只需要注重单一的功能点开发 使得功能与功能之间的耦合度降低 开发者在节点中开发完成需要的功能 无需知道如何去调用下一个环节的程序 只需要将结果数据打包给需要输出的选项点 无论节点布线多么的复杂 节点编辑器会自动完成数据的投递过程

+
+
+
+
+

STNodeEditor 3.0


+

本案例中只是Demo中提供了几个节点控件编写示例 并不包含控件 若后期有空再做一些节点可用的常用控件

+

上面是这一版本发布时候作者提到的 是否期待实现 ???

+ Fork me on Github +
+
+ + + +
.Net版本:.Net Framework 3.5
  VS版本:Visual Studio 20102021-04-27
+
大家都说石头是世界上最帅的码农 好烦
+
+
+ diff --git a/docs/index_en.html b/docs/index_en.html new file mode 100644 index 0000000..1b74e3a --- /dev/null +++ b/docs/index_en.html @@ -0,0 +1,179 @@ + + + + + + + STNodeEditor - home + + + + + +
+
+
+

STNodeEditor

+

STNodeEditor is a lightweight and powerful node editor, very simple to use. Provides a wealth of properties and events, which can easily complete the data interaction and notification between nodes. A large number of overloaded functions are available for developers to use. It is very free.

+
+ Download STNodeEditor
+ + + +
+
+
+
+
+

STNodeEditor 2.0

+

In this case, only a node editor control is provided, and the TreeView,PropertyGrid is not included. If I have time later, I will make a complete framework.

+

The above is what the author of the first version mentioned in the document. NOW it's COME TRUE

+

STNodeEditorSTNodeTreeViewSTNodePropertyGridSTNodeEditorPannel

+
+
+
+
GIF
+
+
+
+

STNodeEditor

+

STNodeEditorhas a very powerful function. It supports the movement and zooming of the canvas and can lock the position of the node and the connection. When connecting, it will automatically check whether the data type is compatible, and whether the connection is repeated or whether it is a loop path, etc.

+
    +
  • Drag the title to move the node
  • +
  • Right-click the title to pop up the menu (setContextMenuStrip)
  • +
  • Drag the connection points to connect
  • +
  • Right-click on the connection to disconnect
  • +
  • Middle-drag to move the canvas (If the notebook touchpad supports it, it can be dragged with two fingers)
  • +
  • CTRL+mouse wheel to zoom the canvas
  • +
+

Note: The operation editor performed in the node client area will not respond, because the operations in the node client area will be converted into node events.

+

Because the author regards a node as a Form and the editor container is Desktop, the developer can develop a node like a WinForm program.

+
+
+
+
+
+
GIF
+
+
+
+

STNodeHub

+

STNodeHubis a built-in node that can disperse one output to multiple inputs or concentrate multiple outputs on one input point to prevent repeated connections. It can also be used for layout when the node connection is complicated

+

The input and output of HUB are of object type by default. When a connection is connected, the data type will be automatically changed and a new row will be added.

+

Note: Only STNodeHub can modify the data type of the connection point. Because the field is marked internal, the data type of the added connection point cannot be modified in the extended STNode.

+
+
+
+
+
+
GIF
+
+
+
+

STNodeTreeView

+

STNodeTreeView is easy to use, and there is no need to create a tree directory by yourself like System.Windows.Forms.TreeView.

+

Mark the STNode subclass by STNodeAttribute to set the path you want to display in STNodeTreeView and the information you want to display in STNodePropertyGrid.

+

Note: If you want nodes to be displayed in STNodeTreeView, you must use STNodeAttribute to mark the STNode subclass.

+
+
+
+
+
+
GIF
+
+
+
+

STNodePropertyGrid

+

If the property in STNode is marked by STNodePropertyAttribute, it will be displayed in STNodePropertyGrid. By default, int, float, double, bool, string, enum and Array of the above data types are supported. If the property data type you want to display is not supported, you can extended STNodePropertyDesciptor, please refer to DEMO for details.

+

You can see that some information about the node can be displayed in the STNodePropertyGrid panel. The author believes that this is a framework, and everyone can build another framework based on this framework. And the Coder that writes the node for the framework can add personal information for this node.

+
+
+
+
+
+
GIF
+
+
+
+

STNodeEditorPannel

+

STNodeEditorPannel is a combination control of STNodeEditor STNodeTreeView STNodePropertyGrid.

+

You can control the layout by dragging the handle +

+
+
+
+

SO what CAN it DO

+

What's this???flow chart??? SOAR?????

+
+ + + +
+

This animation is the best explanation the author can think of

+

When there are many applications (modules), they need to call each other to transfer data to complete a set of processes.
+ It is easy to develop a single-function application (module), but it is tedious to develop a whole set of applications that call each other with many functions.
+ Developers using this framework only need to define the data type, and then develop a single function node, as for the execution process, hand it over to the framework and the user connection.

+ +

The process is the program The program is the process, and the function is visualized.

+
+
+
+
PNG
+
+
+
+

About UI customization

+

From the picture, you can see that the node UI definition is very free.

+

STNodeControl provides the same common functions and properties as System.Windows.Forms.Control, so developers only need to develop STNode controls like WinForm controls.

+

The AttrTestNode node in the picture does not have special UI definition requirements. You only need to provide STNode with the input and output data types and display text. STNode.AutoSize will automatically calculate the size and layout. STNode.AutoSize is true by default.

+

For more details, please refer to DEMO

+
+
+
+
+
+
GIF
+
+
+
+

About node editor

+

Maybe you have never used node editing, but node editing is more and more, especially in the design software Blender, C4D, Houdini, Davinci, etc.

+

The biggest advantage of node editing is the visual operation. A single function is completed in the node and the user can combine the required logic through the node connection to make the process visible, instead of fixing the execution process of the function in the program. Of course, the data type of the node needs to be defined before this. Incompatible data types are not allowed to connect. Normally, the connection points of the same color have the same data type.

+

Let developers only need to focus on the development of a single function, so that the coupling between functions and functions is reduced. The developer completes the required function in the node without knowing how to call the next program. You only need to pack the result data into the output option, no matter how complicated the node connection is, the node editor will automatically complete the data transfer.

+
+
+
+
+

STNodeEditor 3.0


+

In this case, only a few node control writing examples are provided in the Demo, and the control is not included. If there is time, the author will make some common controls that can be used by the node.

+

The above is what the author mentioned when this version was released Are you looking forward to completion ???

+ Fork me on Github +
+
+ + + +
.Net:.Net Framework 3.5
  VS:Visual Studio 20102021-04-27
+
DebugST is the most handsome programmer in the world!
+
+
+ + \ No newline at end of file diff --git a/docs/js/doc.js b/docs/js/doc.js deleted file mode 100755 index e69de29..0000000 diff --git a/docs/js/index.js b/docs/js/index.js deleted file mode 100755 index e69de29..0000000 diff --git a/docs/js/jquery-1.10.2.min.js b/docs/js/jquery-1.10.2.min.js old mode 100755 new mode 100644 diff --git a/docs/js/stdoc.js b/docs/js/stdoc.js new file mode 100644 index 0000000..50a259d --- /dev/null +++ b/docs/js/stdoc.js @@ -0,0 +1,49 @@ +$(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 = '',strLast = ''; + var nHeight =window.innerHeight;// 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'); + } + } + strLast = $(es[i]).attr('anchor'); + } + $(".anchor_btn").removeClass('active'); + $(".anchor_btn[anchor='" + strName + "']").addClass('active'); + }); + + $(document).on("touchstart","#div_left",function(e){}); + + if(navigator.userAgent.toLowerCase().indexOf('webkit') == -1){ + console.log('what the fuck...!!!!!!'); + $('body').append("
" + + "都TM2021年了 Windows还在采用可视滚动条 然而只有WebKit提供了滚动条样式的支持" + + "
" + ); + var e = $('#div_fuck_the_kernel'); + e.css({ + position:"fixed", + top:0, + left:0, + right:0, + color:"white", + "line-height":"20px", + "text-align":"center", + "background-color":"rgba(255,255,0,.5)", + border:"solid 1px yellow", + "z-index":100 + }); + e.click(function(){e.remove();}); + } +}); \ No newline at end of file diff --git a/docs/line.png b/docs/line.png deleted file mode 100755 index 6722a55..0000000 Binary files a/docs/line.png and /dev/null differ diff --git a/docs/mark.png b/docs/mark.png deleted file mode 100755 index 3932a2c..0000000 Binary files a/docs/mark.png and /dev/null differ diff --git a/docs/node.png b/docs/node.png deleted file mode 100755 index 03b0706..0000000 Binary files a/docs/node.png and /dev/null differ diff --git a/docs/out.png b/docs/out.png deleted file mode 100755 index 47f19c4..0000000 Binary files a/docs/out.png and /dev/null differ diff --git a/docs/result.png b/docs/result.png deleted file mode 100755 index 30e4fb7..0000000 Binary files a/docs/result.png and /dev/null differ diff --git a/docs/tutorials_cn.html b/docs/tutorials_cn.html new file mode 100644 index 0000000..b0d8f53 --- /dev/null +++ b/docs/tutorials_cn.html @@ -0,0 +1,1225 @@ + + + + + +STNodeEditor - 教程 + + + + + +
+ +
+

前言

+

简介

+

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

+

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

+

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

+

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

+

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

+

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

+

所以STNodeEditor就诞生了

+ +
+

[基础]STNode

+

STNode是整个框架的核心 如果把STNodeEditor视为Desktop 那么一个STNode就可以视为桌面上的一个应用程序 开发一个健壮的STNode是非常有必要的事情

+
+

创建节点

+
+

节点的基类STNodeabstract修饰 无法直接创建节点 所以节点必须继承至STNode

+

STNode包含了大量的virtual函数可供开发者重写 对于详细的函数列表请参考API文档

+
+
using ST.Library.UI.NodeEditor;
+
+namespace WinNodeEditorDemo
+{
+    public class MyNode : STNode
+    {
+        public MyNode() { //与OnCreate()等效
+            this.Title = "TestNode";
+        }
+        //protected override void OnCreate() {
+        //    base.OnCreate();
+        //}
+    }
+}
+//添加到STNodeEditor中
+stNodeEditor1.Nodes.Add(new MyNode());
+
+ +

会发现只看到一个标题什么都没有 因为并没有为它添加输入输出选项 所以重新修改代码

+
+
public class MyNode : STNode
+{
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "TestNode";
+        //此添加方式会得到添加成功后的 STNodeOption 索引位置
+        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);
+    }
+}
+
+ +

这样节点上就会出现所添加的选项 但是这样还不够 可以看到一共有两个数据类型stringint应该给数据类型赋予颜色信息 来区分不同的数据类型

+
+
public class MyNode : STNode
+{
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "TestNode";
+        int nIndex = this.InputOptions.Add(new STNodeOption("IN_1", typeof(string), false));
+        STNodeOption op = this.InputOptions.Add("IN_2", typeof(int), true);
+        //this.SetOptionDotColor(op, Color.Red); 优先级最高 如果被设置 会忽略容器中的颜色信息
+        this.OutputOptions.Add("OUT", typeof(string), false);
+    }
+    //当所属容器发生变化时候发生 应当向容器提交自己数据类型期望显示的颜色
+    protected override void OnOwnerChanged() {
+        base.OnOwnerChanged();
+        if (this.Owner == null) return;
+        this.Owner.SetTypeColor(typeof(string), Color.Yellow);
+        //此添加方式 会替换容器中已有的类型颜色信息
+        this.Owner.SetTypeColor(typeof(int), Color.DodgerBlue, true);
+    }
+}
+
+ +

这样一个节点就创建完成了 但是这个节点目前不具备任何的功能 接下来的案例中将逐步添加功能

+

无论如何开发者都应该尽量为扩展的STNode保留一个空参数构造器 不然在很多功能使用会存在不必要的麻烦

+
+

STNodeOption

+
+

由上面的案例可知STNodeOptionSTNode的连接选项 连接选项可以是多连接单连接模式

+
+
public class MyNode : STNode {
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "MyNode";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+        //单连接选项
+        this.InputOptions.Add("Single", typeof(string), true);
+        //多连接选项
+        this.OutputOptions.Add("Multi", typeof(string), false);
+    }
+}
+
+ +

多连接模式下 一个选项可以被同数据类型的多个选项连接 以方形绘制

+

单连接模式下 一个选项仅可被同数据类型的一个选项连接 以圆形绘制

+
+

STNodeOption.Empty

+
+

STNodeOption.Empty是一个静态属性 添加到STNode中仅用于在自动布局时候占位使用 不参与绘制与事件触发

+
+
public class MyNode : STNode {
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "MyNode";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+
+        this.InputOptions.Add(STNodeOption.Empty);
+        this.InputOptions.Add("IN_1", typeof(string), false);
+        this.InputOptions.Add("IN_2", typeof(string), false);
+
+        this.OutputOptions.Add("OUT_1", typeof(string), false);
+        this.OutputOptions.Add(STNodeOption.Empty);
+        this.OutputOptions.Add(STNodeOption.Empty);
+        this.OutputOptions.Add("OUT_2", typeof(string), false);
+    }
+}
+
+ +

每一个STNodeOption的高度由STNode.ItemHeight(protected)决定

+
+

STNode.AutoSize

+
+

AutoSize默认为true 在此状态下节点的WidthHeight等属性无法被设置

+
+
public class MyNode : STNode {
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "MyNode";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+
+        this.InputOptions.Add("IN", typeof(string), false);
+        this.OutputOptions.Add("OUT", typeof(string), false);
+        //需要先设置AutoSize=false 才能设置STNode大小
+        this.AutoSize = false;
+        this.Size = new Size(100, 100);
+    }
+}
+
+ +

可以看到MyNode的大小不再自动计算 可STNodeOption的位置依旧会进行自动计算 如果想修改STNodeOption的位置可以重写OnSetOptionXXX

+
+
public class MyNode : STNode
+{
+    private STNodeOption m_op_in;
+    private STNodeOption m_op_out;
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "MyNode";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+
+        m_op_in = this.InputOptions.Add("IN", typeof(string), false);
+        m_op_out = this.OutputOptions.Add("OUT", typeof(string), false);
+        //需要先设置AutoSize=false 才能设置STNode大小
+        this.AutoSize = false;
+        this.Size = new Size(100, 100);
+    }
+    //无论AutoSize为何值 都可以对选项连接点位置进行修改
+    protected override Point OnSetOptionDotLocation(STNodeOption op, Point pt, int nIndex) {
+        if (op == m_op_in) return new Point(pt.X, pt.Y + 20);
+        return base.OnSetOptionDotLocation(op, pt, nIndex);
+    }
+    //无论AutoSize为何值 都可以对选项文本显示区区域进行修改
+    protected override Rectangle OnSetOptionTextRectangle(STNodeOption op, Rectangle rect, int nIndex) {
+        if (op == m_op_out) return new Rectangle(rect.X, rect.Y + 20, rect.Width, rect.Height);
+        return base.OnSetOptionTextRectangle(op, rect, nIndex);
+    }
+}
+
+ +

可以看到代码中修改STNodeOption连线的点和文本区域是通过重写函数去修改的 为什么不设计成STNodeOption.DotLeft=xxx的方式是因为作者认为这样反而会更加的麻烦

+

重写函数中所传入的ptrect都是自动计算过后的数据 这样开发者在修改位置的时候会有一定的参照 如果通过STNodeOption.DotLeft=xxx这样的方式 那么开发者无法得到一个参照位置需要全部自己计算

+

而且还需要绑定STNode.Resize等事件来监听STNode大小的变化来重新计算位置 所以相比之下OnSetOptionXXX的方式反而相对没那么繁琐

+

目前为止出现的所有案例都不具备数据的传递与接收 在接下来的案例中将开始添加功能

+
+

案例 - ClockNode

+
+

STNodeOption通过对DataTransfer事件的绑定可以获取到此选项的所有数据输入

+

STNodeOption.TransferData(object)函数可以向此选项上的所有连线传递数据

+

接下来实现一个具备一定功能的节点 目前而言能想到的最好的实现就是定义一个时钟节点

+

因为目前介绍到的内容还不足以能够自由的向节点提供任意数据 所以需要一个能自己产生数据的节点

+

该节点每秒钟向外输出一次当前系统时间

+
+
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.TransferData(object) 会自动设置STNodeOption.Data
+                m_op_out_time.TransferData(DateTime.Now);
+                //与WinForm一样 在线程中如果需要跨UI线程操作 节点提供了 Begin/Invoke() 可完成对应操作
+                //this.BeginInvoke(new MethodInvoker(() => m_op_out_time.TransferData(DateTime.Now)));
+            }
+        }) { IsBackground = true };
+        m_thread.Start();
+    }
+}
+
+

当然上面的节点我们可以直接显示时间 但是为了演示数据的传递 还需要一个作为接受数据的节点

+
+
public class ShowClockNode : STNode {
+    private STNodeOption m_op_time_in;
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "ShowTime";
+        //由于此节点仅能显示一个时间 选项应当采用"单连接"模式 仅接受一个时间数据
+        m_op_time_in = this.InputOptions.Add("--", typeof(DateTime), true);
+        //监听事件 当有数据被传输到m_op_time_in时 会触发此事件
+        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
+            //因为在AutoSize下Text每发生一次变化STNode就会重新计算一次布局 应当通过添加控件方式来显示
+            //由于当前还并未讲解到STNodeControl 所以这里暂时先采用当前设计
+            this.SetOptionText(m_op_time_in, "--");
+        } else {
+            this.SetOptionText(m_op_time_in, ((DateTime)e.TargetOption.Data).ToString());
+        }
+    }
+}
+
+

添加到STNodeEditor中的效果如下

+ +

可以看到ShowClockNode每秒都在刷新时间 同样通过上面代码可以看到ShowClockNode并没有重写OnOwnerChanged()函数向容器添加数据类型颜色信息

+

如果先把ShowClockNode添加到STNodeEditor中会发现节点选项确实没有颜色 但若ClockNode被添加时候 会发现ShowClockNode的选项立马就有了颜色

+

因为ClockNode在添加的时候 向容器提交了数据类型的颜色 而ShowClockNode选项的数据类型和ClockNode一样 所以自然在绘制的时候具备了颜色数据

+
+

STNode.SetOptionXXX

+
+

在上面和之前的案例中可以看到需要修改STNodeOption一些属性的时候并不是STNodeOption.XXX=XXX方式去修改的 之所以这样设计是为了处于安全考虑

+

作者认为一个STNodeOption只能被它的所有者所修改 而STNodeOption.XXX=XXX的方式无法确定是被谁修改的 而STNode.SetOptionXXX()protected所标记只能在内部被调用且在函数内部会判断STNodeOption.Owner是否是当前类 以确保安全

+
+

关于数据传递

+
+

并不是一定要STNodeOption.TransferData(object)才能向下面的选项传递数据TransferData(object)仅仅是主动向下更新数据

+

当一个新的连接刚被建立的时候 会被动传递一次数据 下面将ClockNode的代码修改一下

+
+
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);
+        //对选项的数据赋值
+        m_op_out_time.Data = DateTime.Now;
+    }
+}
+
+ +

可以看到ShowClockNode依然显示了时间 只是数据不再变化 因为在建立连接的同时也会触发DataTransfer事件 在事件中ShowClockNode通过e.TargetOption.Data获取到了ClockNode选项的数据

+

当一个连接被建立与断开时事件触发顺序如下

+

Connecting-Connected-DataTransfer | DisConnecting-DataTransfer-DisConnected

+
+

STNodeHub

+
+ +

STNodeHub是一个内置的节点 其主要作用分线 可以将一个输出分散到多个输入或多个输出集中到一个输入点上以避免重复布线 也可在节点布线复杂时用于绕线

+

[基础]STNodeControl

+

STNodeControl作为STNode控件的基类 有着许多与System.Windows.Forms.Control同名的属性及事件 使得开发者可以像开发WinForm程序一样去开发一个节点

+

在此版本(2.0)中 并没有提供任何一个可用控件 仅STNodeControl基类 需开发者继承进行扩展 若后期有空作者再来完善

+
+

添加一个控件

+
+

System.Windows.Forms.Control一样 STNode拥有Controls集合 其数据类型为STNodeControl

+
+
public class MyNode : STNode
+{
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "MyNode";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+        //需要先设置AutoSize=false 才能设置STNode大小
+        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");
+    }
+}
+
+ +

可以看到与开发WinForm程序几乎没有任何区别 唯一不同的是STNode暂时还不提供所见即所得的UI设计器

+
+

自定义一个 Button

+
+

虽然上面的代码看起来像是添加了一个按钮控件 事实上那仅仅是STNodeControl的默认绘制样式

+

下面来自定义一个Button控件 具备鼠标悬停和点击效果 使其更加像一个按钮

+
+
public class STNodeButton : STNodeControl {
+
+    private bool m_b_enter;
+    private bool m_b_down;
+
+    protected override void OnMouseEnter(EventArgs e) {
+        base.OnMouseEnter(e);
+        m_b_enter = true;
+        this.Invalidate();
+    }
+
+    protected override void OnMouseLeave(EventArgs e) {
+        base.OnMouseLeave(e);
+        m_b_enter = false;
+        this.Invalidate();
+    }
+
+    protected override void OnMouseDown(MouseEventArgs e) {
+        base.OnMouseDown(e);
+        m_b_down = true;
+        this.Invalidate();
+    }
+
+    protected override void OnMouseUp(MouseEventArgs e) {
+        base.OnMouseUp(e);
+        m_b_down = false;
+        this.Invalidate();
+    }
+
+    protected override void OnPaint(DrawingTools dt) {
+        //base.OnPaint(dt);
+        Graphics g = dt.Graphics;
+        SolidBrush brush = dt.SolidBrush;
+        brush.Color = base.BackColor;
+        if (m_b_down) brush.Color = Color.SkyBlue;
+        else if (m_b_enter) brush.Color = Color.DodgerBlue;
+        g.FillRectangle(brush, 0, 0, this.Width, this.Height);
+        g.DrawString(this.Text, this.Font, Brushes.White, this.ClientRectangle, base.m_sf);
+    }
+}
+
+ +

当然为了代码尽可能的简洁 按钮的效果写死在了代码中 上面的代码仅仅是演示如何去构建一个自定义控件 当然在这之前你需要具备一些GDI相关的知识

+

<GDI+程序设计>是一本不错的树

+ +
+

案例 - 图像信息获取

+
+

在上述的ClockNode案例中 对于数据的数据是通过代码写死在节点中的 接下来这个案例通过上面编写的STNodeButton来获取数据进行输出

+
+
public class ImageShowNode : STNode
+{
+    private STNodeOption m_op_out;
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "ImageShowNode";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+        this.AutoSize = false;
+        this.Size = new Size(160, 150);
+        m_op_out = this.OutputOptions.Add("", typeof(Image), false);
+
+        var ctrl = new STNodeButton();
+        ctrl.Text = "Open Image";
+        ctrl.Location = new Point(5, 0);
+        ctrl.Size = new Size(150, 20);
+        this.Controls.Add(ctrl);
+        ctrl.MouseClick += new MouseEventHandler(ctrl_MouseClick);
+    }
+
+    void ctrl_MouseClick(object sender, MouseEventArgs e) {
+        OpenFileDialog ofd = new OpenFileDialog();
+        ofd.Filter = "*.png|*.png|*.jpg|*.jpg";
+        if (ofd.ShowDialog() != DialogResult.OK) return;
+        m_op_out.TransferData(Image.FromFile(ofd.FileName), true);
+        this.Invalidate();
+    }
+
+    protected override void OnDrawBody(DrawingTools dt) {
+        base.OnDrawBody(dt);//当然用户可以通过扩展"STNodeControl"编写一个"STNodePictureBox"控件来显示图片
+        Graphics g = dt.Graphics;
+        Rectangle rect = new Rectangle(this.Left + 5, this.Top + this.TitleHeight + 20, 150, 105);
+        g.FillRectangle(Brushes.Gray, rect);
+        if (m_op_out.Data != null)
+            g.DrawImage((Image)m_op_out.Data, rect);
+    }
+}
+
+

接下来需要一个数据接收节点 比如获取图像大小

+
+
public class ImageSizeNode : STNode {
+
+    private STNodeOption m_op_in;
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "ImageSize";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+        m_op_in = this.InputOptions.Add("--", typeof(Image), true);
+        m_op_in.DataTransfer += new STNodeOptionEventHandler(m_op_in_DataTransfer);
+    }
+
+    void m_op_in_DataTransfer(object sender, STNodeOptionEventArgs e) {
+        if (e.Status != ConnectionStatus.Connected || e.TargetOption.Data == null) {
+            this.SetOptionText(m_op_in, "--");
+        } else { 
+            Image img = (Image)e.TargetOption.Data;
+            this.SetOptionText(m_op_in, "W:" + img.Width + " H:" + img.Height);
+        }
+    }
+}
+
+ +

通过点击Open Image按钮可以选择一张图片并在节点中显示 在ImageSizeNode连接后 就获取到了图像的大小

+

在上图中ImageChannel节点代码这里并没有给出 代码在WinNodeEditorDemo工程中 其作用为提取一张图像的RGB通道 对于ImageShowNode而言它仅仅是提供了数据源并显示 对于ImageSizeNodeImageChannel节点而言 它们并不知道会被什么节点连接 它们仅仅是完成了各自的功能并将结果传递给了输出选项 等待被下一个节点连接

+

而对于执行逻辑完全是由用户布线将它们的功能串在了一起 开发期间节点与节点之间没有任何的交互 唯一将它们牵扯在一起的是一个Image的数据类型 这样一来节点与节点之间几乎不存在耦合关系

+

[基础]STNodeEditor

+

STNodeEditor作为STNode的容器 同样提供了大量的属性与事件供开发者使用 关于STNodeEditor更为详细的API列表 请参考帮助文档

+
+

保存画布

+
+

对于STNodeEditor中的节点以及连线的关系是可以文件持久化保存的

+

通过STNodeEditor.SaveCanvas(string strFileName)函数可以将画布中的内容持久化保存

+

需要注意的是SaveCanvas()系列函数会调用internal byte[] STNode.GetSaveData()函数来获取每个节点的二进制数据

+

GetSaveData()函数并非将节点自身序列化 GetSaveData()函数会将节点自身的基础数据及原始属性进行二进制化 然后调用virtual OnSaveNode(Dictionary<string, byte[]> dic)向扩展节点索要节点需要保存的数据

+

所以若有保存需求 节点开发者可能需要通过重写OnSaveNode()函数 来确保一些需要的数据能够被保存

+

关于更多节点保存的内容将会在后面的内容中介绍

+
+

加载画布

+
+

通过STNodeEditor.LoadCanvas(string strFileName)函数可以从文件中加载保存的数据

+

STNodeEditor存在其他程序集中的节点 则需要通过调用STNodeEditor.LoadAssembly(string strFile)来加载程序集以确保文件中的节点能够被正确还原

+

因为还原过程并非序列化 而是通过(STNode)Activator.CreateInstance(stNodeType)方式动态创建一个节点然后调用virtual OnSaveNode(Dictionary<string, byte[]> dic)对数据进行还原 而dic则为OnSaveNode()所保存的数据

+

因为还原节点是通过反射动态创建节点的 所以扩展的STNode中必须提供空参数构造器

+

关于更多节点加载的内容将会在后面的内容中介绍

+
+

常用事件

+
+

ActiveChanged,SelectedChanged 可以监控控件中节点被选中的变化情况

+
+
stNodeEditor1.ActiveChanged += (s, e) => Console.WriteLine(stNodeEditor1.ActiveNode.Title);
+
+stNodeEditor1.SelectedChanged += (s, e) => {
+    foreach(var n in stNodeEditor1.GetSelectedNode()){
+        Console.WriteLine(n.Title);
+    }
+};
+
+

如果希望在画布每次缩放后在编辑器上提示缩放比可以通过CanvasScaled事件获取

+
+
stNodeEditor1.CanvasScaled += (s, e) => {
+    stNodeEditor1.ShowAlert(stNodeEditor1.CanvasScale.ToString("F2"),
+        Color.White, Color.FromArgb(127, 255, 255, 0));
+};
+
+

如果希望画布中有节点连线时 提示连线状态可以通过OptionConnected事件获取状态

+
+
stNodeEditor1.OptionConnected += (s, e) => {
+    stNodeEditor1.ShowAlert(e.Status.ToString(), Color.White,
+        Color.FromArgb(125, e.Status ConnectionStatus.Connected ? Color.Lime : Color.Red));
+};
+
+
+

常用方法

+
+
+
/// <summary>
+/// 移动画布原点坐标到指定的控件坐标位置 (当不存在 Node 时候 无法移动)
+/// </summary>
+/// <param name="x">X 坐标</param>
+/// <param name="y">Y 坐标</param>
+/// <param name="bAnimation">移动过程中是否启动动画效果</param>
+/// <param name="ma">指定需要修改的坐标参数</param>
+public void MoveCanvas(float x, float y, bool bAnimation, CanvasMoveArgs ma);
+
+
+
+
/// <summary>
+/// 缩放画布(当不存在 Node 时候 无法缩放)
+/// </summary>
+/// <param name="f">缩放比例</param>
+/// <param name="x">缩放中心X位于控件上的坐标</param>
+/// <param name="y">缩放中心Y位于控件上的坐标</param>
+public void ScaleCanvas(float f, float x, float y);
+
+
+
+
/// <summary>
+/// 向编辑器中添加默认数据类型颜色
+/// </summary>
+/// <param name="t">数据类型</param>
+/// <param name="clr">对应颜色</param>
+/// <param name="bReplace">若已经存在是否替换颜色</param>
+/// <returns>被设置后的颜色</returns>
+public Color SetTypeColor(Type t, Color clr, bool bReplace);
+
+
+
+
/// <summary>
+/// 在画布中显示提示信息
+/// </summary>
+/// <param name="strText">要显示的信息</param>
+/// <param name="foreColor">信息前景色</param>
+/// <param name="backColor">信息背景色</param>
+/// <param name="nTime">信息持续时间</param>
+/// <param name="al">信息要显示的位置</param>
+/// <param name="bRedraw">是否立即重绘</param>
+void ShowAlert(string strText, Color foreColor, Color backColor, int nTime, AlertLocation al, bool bRedraw);
+//e.g.
+stNodeEditor1.ShowAlert("this is test info", Color.White, Color.FromArgb(200, Color.Yellow));
+
+ +

关于STNodeEditor更多的属性函数事件请参考API文档 此文档更注重于与STNode相关的内容及演示

+

STNodePropertyGrid

+

STNodePropertyGrid是随着类库一起发布的另一个控件 可以与STNodeEditor结合使用

+ +

STNodePropertyGrid一共有两个面板 通过右上角的按钮可以进行切换 分别是属性面板节点信息面板

+

仅存在属性节点信息才会显示对应面板

+
+

如何使用

+
+

STNodePropertyGrid的核心方法为SetNode(STNode)通常与STNodeEditor绑定使用

+
+
stNodeEditor1.ActiveChanged += (s, e) => stNodePropertyGrid1.SetNode(stNodeEditor1.ActiveNode);
+
+

既然叫做属性编辑器必然和STNode的属性相关 STNode作为一个普通的class当然也可以是拥有属性的 而STNodePropertyGrid就是展示并修改它们 就像在WinForm开发时候UI设计器中所看到的一样

+

接下来编写一个节点试一下

+
+
public class PropertyTestNode : STNode {
+
+    private int _Number;
+
+    public int Number {
+        get { return _Number; }
+        set { _Number = value; }
+    }
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "PropertyTest";
+    }
+}
+
+ +

发现并没有看到想象中的画面 并没有显示Number属性

+

System.Windows.Forms.PropertyGrid不同的是PropertyGrid会显示class中的所有属性 而STNodePropertyGrid并没有采用这样的设计方案

+

S T N O D E PropertyGrid 这个是STNodePropertyGrid并不会随便的显示一个属性 因为作者认为开发者可能并不希望STNode中的所有属性都被显示出来 即使需要显示出来开发者可能也并不希望在属性窗口看到的是Number而是一个别的名字 毕竟Number是写代码时候用的

+

只有被STNodePropertyAttribute特性所标记的属性才会被STNodePropertyGrid显示

+
+

STNodePropertyAttribute

+
+

STNodePropertyAttribute拥有三个属性NameDescriptionDescriptorType

+

Name - 对于此属性希望在STNodePropertyGrid上显示的名称

+

Description - 当鼠标左键STNodePropertyGrid长按属性名称时候希望显示的描述信息

+

DescriptorType - 决定属性如何与属性窗口进行数据交互 将在稍后讲解此属性

+

STNodePropertyAttribute构造函数为STNodePropertyAttribute(string strName,string strDescription)

+
+
public class PropertyTestNode : STNode
+{
+    private int _Number;
+    [STNodeProperty("Name", "Description for this property")]
+    public int Number {
+        get { return _Number; }
+        set { _Number = value; }
+    }
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "PropertyTest";
+    }
+}
+
+ +

此时就能看到属性被正确的显示了 并且可以被设置 而且左键长按属性名称会显示描述信息

+
+

STNodeAttribute

+
+

若希望显示节点信息 则STNode需要被STNodeAttribute特性标记

+
+
[STNode("AA/BB", "Author", "Mail", "Link", "Description")]
+public class PropertyTestNode : STNode
+{
+    private int _Number;
+    [STNodeProperty("Name", "Description for this property")]
+    public int Number {
+        get { return _Number; }
+        set { _Number = value; }
+    }
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "PropertyTest";
+    }
+}
+
+

通过右上角的按钮可进行面板切换

+

其中AA/BB用于在STNodeTreeView中构建路径使用

+ +
+
stNodePropertyGrid1.SetInfoKey("Author", "Mail", "Link", "Show Help");
+
+

信息面板内容的key可通过SetInfoKey()函数来设置语言 默认简体中文显示

+
+

查看帮助

+
+

上面一个信息面板案例中可以看到查看帮助按钮不可用 若希望可用则需要提供魔术方法

+
+
[STNode("AA/BB", "Author", "Mail", "Link", "Description")]
+public class PropertyTestNode : STNode
+{
+    private int _Number;
+    [STNodeProperty("Name", "Description for this property")]
+    public int Number {
+        get { return _Number; }
+        set { _Number = value; }
+    }
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "PropertyTest";
+    }
+    /// <summary>
+    /// 此方法为魔术方法
+    /// 若存在 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);
+    }
+}
+
+ +

此时发现查看帮助按钮变成了启用状态 STNodeAttribute还提供了两个static函数

+
+
/// <summary>
+/// 获取类型的帮助函数
+/// </summary>
+/// <param name="stNodeType">节点类型</param>
+/// <returns>函数信息</returns>
+public static MethodInfo GetHelpMethod(Type stNodeType);
+/// <summary>
+/// 执行对应节点类型的帮助函数
+/// </summary>
+/// <param name="stNodeType">节点类型</param>
+public static void ShowHelp(Type stNodeType);
+
+
+

案例 - 两数相加

+
+

既然STNodePropertyGrid可以显示并修改属性 那么接下来这个案例将通过属性窗口来提供一个数据的输入

+
+
public class NumberInputNode : STNode
+{
+    private int _Number;
+    [STNodeProperty("Input", "Input number")]
+    public int Number {
+        get { return _Number; }
+        set {
+            _Number = value;
+            this.SetOptionText(m_op_out, value.ToString());
+            m_op_out.TransferData(value);
+        }
+    }
+
+    private STNodeOption m_op_out;
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "NumberInput";
+        m_op_out = this.OutputOptions.Add("0", typeof(int), false);
+    }
+}
+
+
+
public class NumberAddNode : STNode {
+
+    private STNodeOption m_op_in_1;
+    private STNodeOption m_op_in_2;
+    private STNodeOption m_op_out;
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "NumberAdd";
+        m_op_in_1 = this.InputOptions.Add("0", typeof(int), true);
+        m_op_in_2 = this.InputOptions.Add("0", typeof(int), true);
+        m_op_out = this.OutputOptions.Add("0", typeof(int), false);
+
+        m_op_in_1.DataTransfer += new STNodeOptionEventHandler(m_op_in_DataTransfer);
+        m_op_in_2.DataTransfer += new STNodeOptionEventHandler(m_op_in_DataTransfer);
+    }
+
+    void m_op_in_DataTransfer(object sender, STNodeOptionEventArgs e) {
+        if (e.Status != ConnectionStatus.Connected || e.TargetOption == null) {
+            if (sender == m_op_in_1) m_op_in_1.Data = 0;
+            if (sender == m_op_in_2) m_op_in_2.Data = 0;
+        } else {
+            if (sender == m_op_in_1) m_op_in_1.Data = e.TargetOption.Data;
+            if (sender == m_op_in_2) m_op_in_2.Data = e.TargetOption.Data;
+        }
+        if (m_op_in_1.Data == null) m_op_in_1.Data = 0;
+        if (m_op_in_2.Data == null) m_op_in_2.Data = 0;
+        int nResult = (int)m_op_in_1.Data + (int)m_op_in_2.Data;
+        this.SetOptionText(m_op_in_1, m_op_in_1.Data.ToString());
+        this.SetOptionText(m_op_in_2, m_op_in_2.Data.ToString());
+        this.SetOptionText(m_op_out, nResult.ToString());
+        m_op_out.TransferData(nResult);
+    }
+}
+
+ +

通过Number属性的set访问器将输入的数字向下传递

+
+

ReadOnlyModel

+
+

某些情况下并不希望STNodePropertyGrid对属性进行设置 仅仅希望展示属性 则可以启用ReadOnlyModel

+
+
stNodePropertyGrid1.ReadOnlyModel = true;
+
+ +

ReadOnlyModel下 属性无法通过STNodePropertyGrid被设置

+

STNodePropertyDescriptor

+

上面介绍了STNodePropertyAttributeNameDescription属性 还有第三个属性DescriptorType其数据类型为Type 默认值为typeof(STNodePropertyDescriptor)

+

虽然从目前的案例来看 上面的操作没有任何问题 但是并不是所有数据类型的属性都能够正确的被STNodePropertyGrid所支持 默认的STNodePropertyDescriptor仅支持下列数据类型

+

intfloatdoubleboolstringEnum以及它们的Array

+
+

失败案例 - ColorTestNode

+
+

下面创建一个节点添加一个Color类型的属性

+
+
public class ColorTestNode : STNode
+{
+    [STNodeProperty("TitleColor", "Get or set the node TitleColor")]
+    public Color ColorTest {
+        get { return this.TitleColor; }
+        set { this.TitleColor = value; }
+    }
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "ColorNode";
+    }
+}
+
+ +

运行上述代码 会发现通过STNodePropertyGrid对属性进行进行设置会出现错误 而且STNodePropertyGrid中对于属性的值显示也很奇怪

+

即便System.Windows.Forms.PropertyGrid能支持很多数据类型 但也并非万能 比如当属性类型是用户自定义类型时候属性编辑器根本无法知道要如何与图形化界面上的属性交互

+

对于System.Windows.Forms.PropertyGrid的解决方案便是提供TypeConverter将目标类型用TypeConverter标记并实现重写 这样一来PropertyGrid通过TypeConverter就能够知道如何与图形化界面进行交互

+

STNodePropertyGrid提供的解决方案便是STNodePropertyDescriptor

+
+

关于属性描述器

+
+

之所以STNodePropertyGrid能够正确的获取以及修改STNode属性的值 全是依靠STNodePropertyDescriptor在中间做交互 进行数据的转换以及响应属性窗口上的一些行为操作

+

STNodePropertyAttribute标记的属性都会包装成STNodePropertyDescriptor传递给STNodePropertyGrid 一个STNodePropertyDescriptor包含了STNodePropertyAttribute中的NameDescription以及属性将会在STNodePropertyGrid上显示的位置信息等 以及如何响应鼠标事件或键盘事件等

+

可以理解为STNodePropertyDescriptor是每个被STNodePropertyAttribute所标记属性的图形化界面接口 而主要作用则为在图形界面和真实属性之间传递数据

+
+

GetValueFromString()

+
+
+
/// <summary>
+/// 将字符串形式的属性值转换为属性目标类型的值
+/// 默认只支持 int float double string bool 以及上述类型的Array
+/// 若目标类型不在上述中 请重写此函数自行转换
+/// </summary>
+/// <param name="strText">字符串形式的属性值</param>
+/// <returns>属性真实目标类型的值</returns>
+protected internal virtual object GetValueFromString(string strText);
+
+

GetValueFromString()函数主要负责将用户在STNodePropertyGrid中输入字符串转换为对应属性需要的真实值

+

如: 假若属性是int类型值 而用户在STNodePropertyGrid中只能输入字符串的123那么默认的GetValueFromString()函数内部则会int.Parse(strText)这样string类型的123就变成了int类型的123

+
+

GetStringFromValue()

+
+
+
/// <summary>
+/// 将属性目标类型的值转换为字符串形式的值
+/// 默认对类型值进行 ToString() 操作
+/// 如需特殊处理 请重写此函数自行转换
+/// </summary>
+/// <returns>属性值的字符串形式</returns>
+protected internal virtual string GetStringFromValue();
+
+

GetStringFromValue()函数主要负责将属性值转换为字符串的方式在STNodePropertyGrid中显示 默认的GetStringFromValue()内部仅仅是对属性的值ToString()操作

+
+

成功案例 - ColorTestNode

+
+

对上面失败案例的ColorTestNode.ColorTestSTNodePropertyDescriptor进行扩展

+
+
public class ColorTestNode : STNode
+{
+    //指定使用扩展的 STNodePropertyDescriptor 以便完成对 Color 类型的支持
+    [STNodeProperty("TitleColor", "Get or set the node TitleColor", DescriptorType = typeof(ColorDescriptor))]
+    public Color ColorTest {
+        get { return this.TitleColor; }
+        set { this.TitleColor = value; }
+    }
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "ColorNode";
+    }
+}
+
+public class ColorDescriptor : STNodePropertyDescriptor
+{
+    protected override object GetValueFromString(string strText) {
+        string[] strs = strText.Split(',');
+        return Color.FromArgb(int.Parse(strs[0]), int.Parse(strs[1]), int.Parse(strs[2]), int.Parse(strs[3]));
+        //return base.GetValueFromString(strText);
+    }
+
+    protected override string GetStringFromValue() {
+        var v = (Color)this.GetValue(null);//获取当前属性值
+        return v.A + "," + v.R + "," + v.G + "," + v.B;
+        //return base.GetStringFromValue();
+    }
+}
+
+ +

此时就能够正确的显示与设置属性值了 因为GetValueFromString()GetStringFromValue()正确的完成了属性值与字符串之间的转换

+
+

高级案例 - ColorTestNode

+
+

上面的案例虽然可以通过设置Color的ARGB来完成对属性值的设置 但是明显这样的方式并不够友好

+ +

是否能够像System.Windows.Forms.PropertyGrid一样 能够让用户进行可视化的选择 STNodePropertyDescriptor当然也能办到

+

STNodePropertyDescriptor可以视作是一个显示属性的自定义控件 既然是自定义控件那么就具备了鼠标事件的响应

+

下面对ColorDescriptor增加一些代码

+
+
public class ColorDescriptor : STNodePropertyDescriptor
+{
+    private Rectangle m_rect;//此区域用作 属性窗口上绘制颜色预览
+
+    protected override object GetValueFromString(string strText) {
+        string[] strs = strText.Split(',');
+        return Color.FromArgb(int.Parse(strs[0]), int.Parse(strs[1]), int.Parse(strs[2]), int.Parse(strs[3]));
+        //return base.GetValueFromString(strText);
+    }
+
+    protected override string GetStringFromValue() {
+        var v = (Color)this.GetValue(null);
+        return v.A + "," + v.R + "," + v.G + "," + v.B;
+        //return base.GetStringFromValue();
+    }
+
+    //当此属性在属性窗口中被确定位置时候发生
+    protected override void OnSetItemLocation() {
+        base.OnSetItemLocation();
+        Rectangle rect = base.RectangleR;
+        m_rect = new Rectangle(rect.Right - 25, rect.Top + 5, 19, 12);
+    }
+    //绘制属性窗口值区域时候调用
+    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);
+    }
+}
+
+ +

此时可以看到与上一个案例相比 多了一个颜色预览区域 并且鼠标点击预览区域会弹出系统颜色对话框对属性值进行设置 如果在非预览区域点击 则会使用默认操作方式 手动输入ARGB

+

关于STNodePropertyDescriptor还有两个重要虚函数GetBytesFromValue()SetValue(byte[])将会在后面保存画布中进行介绍

+

STNodeTreeView

+

STNodeTreeViewSTNodePropertyGrid一样 是随着类库一起发布的另一个控件 可以与STNodeEditor结合使用

+ +

STNodeTreeView中的节点可直接拖拽进STNodeEditor中 并且提供预览和检索功能

+

STNodeTreeView的使用简单 无需像System.Windows.Forms.TreeView需要自行去构造树目录

+

通过使用STNodeAttribute标记STNode子类可直接设置需要在STNodeTreeView中显示的路径 以及希望在STNodePropertyGrid中显示的信息

+

注:若希望节点能够在STNodeTreeView中显示 必须使用STNodeAttribute标记STNode子类

+

使用方式

+

目前而言 以上案例的节点都是通过STNodeEditor.Nodes.Add(STNode)方式进行添加 接下来通过STNodeTreeView拖拽方式进行添加 但在这之前需要先把节点添加到STNodeTreeView

+
+
[STNode("AA/BB", "Author", "Mail", "Link", "Description")]
+public class MyNode : STNode
+{
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "TreeViewTest";
+    }
+}
+//添加到 STNodeTreeView 中
+stNodeTreeView1.AddNode(typeof(MyNode));
+
+ +

可以看到STNodeTreeView中出现了添加的节点 并且自动构建好了路径 节点可以预览并且直接被拖拽到STNodeEditor中进行添加

+

同路径下禁止出现同名节点 不然将会被覆盖

+
+

加载程序集

+
+

除了STNodeTreeView.AddNode(Type)向树中添加节点以外 还可以通过LoadAssembly(string)方式从一个程序集里面加载节点 LoadAssembly(string)会自动检测程序集中被STNodeAttribute标记的节点并添加 并且会以程序集名称创建根节点显示

+
+
stNodeTreeView1.AddNode(typeof(MyNode));
+stNodeTreeView1.LoadAssembly(Application.ExecutablePath);
+//如果是来自外部程序集 STNodeEditor也应当做同样操作确保节点能够被STNodeEditor正确识别
+stNodeEditor1.LoadAssembly(Application.ExecutablePath);
+
+ +

可以看到在上图中有两个根节点AAWinNodeEditorDemo 其实MyNode是属于WinNodeEditorDemo程序集的 但是在WinNodeEditorDemo的子节点中却无法找到 由于在加载程序集之前MyNode先被添加 当加载程序集是识别到MyNode被重复添加所以就跳过了

+

STNodeTreeView中无法添加重复类型 与 同路径同名节点

+

持久化保存

+

在上面STNodeEditor介绍时提到过画布的保存与加载 这里将详细介绍

+
+

保存

+
+

STNodeEditor.SaveCanvas()STNodeEditor会遍历所有的Nodes并调用internal byte[] STNode.GetSaveData()以获得每个节点的二进制数据

+

GetSaveData()为内部方法被internal修饰 函数体如下

+
+
internal byte[] GetSaveData() {
+    List<byte> lst = new List<byte>();
+    Type t = this.GetType();
+    byte[] byData = Encoding.UTF8.GetBytes(t.Module.Name + "|" + t.FullName);
+    lst.Add((byte)byData.Length);
+    lst.AddRange(byData);
+    byData = Encoding.UTF8.GetBytes(t.GUID.ToString());
+    lst.Add((byte)byData.Length);
+    lst.AddRange(byData);
+
+    var dic = this.OnSaveNode();
+    if (dic != null) {
+        foreach (var v in dic) {
+            byData = Encoding.UTF8.GetBytes(v.Key);
+            lst.AddRange(BitConverter.GetBytes(byData.Length));
+            lst.AddRange(byData);
+            lst.AddRange(BitConverter.GetBytes(v.Value.Length));
+            lst.AddRange(v.Value);
+        }
+    }
+    return lst.ToArray();
+}
+
+

可以看到GetSaveData()内部先对节点自身基本信息获取二进制 然后调用OnSaveNode()获取了一个字典类型数据

+
+
internal Dictionary<string, byte[]> OnSaveNode() {
+    Dictionary<string, byte[]> dic = new Dictionary<string, byte[]>();
+    dic.Add("Guid", this._Guid.ToByteArray());
+    dic.Add("Left", BitConverter.GetBytes(this._Left));
+    dic.Add("Top", BitConverter.GetBytes(this._Top));
+    dic.Add("Width", BitConverter.GetBytes(this._Width));
+    dic.Add("Height", BitConverter.GetBytes(this._Height));
+    dic.Add("AutoSize", new byte[] { (byte)(this._AutoSize ? 1 : 0) });
+    if (this._Mark != null) dic.Add("Mark", Encoding.UTF8.GetBytes(this._Mark));
+    dic.Add("LockOption", new byte[] { (byte)(this._LockLocation ? 1 : 0) });
+    dic.Add("LockLocation", new byte[] { (byte)(this._LockLocation ? 1 : 0) });
+    Type t = this.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;//获取被STNodePropertyAttribute标记的属性进行自动保存
+            object obj = Activator.CreateInstance(attr.DescriptorType);
+            if (!(obj is STNodePropertyDescriptor))
+                throw new InvalidOperationException("[STNodePropertyAttribute.Type]参数值必须为[STNodePropertyDescriptor]或者其子类的类型");
+            var desc = (STNodePropertyDescriptor)Activator.CreateInstance(attr.DescriptorType);
+            desc.Node = this;
+            desc.PropertyInfo = p;
+            byte[] byData = desc.GetBytesFromValue();//通过 STNodePropertyDescriptor 获取属性二进制数据
+            if (byData == null) continue;
+            dic.Add(p.Name, byData);
+        }
+    }
+    this.OnSaveNode(dic);
+    return dic;
+}
+/// <summary>
+/// 当需要保存时候 此Node有哪些需要额外保存的数据
+/// 注意: 保存时并不会进行序列化 还原时候仅重新通过空参数构造器创建此Node
+/// 然后调用 OnLoadNode() 将保存的数据进行还原
+/// </summary>
+/// <param name="dic">需要保存的数据</param>
+protected virtual void OnSaveNode(Dictionary<string, byte[]> dic) { }
+
+

通过上面代码可以看到STNode会对自身基本属性进行保存二进制数据 并且会识别被STNodePropertyAttribute标记的属性并通过GetBytesFromValue()获取对应属性二进制数据 然后再调用OnSaveNode(dic)向扩展节点索要需要保存的数据

+

如果有需要保存的属性则应当用STNodePropertyAttribute标记并且确保GetBytesFromValue()能够正确的获取到属性的二进制数据或者通过OnSaveNode(dic)进行保存

+

如果有私有字段需要保存 应当通过OnSaveNode(dic)进行保存

+
+

OnSaveNode(dic)

+
+

OnSaveNode(dic)OnLoadNode(dic)互相对应

+
+
protected override void OnSaveNode(Dictionary<string, byte[]> dic) {
+    dic.Add("count", BitConverter.GetBytes(this.InputOptionsCount));
+}
+
+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();
+}
+
+

上面代码片段为STNodeHubOnSaveNode(dic)OnLoadNode(dic)重写 可以看到额外保存了一个count数据 因为STNodeHub的选项是动态创建的 而一个新创建的STNodeHub只有一行连接 所以在保存时候需要记录状态以确保能正确的还原连线状态 然后在OnLoadNode(dic)中对状态进行还原

+

STNodeEditor除了保存节点数据之外还会保存节点选项的连线关系 会对当前画布中的所有节点的每个STNodeOption进行编号 将编号关系保存 所以STNodeHub保存和还原的时候一定要确保STNodeOption的个数是没有变动的

+
+

GetBytesFromValue()

+
+

对于被STNodePropertyAttribute标记的属性在保存时候会自动调用STNodePropertyDescriptor.GetBytesFromValue()以获取属性的二进制数据

+

GetBytesFromValue()GetValueFromBytes()互相对应

+
+
/// <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>
+/// 将二进制形式的属性值转换为属性目标类型的值 用于从文件存储中的数据还原属性值
+/// 默认将其转换为字符串然后调用 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);
+}
+
+

STNodePropertyDescriptor.GetBytesFromValue()默认调用STNodePropertyDescriptor.GetStringFromValue()获取属性的字符串值 将字符串转换为byte[]

+

如果GetStringFromValue()GetValueFromString(strText)能正确的运行 那么采用默认处理方式也能够将属性值正确保存

+
+

OnLoadNode(dic)

+
+

当还原节点时候 STNodeEditor通过(STNode)Activator.CreateInstance(stNodeType)创建节点 然后调用OnLoadNode(dic)

+
+
protected internal virtual void OnLoadNode(Dictionary<string, byte[]> dic) {
+    if (dic.ContainsKey("AutoSize")) this._AutoSize = dic["AutoSize"][0] == 1;
+    if (dic.ContainsKey("LockOption")) this._LockOption = dic["LockOption"][0] == 1;
+    if (dic.ContainsKey("LockLocation")) this._LockLocation = dic["LockLocation"][0] == 1;
+    if (dic.ContainsKey("Guid")) this._Guid = new Guid(dic["Guid"]);
+    if (dic.ContainsKey("Left")) this._Left = BitConverter.ToInt32(dic["Left"], 0);
+    if (dic.ContainsKey("Top")) this._Top = BitConverter.ToInt32(dic["Top"], 0);
+    if (dic.ContainsKey("Width") && !this._AutoSize) this._Width = BitConverter.ToInt32(dic["Width"], 0);
+    if (dic.ContainsKey("Height") && !this._AutoSize) this._Height = BitConverter.ToInt32(dic["Height"], 0);
+    if (dic.ContainsKey("Mark")) this.Mark = Encoding.UTF8.GetString(dic["Mark"]);
+    Type t = this.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 InvalidOperationException("[STNodePropertyAttribute.Type]参数值必须为[STNodePropertyDescriptor]或者其子类的类型");
+            var desc = (STNodePropertyDescriptor)Activator.CreateInstance(attr.DescriptorType);
+            desc.Node = this;
+            desc.PropertyInfo = p;
+            try {
+                if (dic.ContainsKey(p.Name)) desc.SetValue(dic[p.Name]);
+            } catch (Exception ex) {
+                string strErr = "属性[" + this.Title + "." + p.Name + "]的值无法被还原 可通过重写[STNodePropertyAttribute.GetBytesFromValue(),STNodePropertyAttribute.GetValueFromBytes(byte[])]确保保存和加载时候的二进制数据正确";
+                Exception e = ex;
+                while (e != null) {
+                    strErr += "\r\n----\r\n[" + e.GetType().Name + "] -> " + e.Message;
+                    e = e.InnerException;
+                }
+                throw new InvalidOperationException(strErr, ex);
+            }
+        }
+    }
+}
+
+

在默认的OnLoadNode(dic)中 仅仅对自身基础属性以及被STNodePropertyAttribute标记的属性进行还原

+

对于STNodeHubOnLoadNode(dic)

+
+
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();
+}
+
+
+

OnEditorLoadCompleted

+
+

如果某些节点在整个画布都还原完成后需要一些初始化操作可以重写OnEditorLoadCompleted来完成操作

+
+
/// <summary>
+/// 当编辑器加载完成所有的节点时候发生
+/// </summary>
+protected internal virtual void OnEditorLoadCompleted();
+
+

THE END

+

谢谢你的阅读 同样也谢谢你选择STNodeEditor 如果你觉得STNodeEditor还不错的话可以推荐给你的朋友

+ +
+
+ + \ No newline at end of file diff --git a/docs/tutorials_en.html b/docs/tutorials_en.html new file mode 100644 index 0000000..d4c00eb --- /dev/null +++ b/docs/tutorials_en.html @@ -0,0 +1,1228 @@ + + + + + +STNodeEditor - Tutorials + + + + + +
+ +
+

Foreword

+

Introduction

+

It was a winter. The author who was studying radio security used GNURadio. That was the first time the author used the node editor.

+

-> What? Excuse me... What"s this?.. What the hell is this?...

+

It was a spring season, and I don"t know why the whole world has changed after the Chinese New Year. Everyone was forced to stay at home. The extremely boring author learned Blender. That was the second time the author used the node editor.

+

-> Wo...It turns out that this one is really convenient to use.

+

So some ideas gradually emerged in the author"s mind, making the author want to make one.

+

It was a summer, I don’t know why the author started to learn Davinci again. That was the third time the author used the node editor. The use of this time has doubled the author"s favor with the node editor. The author instantly felt that as long as it is a program that can be modularized and streamlined, everything can be nodeized.

+

So STNodeEditor appeared.

+ +
+

[Basic]STNode

+

STNode is the core of the whole framework. If STNodeEditor is regarded as Desktop, then an STNode can be regarded as an application on the desktop. It is very necessary to develop a strong STNode.

+
+

Create Node

+
+

The base class STNode of the node is modified by abstract, so the node must be extended to STNode

+

STNode contains a large number of virtual functions for developers to overload. For more information, please refer to API Documentation

+
+
using ST.Library.UI.NodeEditor;
+
+namespace WinNodeEditorDemo
+{
+    public class MyNode : STNode
+    {
+        public MyNode() { //Equivalent to OnCreate()
+            this.Title = "TestNode";
+        }
+        //protected override void OnCreate() {
+        //    base.OnCreate();
+        //}
+    }
+}
+//Add to STNodeEditor
+stNodeEditor1.Nodes.Add(new MyNode());
+
+ +

You will find that you only see a title and nothing. Because no input and output options are added to it, let's add some code.

+
+
public class MyNode : STNode
+{
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "TestNode";
+        //get the index of STNodeOption that added
+        int nIndex = this.InputOptions.Add(new STNodeOption("IN_1", typeof(string), false));
+        //get the STNodeOption that added
+        STNodeOption op = this.InputOptions.Add("IN_2", typeof(int), true);
+
+        this.OutputOptions.Add("OUT", typeof(string), false);
+    }
+}
+
+ +

In this way, the added option will be displayed on the node, but this is not enough. You can see that there are two data types string and int. Color should be added to the data type to distinguish different data types.

+
+
public class MyNode : STNode
+{
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "TestNode";
+        int nIndex = this.InputOptions.Add(new STNodeOption("IN_1", typeof(string), false));
+        STNodeOption op = this.InputOptions.Add("IN_2", typeof(int), true);
+        //The highest priority, the color information in the container will be ignored
+        //this.SetOptionDotColor(op, Color.Red); 
+        this.OutputOptions.Add("OUT", typeof(string), false);
+    }
+    //Occurs when the owner changes, submit the color
+    protected override void OnOwnerChanged() {
+        base.OnOwnerChanged();
+        if (this.Owner == null) return;
+        this.Owner.SetTypeColor(typeof(string), Color.Yellow);
+        //will replace old color
+        this.Owner.SetTypeColor(typeof(int), Color.DodgerBlue, true);
+    }
+}
+
+ +

Such a node is created, but this node does not have any functions. In the next case, functions will be added.

+

In any case, developers should try to keep an empty parameter constructor for the extended STNode, otherwise there will be unnecessary troubles in many functions.

+
+

STNodeOption

+
+

From the above case, we can see that STNodeOption is the connection option of STNode. The connection option can be multi-connection and single-connection mode.

+
+
public class MyNode : STNode {
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "MyNode";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+        //multi-connection
+        this.InputOptions.Add("Single", typeof(string), true);
+        //single-connection
+        this.OutputOptions.Add("Multi", typeof(string), false);
+    }
+}
+
+ +

In multi-connection mode, an option can be connected by multiple options of the same data type (rectangle)

+

In single-connection mode, an option can only be connected by one option of the same data type (circle)

+
+

STNodeOption.Empty

+
+

STNodeOption.Empty is a static property, added to STNode is only used to occupy a place during auto layout.

+
+
public class MyNode : STNode {
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "MyNode";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+
+        this.InputOptions.Add(STNodeOption.Empty);
+        this.InputOptions.Add("IN_1", typeof(string), false);
+        this.InputOptions.Add("IN_2", typeof(string), false);
+
+        this.OutputOptions.Add("OUT_1", typeof(string), false);
+        this.OutputOptions.Add(STNodeOption.Empty);
+        this.OutputOptions.Add(STNodeOption.Empty);
+        this.OutputOptions.Add("OUT_2", typeof(string), false);
+    }
+}
+
+ +

a STNodeOption height can set by STNode.ItemHeight(protected)

+
+

STNode.AutoSize

+
+

AutoSizedefault value istrue,when AitoSize is set WidthHeightcan not be set

+
+
public class MyNode : STNode {
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "MyNode";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+
+        this.InputOptions.Add("IN", typeof(string), false);
+        this.OutputOptions.Add("OUT", typeof(string), false);
+        //you should close AutoSize Model before you set the node size
+        this.AutoSize = false;
+        this.Size = new Size(100, 100);
+    }
+}
+
+ +

You can see that the size of MyNode is no longer automatically calculated, but the position of STNodeOption will still be automatically calculated. If you want to modify the position of STNodeOption, you can override OnSetOptionXXX

+
+
public class MyNode : STNode
+{
+    private STNodeOption m_op_in;
+    private STNodeOption m_op_out;
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "MyNode";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+
+        m_op_in = this.InputOptions.Add("IN", typeof(string), false);
+        m_op_out = this.OutputOptions.Add("OUT", typeof(string), false);
+        this.AutoSize = false;
+        this.Size = new Size(100, 100);
+    }
+    //you can set the location of STNodeOption when the AutoSize is true
+    protected override Point OnSetOptionDotLocation(STNodeOption op, Point pt, int nIndex) {
+        if (op == m_op_in) return new Point(pt.X, pt.Y + 20);
+        return base.OnSetOptionDotLocation(op, pt, nIndex);
+    }
+    //you can set the rectangle of STNodeOption when the AutoSize is true
+    protected override Rectangle OnSetOptionTextRectangle(STNodeOption op, Rectangle rect, int nIndex) {
+        if (op == m_op_out) return new Rectangle(rect.X, rect.Y + 20, rect.Width, rect.Height);
+        return base.OnSetOptionTextRectangle(op, rect, nIndex);
+    }
+}
+
+ +

You can see that in the code, the point and text area of the STNodeOption connection line are modified by overloading the function. Why is it not designed to be STNodeOption.DotLeft=xxx because the author thinks it will be more troublesome.

+

The pt and rect passed in the overloaded function are all automatically calculated data so that the developer will have a certain reference when modifying the position. If the method is STNodeOption.DotLeft=xxx, the developer cannot Obtaining a reference position requires all calculations by yourself

+

It also needs to bind events such as STNode.Resize to monitor the changes in the size of STNode to recalculate the position, so the OnSetOptionXXX method is more friendly in comparison.

+

All instances currently have no functions. In the next cases, functions will be added.

+
+

e.g. - ClockNode

+
+

STNodeOption can get all the data input of this option by binding to the DataTransfer event

+

STNodeOption.TransferData(object) function can transfer data to all connections on this option

+

Next, implement a functional node. The best example for now is to create a clock node.

+

Because the content introduced so far is not enough to be able to freely provide arbitrary data to nodes, a node that can generate data by itself is needed.

+

The node outputs the current system time every second

+
+
public class ClockNode : STNode
+{
+    private Thread m_thread;
+    private STNodeOption m_op_out_time;
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "ClockNode";
+        m_op_out_time = this.OutputOptions.Add("Time", typeof(DateTime), false);
+    }
+    //when the owner changed 
+    protected override void OnOwnerChanged() {
+        base.OnOwnerChanged();
+        if (this.Owner == null) {   //when the owner is null abort thread
+            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) will automatically post data to all connections on the option
+                //STNodeOption.TransferData(object) will set STNodeOption.Data automatically
+                m_op_out_time.TransferData(DateTime.Now);
+                //if you need to operate across UI threads in a thread, the node provides Begin/Invoke() to complete the operation.
+                //this.BeginInvoke(new MethodInvoker(() => m_op_out_time.TransferData(DateTime.Now)));
+            }
+        }) { IsBackground = true };
+        m_thread.Start();
+    }
+}
+
+

Of course, we can directly display the time of the above node, but in order to demonstrate the data transfer, we also need a node that accepts the data

+
+
public class ShowClockNode : STNode {
+    private STNodeOption m_op_time_in;
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "ShowTime";
+        //use "single-connection" model
+        m_op_time_in = this.InputOptions.Add("--", typeof(DateTime), true);
+        //This event is triggered when data is transferred to m_op_time_in
+        m_op_time_in.DataTransfer += new STNodeOptionEventHandler(op_DataTransfer);
+    }
+
+    void op_DataTransfer(object sender, STNodeOptionEventArgs e) {
+        //This event is not only triggered when there is data coming in.
+        //This event is also triggered when there is a connection or disconnection,
+        //so you need to check the connection status.
+        if (e.Status != ConnectionStatus.Connected || e.TargetOption.Data == null) {
+            //When STNode.AutoSize=true, it is not recommended to use STNode.SetOptionText
+            //the STNode will recalculate the layout every time the Text changes.
+            //It should be displayed by adding controls.
+            //Since STNodeControl has not yet been mentioned, the current design will be used for now.
+            this.SetOptionText(m_op_time_in, "--");
+        } else {
+            this.SetOptionText(m_op_time_in, ((DateTime)e.TargetOption.Data).ToString());
+        }
+    }
+}
+
+

add to STNodeEditor

+ +

You can see that ShowClockNode is refreshing every second

+
+

STNode.SetOptionXXX

+
+

In the above and previous examples, we can see that when some properties of STNodeOption need to be modified, they are not modified in the way of STNodeOption.XXX=XXX. This design is for safety.

+

The author thinks that STNodeOption can only be modified by its owner, and the method of STNodeOption.XXX=XXX cannot know who modified it and STNode.SetOptionXXX() is marked by protected only internally Is called and inside the function will check whether STNodeOption.Owner is the current class

+
+

About TransferData

+
+

It is not necessary to STNodeOption.TransferData(object) to transfer data. TransferData(object) only actively updates data

+

When a new connection is successful, the DataTransfer event will also be triggered, the following will modify the code of ClockNode

+
+
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);
+        //Assign value to option data
+        m_op_out_time.Data = DateTime.Now;
+    }
+}
+
+ +

You can see that ShowClockNode still shows the time but the data does not change because the DataTransfer event will be triggered when the connection is successful. In the event, ShowClockNode gets the data of the ClockNode option through e.TargetOption.Data

+

When a connection is successful and disconnected, the event trigger sequence is as follows

+

Connecting-Connected-DataTransfer | DisConnecting-DataTransfer-DisConnected

+
+

STNodeHub

+
+ +

STNodeHub is a built-in node that can disperse one output to multiple inputs or concentrate multiple outputs on one input point to prevent repeated connections. It can also be used for layout when the node connection is complicated.

+

[basic]STNodeControl

+

As the base class of STNode control, STNodeControl has many properties and events with the same name as System.Windows.Forms.Control, allowing developers to develop a node like a WinForm program.

+

In this version (2.0), no available control is provided. Only the STNodeControl base class needs to be extended by the developer. If available later, the author will improve it.

+
+

add a control

+
+

Same as System.Windows.Forms.Control STNode has the Controls collection and its data type is STNodeControl

+
+
public class MyNode : STNode
+{
+    protected override void OnCreate() {
+        base.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");
+    }
+}
+
+ +

You can see that there is almost no difference between developing a WinForm program. The only difference is that STNode does not yet provide a WYSIWYG UI designer.

+
+

Customize a Button

+
+

Although the above code looks like it adds a button control, in fact it is just the default drawing style of STNodeControl

+

The following is to customize a Button control with mouse hovering and clicking effects to make it more like a button

+
+
public class STNodeButton : STNodeControl {
+
+    private bool m_b_enter;
+    private bool m_b_down;
+
+    protected override void OnMouseEnter(EventArgs e) {
+        base.OnMouseEnter(e);
+        m_b_enter = true;
+        this.Invalidate();
+    }
+
+    protected override void OnMouseLeave(EventArgs e) {
+        base.OnMouseLeave(e);
+        m_b_enter = false;
+        this.Invalidate();
+    }
+
+    protected override void OnMouseDown(MouseEventArgs e) {
+        base.OnMouseDown(e);
+        m_b_down = true;
+        this.Invalidate();
+    }
+
+    protected override void OnMouseUp(MouseEventArgs e) {
+        base.OnMouseUp(e);
+        m_b_down = false;
+        this.Invalidate();
+    }
+
+    protected override void OnPaint(DrawingTools dt) {
+        //base.OnPaint(dt);
+        Graphics g = dt.Graphics;
+        SolidBrush brush = dt.SolidBrush;
+        brush.Color = base.BackColor;
+        if (m_b_down) brush.Color = Color.SkyBlue;
+        else if (m_b_enter) brush.Color = Color.DodgerBlue;
+        g.FillRectangle(brush, 0, 0, this.Width, this.Height);
+        g.DrawString(this.Text, this.Font, Brushes.White, this.ClientRectangle, base.m_sf);
+    }
+}
+
+ +

Of course, in order to make the code as simple as possible, the effect of the button is written in the code. The above code is just to demonstrate how to build a custom control. Of course, you need to have some GDI related knowledge before this.

+

<GDI+Programming> is a good book

+ +
+

e.g. - Image info

+
+

In the above ClockNode case, the data for the data is written in the node through code. Next, in this case, the data is obtained through the STNodeButton for output.

+
+
public class ImageShowNode : STNode
+{
+    private STNodeOption m_op_out;
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "ImageShowNode";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+        this.AutoSize = false;
+        this.Size = new Size(160, 150);
+        m_op_out = this.OutputOptions.Add("", typeof(Image), false);
+
+        var ctrl = new STNodeButton();
+        ctrl.Text = "Open Image";
+        ctrl.Location = new Point(5, 0);
+        ctrl.Size = new Size(150, 20);
+        this.Controls.Add(ctrl);
+        ctrl.MouseClick += new MouseEventHandler(ctrl_MouseClick);
+    }
+
+    void ctrl_MouseClick(object sender, MouseEventArgs e) {
+        OpenFileDialog ofd = new OpenFileDialog();
+        ofd.Filter = "*.png|*.png|*.jpg|*.jpg";
+        if (ofd.ShowDialog() != DialogResult.OK) return;
+        m_op_out.TransferData(Image.FromFile(ofd.FileName), true);
+        this.Invalidate();
+    }
+
+    protected override void OnDrawBody(DrawingTools dt) {
+        base.OnDrawBody(dt);
+        //of course you can extended STNodeControl to build a "STNodePictureBox" for display image
+        Graphics g = dt.Graphics;
+        Rectangle rect = new Rectangle(this.Left + 5, this.Top + this.TitleHeight + 20, 150, 105);
+        g.FillRectangle(Brushes.Gray, rect);
+        if (m_op_out.Data != null)
+            g.DrawImage((Image)m_op_out.Data, rect);
+    }
+}
+
+

Now we need a node to get image size.

+
+
public class ImageSizeNode : STNode {
+
+    private STNodeOption m_op_in;
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "ImageSize";
+        this.TitleColor = Color.FromArgb(200, Color.Goldenrod);
+        m_op_in = this.InputOptions.Add("--", typeof(Image), true);
+        m_op_in.DataTransfer += new STNodeOptionEventHandler(m_op_in_DataTransfer);
+    }
+
+    void m_op_in_DataTransfer(object sender, STNodeOptionEventArgs e) {
+        if (e.Status != ConnectionStatus.Connected || e.TargetOption.Data == null) {
+            this.SetOptionText(m_op_in, "--");
+        } else { 
+            Image img = (Image)e.TargetOption.Data;
+            this.SetOptionText(m_op_in, "W:" + img.Width + " H:" + img.Height);
+        }
+    }
+}
+
+ +

clickthe Open Image button, you can select an image and display it in the node. After the ImageSizeNode is connected, the size of the image will be display.

+

The code of the ImageChannel node is not given here. The code is used in the WinNodeEditorDemo project to extract the RGB channel of an image. For ImageShowNode, it just provides the data source and displays it. For the ImageSizeNode and ImageChannel nodes, they don"t know what node will be connected. They just complete their functions and package the results to the output option, waiting to be connected by the next node

+

The execution logic is completely connected by the user to connect their functions together. During the development, there is no interaction between nodes and nodes. The only thing that ties them together is an Image data type, so that nodes and nodes There is no coupling relationship between them. High class poly low coupling.

+

[basic]STNodeEditor

+

STNodeEditor as a container of STNode also provides a large number of properties and events for developers to use. For more detailed API list of STNodeEditor, please refer to API document

+
+

Save canvas

+
+

The relationship between nodes and connections in STNodeEditor can be saved to a file

+

The contents of the canvas can be saved to a file through the STNodeEditor.SaveCanvas(string strFileName) function

+

Note that the SaveCanvas() function will call the internal byte[] STNode.GetSaveData() function to get the binary data of each node

+

The GetSaveData() function does not serialize the node itself. The GetSaveData() function binarizes the basic data and original attributes of the node itself and then calls virtual OnSaveNode(Dictionary<string, byte[]> dic) to The expansion node asks for the data that the node needs to save

+

If there is a saving requirement, the node developer may need to override the OnSaveNode() function to ensure that some required data can be saved

+

More content about saving nodes will be introduced in the following content

+
+

Load canvas

+
+

The saved data can be loaded from the file through the STNodeEditor.LoadCanvas(string strFileName) function

+

If STNodeEditor has nodes in other assemblies, you need to load the assembly by calling STNodeEditor.LoadAssembly(string strFile) to ensure that the nodes in the file can be restored correctly

+

Because the restored node is not serialized, a node is dynamically created by (STNode)Activator.CreateInstance(stNodeType) and then called virtual OnSaveNode(Dictionary<string, byte[]> dic) to restore the data, while dic Is the data saved by OnSaveNode()

+

Because the restore node is dynamically created through reflection, an empty parameter constructor must be provided in the extended STNode

+

More content about saving nodes will be introduced in the following content

+
+

useful event

+
+

ActiveChanged,SelectedChanged can monitor the selected changes of the node in the control

+
+
stNodeEditor1.ActiveChanged += (s, e) => Console.WriteLine(stNodeEditor1.ActiveNode.Title);
+
+stNodeEditor1.SelectedChanged += (s, e) => {
+    foreach(var n in stNodeEditor1.GetSelectedNode()){
+        Console.WriteLine(n.Title);
+    }
+};
+
+

If you want to display the scale on the editor after each zoom of the canvas, you can get it through the CanvasScaled event

+
+
stNodeEditor1.CanvasScaled += (s, e) => {
+    stNodeEditor1.ShowAlert(stNodeEditor1.CanvasScale.ToString("F2"),
+        Color.White, Color.FromArgb(127, 255, 255, 0));
+};
+
+

If you want to display the connection status when there are nodes connected in the canvas, you can get the status through the OptionConnected event

+
+
stNodeEditor1.OptionConnected += (s, e) => {
+    stNodeEditor1.ShowAlert(e.Status.ToString(), Color.White,
+        Color.FromArgb(125, e.Status ConnectionStatus.Connected ? Color.Lime : Color.Red));
+};
+
+
+

useful function

+
+
+
/// <summary>
+/// Move the origin position of the canvas to the specified control position 
+/// (cannot be moved when there is no Node)
+/// </summary>
+/// <param name="x">X</param>
+/// <param name="y">Y</param>
+/// <param name="bAnimation">use animation</param>
+/// <param name="ma">Specify the position that needs to be modified</param>
+public void MoveCanvas(float x, float y, bool bAnimation, CanvasMoveArgs ma);
+
+
+
+
/// <summary>
+/// Scale Canvas(cannot be moved when there is no Node)
+/// </summary>
+/// <param name="f">scale</param>
+/// <param name="x">The position of the zoom center X on the control</param>
+/// <param name="y">The position of the zoom center Y on the control</param>
+public void ScaleCanvas(float f, float x, float y);
+
+
+
+
/// <summary>
+/// Add the default data type color to the editor
+/// </summary>
+/// <param name="t">data type</param>
+/// <param name="clr">color</param>
+/// <param name="bReplace">replace</param>
+/// <returns>new color</returns>
+public Color SetTypeColor(Type t, Color clr, bool bReplace);
+
+
+
+
/// <summary>
+/// Display information in the canvas
+/// </summary>
+/// <param name="strText">message text</param>
+/// <param name="foreColor">fore color</param>
+/// <param name="backColor">back color</param>
+/// <param name="nTime">time</param>
+/// <param name="al">message location</param>
+/// <param name="bRedraw">redraw</param>
+void ShowAlert(string strText, Color foreColor, Color backColor, int nTime, AlertLocation al, bool bRedraw);
+//e.g.
+stNodeEditor1.ShowAlert("this is test info", Color.White, Color.FromArgb(200, Color.Yellow));
+
+ +

For more propertyfunctioneventsofSTNodeEditor, please refer to API document This document pays more attention to the example of STNode

+

STNodePropertyGrid

+

STNodePropertyGrid is another control released with the class library and can be used with STNodeEditor

+ +

There are two panels in STNodePropertyGrid, which can be switched by the button on the top Property Panel and Node Information Panel.

+

Only include attribute or node information will display their panel

+
+

how to use

+
+

The core method of STNodePropertyGrid is SetNode(STNode) usually used with STNodeEditor

+
+
stNodeEditor1.ActiveChanged += (s, e) => stNodePropertyGrid1.SetNode(stNodeEditor1.ActiveNode);
+
+

STNode is a class which of course can have properties, and STNodePropertyGrid is to display and modify them, just like what you see in the UI designer during the development of WinForm

+

Let"s try it

+
+
public class PropertyTestNode : STNode {
+
+    private int _Number;
+
+    public int Number {
+        get { return _Number; }
+        set { _Number = value; }
+    }
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "PropertyTest";
+    }
+}
+
+ +

You will find that the Number property is not displayed as you expected

+

The difference with System.Windows.Forms.PropertyGrid is that PropertyGrid will display all the properties in class while STNodePropertyGrid does not.

+

S T N O D E PropertyGrid This is STNodePropertyGrid and does not display a property casually, because the author thinks that the developer may not want all the properties in STNode to be displayed, even if it needs to be displayed, the developer may not want to display a property. What you see in the window is Number but a different name. After all, Number is used when writing code

+

Only property with the STNodePropertyAttribute feature added will be displayed in the STNodePropertyGrid

+
+

STNodePropertyAttribute

+
+

STNodePropertyAttribute has three properties Name, Description and DescriptorType

+

Name-the name of this property that you want to display on STNodePropertyGrid

+

Description-The description you want to display when the left mouse button is long press the property name on STNodePropertyGrid

+

DescriptorType-Data interaction interface with properoty editor, this property will be mentioned later

+

The constructor of STNodePropertyAttribute is STNodePropertyAttribute(string strName,string strDescription)

+
+
public class PropertyTestNode : STNode
+{
+    private int _Number;
+    [STNodeProperty("Name", "Description for this property")]
+    public int Number {
+        get { return _Number; }
+        set { _Number = value; }
+    }
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "PropertyTest";
+    }
+}
+
+ +

Now you can see that the property is displayed correctly and can be set, and long press the property name will display the description information

+
+

STNodeAttribute

+
+

If you want to display node information, STNode needs to be marked with the STNodeAttribute feature

+
+
[STNode("AA/BB", "Author", "Mail", "Link", "Description")]
+public class PropertyTestNode : STNode
+{
+    private int _Number;
+    [STNodeProperty("Name", "Description for this property")]
+    public int Number {
+        get { return _Number; }
+        set { _Number = value; }
+    }
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "PropertyTest";
+    }
+}
+
+

You can switch between panels through the button in the top

+

The AA/BB is used to construct the path in STNodeTreeView

+ +
+
stNodePropertyGrid1.SetInfoKey("Author", "Mail", "Link", "Show Help");
+
+

The key of the content of the Information Panel can be used to set the language through the SetInfoKey() function, which is displayed in simplified Chinese by default.

+
+

Show help

+
+

In the example of Information Panel, you can see that the Show Help button is not available. If you want it to be available, you need to provide a magic method

+
+
[STNode("AA/BB", "Author", "Mail", "Link", "Description")]
+public class PropertyTestNode : STNode
+{
+    private int _Number;
+    [STNodeProperty("Name", "Description for this property")]
+    public int Number {
+        get { return _Number; }
+        set { _Number = value; }
+    }
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "PropertyTest";
+    }
+    /// <summary>
+    /// This method is a magic method
+    /// If there is "static void ShowHelpInfo(string)" and this class is marked by "STNodeAttribute"
+    /// Then this method will be used as the "Show Help" function on the property editor
+    /// </summary>
+    /// <param name="strFileName">The file path of the module where this class is located.</param>
+    public static void ShowHelpInfo(string strFileName) {
+        MessageBox.Show("this is -> ShowHelpInfo(string);\r\n" + strFileName);
+    }
+}
+
+ +

Now find that the Show Help button has become enabled. STNodeAttribute also provides two static functions.

+
+
/// <summary>
+/// Get help method for stNodeType
+/// </summary>
+/// <param name="stNodeType">stNodeType</param>
+/// <returns>MethodInfo</returns>
+public static MethodInfo GetHelpMethod(Type stNodeType);
+/// <summary>
+/// Excute the "ShowHelpInfo" for stNodeType
+/// </summary>
+/// <param name="stNodeType">节点类型</param>
+public static void ShowHelp(Type stNodeType);
+
+
+

e.g. - Add number

+
+

STNodePropertyGrid can display and modify properties. Then this case will provide a data input through the STNodePropertyGrid.

+
+
public class NumberInputNode : STNode
+{
+    private int _Number;
+    [STNodeProperty("Input", "Input number")]
+    public int Number {
+        get { return _Number; }
+        set {
+            _Number = value;
+            this.SetOptionText(m_op_out, value.ToString());
+            m_op_out.TransferData(value);
+        }
+    }
+
+    private STNodeOption m_op_out;
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "NumberInput";
+        m_op_out = this.OutputOptions.Add("0", typeof(int), false);
+    }
+}
+
+
+
public class NumberAddNode : STNode {
+
+    private STNodeOption m_op_in_1;
+    private STNodeOption m_op_in_2;
+    private STNodeOption m_op_out;
+
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "NumberAdd";
+        m_op_in_1 = this.InputOptions.Add("0", typeof(int), true);
+        m_op_in_2 = this.InputOptions.Add("0", typeof(int), true);
+        m_op_out = this.OutputOptions.Add("0", typeof(int), false);
+
+        m_op_in_1.DataTransfer += new STNodeOptionEventHandler(m_op_in_DataTransfer);
+        m_op_in_2.DataTransfer += new STNodeOptionEventHandler(m_op_in_DataTransfer);
+    }
+
+    void m_op_in_DataTransfer(object sender, STNodeOptionEventArgs e) {
+        if (e.Status != ConnectionStatus.Connected || e.TargetOption == null) {
+            if (sender == m_op_in_1) m_op_in_1.Data = 0;
+            if (sender == m_op_in_2) m_op_in_2.Data = 0;
+        } else {
+            if (sender == m_op_in_1) m_op_in_1.Data = e.TargetOption.Data;
+            if (sender == m_op_in_2) m_op_in_2.Data = e.TargetOption.Data;
+        }
+        if (m_op_in_1.Data == null) m_op_in_1.Data = 0;
+        if (m_op_in_2.Data == null) m_op_in_2.Data = 0;
+        int nResult = (int)m_op_in_1.Data + (int)m_op_in_2.Data;
+        this.SetOptionText(m_op_in_1, m_op_in_1.Data.ToString());
+        this.SetOptionText(m_op_in_2, m_op_in_2.Data.ToString());
+        this.SetOptionText(m_op_out, nResult.ToString());
+        m_op_out.TransferData(nResult);
+    }
+}
+
+ +

Pass the entered number through the set accessor of the Number property.

+
+

ReadOnlyModel

+
+

In some cases, you don’t want STNodePropertyGrid to set the properties, you just want to display the properties, you can enable ReadOnlyModel

+
+
stNodePropertyGrid1.ReadOnlyModel = true;
+
+ +

You can not set the properoty value from STNodePropertyGrid when the ReadOnlyModel is true

+

STNodePropertyDescriptor

+

The Name and Description properties of STNodePropertyAttribute are introduced above. There is also a properoty DescriptorType whose data type is Type and the default value is typeof(STNodePropertyDescriptor)

+

Although from the current case, there is no problem with the above operation, but not all data type properties can be correctly supported by STNodePropertyGrid. The default STNodePropertyDescriptor only supports the following data types.

+

intfloatdoubleboolstringEnum and their Array

+
+

e.g. - ColorTestNode

+
+

Create a node below and add an properoty of type Color

+
+
public class ColorTestNode : STNode
+{
+    [STNodeProperty("TitleColor", "Get or set the node TitleColor")]
+    public Color ColorTest {
+        get { return this.TitleColor; }
+        set { this.TitleColor = value; }
+    }
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "ColorNode";
+    }
+}
+
+ +

If you run the above code, you will find that there will be errors when setting properties through STNodePropertyGrid, and the display of property values in STNodePropertyGrid is also very strange.

+

Even though System.Windows.Forms.PropertyGrid can support many data types, it does not support all types. For example, when the property type is a user-defined type, the property editor cannot know how to interact with the properties on the property grid.

+

The solution for System.Windows.Forms.PropertyGrid is to provide TypeConverter, mark the target type with TypeConverter and implement overloading so that PropertyGrid can know how to interact with the PropertyGrid through TypeConverter .

+

The solution provided by STNodePropertyGrid is STNodePropertyDescriptor.

+
+

about properoty discriptor

+
+

STNodePropertyGrid can correctly obtain and modify the value of the STNode property, because STNodePropertyDescriptor is performing data conversion and responding to some event operations on the property window.

+

The property marked by STNodePropertyAttribute will be packaged as STNodePropertyDescriptor and passed to STNodePropertyGrid. A STNodePropertyDescriptor contains the Name and Description in STNodePropertyAttribute and the position information of the attribute will be displayed on STNodePropertyGrid, and How to respond to mouse events or keyboard events.

+

It can be considered that STNodePropertyDescriptor is a graphical interface for each properoty marked by STNodePropertyAttribute, and the main function is to transfer data between the graphical interface and the real properoty.

+
+

GetValueFromString()

+
+
+
/// <summary>
+/// Convert the value of the string to the value of the property's type.
+/// Only int float double string bool and the above types of Array are supported by default
+/// If the target type is not in the above, please override the function to convert it by yourself.
+/// </summary>
+/// <param name="strText">text</param>
+/// <returns>the properoty value</returns>
+protected internal virtual object GetValueFromString(string strText);
+
+

The GetValueFromString() function converts the string entered by the user in the STNodePropertyGrid into the real value required by the property.

+

For example: If the property is int type value and the user can only enter the string 123 in STNodePropertyGrid, then the default GetValueFromString() function will be int.Parse(strText) such as string Type 123 becomes 123 of type int

+
+

GetStringFromValue()

+
+
+
/// <summary>
+/// Convert the value of the properoty to a string.
+/// Calling Tostring() of the property value is the default operation.
+/// If you need special processing, please override this function to convert by yourself
+/// </summary>
+/// <returns>string</returns>
+protected internal virtual string GetStringFromValue();
+
+

The GetStringFromValue() function converts the property value into a string and displays it in the STNodePropertyGrid. The default GetStringFromValue() internally just calls the property value ToString().

+
+

e.g. - ColorTestNode

+
+

Extend the STNodePropertyDescriptor of ColorTestNode.ColorTest in the above failed example.

+
+
public class ColorTestNode : STNode
+{
+    //Specify to use the extended STNodePropertyDescriptor in order to complete the support for the Color type.
+    [STNodeProperty("TitleColor", "Get or set the node TitleColor", DescriptorType = typeof(ColorDescriptor))]
+    public Color ColorTest {
+        get { return this.TitleColor; }
+        set { this.TitleColor = value; }
+    }
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "ColorNode";
+    }
+}
+
+public class ColorDescriptor : STNodePropertyDescriptor
+{
+    protected override object GetValueFromString(string strText) {
+        string[] strs = strText.Split(',');
+        return Color.FromArgb(int.Parse(strs[0]), int.Parse(strs[1]), int.Parse(strs[2]), int.Parse(strs[3]));
+        //return base.GetValueFromString(strText);
+    }
+
+    protected override string GetStringFromValue() {
+        var v = (Color)this.GetValue(null);//get the value
+        return v.A + "," + v.R + "," + v.G + "," + v.B;
+        //return base.GetStringFromValue();
+    }
+}
+
+ +

At this time, the property value can be displayed and set correctly, because GetValueFromString() and GetStringFromValue() correctly complete the conversion between the property value and the string.

+
+

e.g. - ColorTestNode

+
+

Although the above example can be used to set the Color property through ARGB,but this method is not friendly enough.

+ +

Can it be the same as System.Windows.Forms.PropertyGrid to allow users to make visual settings. STNodePropertyDescriptor can of course do it.

+

STNodePropertyDescriptor can be regarded as a custom control that displays properties. Since it is a custom control, it can respond to mouse events.

+

now add some code to ColorDescriptor

+
+
public class ColorDescriptor : STNodePropertyDescriptor
+{
+    //This rect is used as a color preview for drawing on the STNodePropertyGrid.
+    private Rectangle m_rect;
+
+    protected override object GetValueFromString(string strText) {
+        string[] strs = strText.Split(',');
+        return Color.FromArgb(int.Parse(strs[0]), int.Parse(strs[1]), int.Parse(strs[2]), int.Parse(strs[3]));
+        //return base.GetValueFromString(strText);
+    }
+
+    protected override string GetStringFromValue() {
+        var v = (Color)this.GetValue(null);
+        return v.A + "," + v.R + "," + v.G + "," + v.B;
+        //return base.GetStringFromValue();
+    }
+
+    //Called when the position of this property is confirmed in the STNodePropertyGrid.
+    protected override void OnSetItemLocation() {
+        base.OnSetItemLocation();
+        Rectangle rect = base.RectangleR;
+        m_rect = new Rectangle(rect.Right - 25, rect.Top + 5, 19, 12);
+    }
+    //Called when drawing this properoty value area in the STNodePropertyGrid
+    protected override void OnDrawValueRectangle(DrawingTools dt) {
+        base.OnDrawValueRectangle(dt);//call the base and then draw color preview
+        dt.SolidBrush.Color = (Color)this.GetValue(null);
+        dt.Graphics.FillRectangle(dt.SolidBrush, m_rect);//fill color
+        dt.Graphics.DrawRectangle(Pens.Black, m_rect);   //draw rectangle
+    }
+
+    protected override void OnMouseClick(MouseEventArgs e) {
+        //If the user clicks in the color preview area, show system color dialog.
+        if (m_rect.Contains(e.Location)) {
+            ColorDialog cd = new ColorDialog();
+            if (cd.ShowDialog() != DialogResult.OK) return;
+            this.SetValue(cd.Color, null);
+            this.Invalidate();
+            return;
+        }
+        //Otherwise, show the text input box by default.
+        base.OnMouseClick(e);
+    }
+}
+
+ +

At this point, you can see that compared with the previous example, there is one more color preview area, and clicking the preview area will pop up the system color dialog box to set the property value. If you click in the non-preview area, the default operation method will be show text input box to input ARGB.

+

About STNodePropertyDescriptor there are two important overloaded functions GetBytesFromValue() and SetValue(byte[]) which will be introduced in Save Canvas later.

+

STNodeTreeView

+

STNodeTreeView, like STNodePropertyGrid, is another control released with the class library and can be used with STNodeEditor.

+ +

The nodes in STNodeTreeView can be dragged and dropped into STNodeEditor, and preview and search functions are provided.

+

STNodeTreeView is easy to use, and there is no need to create a tree directory by yourself like System.Windows.Forms.TreeView.

+

Mark the STNode subclass by STNodeAttribute to set the path you want to display in STNodeTreeView and the information you want to display in STNodePropertyGrid.

+

Note: If you want nodes to be displayed in STNodeTreeView, you must use STNodeAttribute to mark the STNode subclass.

+

how to use

+

The nodes in the above example are all added through the STNodeEditor.Nodes.Add(STNode) method, and then added through the STNodeTreeView drag-and-drop method. But before that, you need to add the nodes to the STNodeTreeView first.

+
+
[STNode("AA/BB", "Author", "Mail", "Link", "Description")]
+public class MyNode : STNode
+{
+    protected override void OnCreate() {
+        base.OnCreate();
+        this.Title = "TreeViewTest";
+    }
+}
+//add to STNodeTreeView
+stNodeTreeView1.AddNode(typeof(MyNode));
+
+ +

You can see that the added nodes are displayed in STNodeTreeView and the path is automatically created. The nodes can be previewed and dragged to STNodeEditor.

+

There cannot be a node with the same name in the same path, otherwise it will be overwritten.

+
+

Load assembly

+
+

In addition to STNodeTreeView.AddNode(Type) to add nodes to the tree, you can also load nodes from an assembly through the LoadAssembly(string) method. LoadAssembly(string) will automatically check the nodes marked by STNodeAttribute in the assembly and add them, and will create a root node display with the assembly name.

+
+
stNodeTreeView1.AddNode(typeof(MyNode));
+stNodeTreeView1.LoadAssembly(Application.ExecutablePath);
+//If the node is in another assembly, STNodeEditor should do the same to confirm that the node is recognized by STNodeEditor.
+stNodeEditor1.LoadAssembly(Application.ExecutablePath);
+
+ +

You can see that there are two root nodes AA and WinNodeEditorDemo. In fact, MyNode belongs to the WinNodeEditorDemo assembly, but it cannot be found in the child nodes of WinNodeEditorDemo. Because MyNode is added before loading the assembly, when the loading assembly recognizes that MyNode has been added repeatedly, it is skipped.

+

STNodeTreeView cannot add a node of the same type and the same name with the same path.

+

Save nodes

+

In the introduction of STNodeEditor above, we mentioned the saving and loading of canvas. Here we will introduce it in detail.

+
+

Save

+
+

When STNodeEditor.SaveCanvas(), STNodeEditor will call internal byte[] STNode.GetSaveData() of each Nodes to get the binary data of each node.

+

GetSaveData() is an internal method modified by internal. The method body is as follows.

+
+
internal byte[] GetSaveData() {
+    List<byte> lst = new List<byte>();
+    Type t = this.GetType();
+    byte[] byData = Encoding.UTF8.GetBytes(t.Module.Name + "|" + t.FullName);
+    lst.Add((byte)byData.Length);
+    lst.AddRange(byData);
+    byData = Encoding.UTF8.GetBytes(t.GUID.ToString());
+    lst.Add((byte)byData.Length);
+    lst.AddRange(byData);
+
+    var dic = this.OnSaveNode();
+    if (dic != null) {
+        foreach (var v in dic) {
+            byData = Encoding.UTF8.GetBytes(v.Key);
+            lst.AddRange(BitConverter.GetBytes(byData.Length));
+            lst.AddRange(byData);
+            lst.AddRange(BitConverter.GetBytes(v.Value.Length));
+            lst.AddRange(v.Value);
+        }
+    }
+    return lst.ToArray();
+}
+
+

You can see that GetSaveData() gets the binary of its own basic data, and then calls OnSaveNode() to get a dictionary.

+
+
internal Dictionary<string, byte[]> OnSaveNode() {
+    Dictionary<string, byte[]> dic = new Dictionary<string, byte[]>();
+    dic.Add("Guid", this._Guid.ToByteArray());
+    dic.Add("Left", BitConverter.GetBytes(this._Left));
+    dic.Add("Top", BitConverter.GetBytes(this._Top));
+    dic.Add("Width", BitConverter.GetBytes(this._Width));
+    dic.Add("Height", BitConverter.GetBytes(this._Height));
+    dic.Add("AutoSize", new byte[] { (byte)(this._AutoSize ? 1 : 0) });
+    if (this._Mark != null) dic.Add("Mark", Encoding.UTF8.GetBytes(this._Mark));
+    dic.Add("LockOption", new byte[] { (byte)(this._LockLocation ? 1 : 0) });
+    dic.Add("LockLocation", new byte[] { (byte)(this._LockLocation ? 1 : 0) });
+    Type t = this.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;//Get the property marked by STNodePropertyAttribute and save them automatically.
+            object obj = Activator.CreateInstance(attr.DescriptorType);
+            if (!(obj is STNodePropertyDescriptor))
+                throw new InvalidOperationException("[STNodePropertyAttribute.Type]参数值必须为[STNodePropertyDescriptor]或者其子类的类型");
+            var desc = (STNodePropertyDescriptor)Activator.CreateInstance(attr.DescriptorType);
+            desc.Node = this;
+            desc.PropertyInfo = p;
+            byte[] byData = desc.GetBytesFromValue();//Get the binary data of the property through STNodePropertyDescriptor.
+            if (byData == null) continue;
+            dic.Add(p.Name, byData);
+        }
+    }
+    this.OnSaveNode(dic);
+    return dic;
+}
+/// <summary>
+/// When saving the node, get the data that needs to be saved from the expansion node.
+/// Note: Saving is not serialization. When restoring, only re-create this Node through the empty parameter constructor,
+/// and then call OnLoadNode() to restore the saved data.
+/// </summary>
+/// <param name="dic">the data need to save</param>
+protected virtual void OnSaveNode(Dictionary<string, byte[]> dic) { }
+
+

From the above code, you can see that STNode will save binary data of its own basic property, and will recognize the property marked by STNodePropertyAttribute and get the property binary data through GetBytesFromValue(), and then call OnSaveNode(dic) Ask the expansion node for the data that needs to be saved.

+

If there is an properoty that needs to be saved, it should be marked with STNodePropertyAttribute and make sure that GetBytesFromValue() can correctly get the binary data of the property, or save it through OnSaveNode(dic)

+

If there are private fields that need to be saved, they should be saved through OnSaveNode(dic).

+
+

OnSaveNode(dic)

+
+

OnSaveNode(dic) and OnLoadNode(dic) correspond to each other.

+
+
protected override void OnSaveNode(Dictionary<string, byte[]> dic) {
+    dic.Add("count", BitConverter.GetBytes(this.InputOptionsCount));
+}
+
+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();
+}
+
+

The above code snippet is the overload of OnSaveNode(dic) and OnLoadNode(dic) in STNodeHub. You can see that an additional count data is saved because the option of STNodeHub is dynamically created, and a newly created STNodeHub has only one line of connection. Therefore, you need to record the status when saving to ensure that the connection status can be restored correctly, and then restore the status in OnLoadNode(dic).

+

In addition to saving node data, STNodeEditor also saves the connection relationship of node options. It will number each STNodeOption of all nodes in the current canvas and save the numbering information. Therefore, when saving and restoring STNodeHub, make sure that the number of STNodeOption is unchanged.

+
+

GetBytesFromValue()

+
+

For properties marked by STNodePropertyAttribute, STNodePropertyDescriptor.GetBytesFromValue() will be automatically called when saving to get the binary data of the attribute.

+

GetBytesFromValue() and GetValueFromBytes() correspond to each other

+
+
/// <summary>
+/// Convert properoty value to binary It is called when saving node properoty.
+/// By default, GetStringFromValue() is called and then the string is converted to binary data.
+/// If you need special processing, please override this method to convert by yourself and override GetValueFromBytes()
+/// </summary>
+/// <returns>binary data</returns>
+protected internal virtual byte[] GetBytesFromValue() {
+    string strText = this.GetStringFromValue();
+    if (strText == null) return null;
+    return Encoding.UTF8.GetBytes(strText);
+}
+/// <summary>
+/// When restoring the properoty value, convert the binary to the properoty value.
+/// Convert it to a string by default and then call GetValueFromString(string).
+/// If override this method,you should override GetBytesFromValue() together.
+/// </summary>
+/// <param name="byData">binary data</param>
+/// <returns>properoty value</returns>
+protected internal virtual object GetValueFromBytes(byte[] byData) {
+    if (byData == null) return null;
+    string strText = Encoding.UTF8.GetString(byData);
+    return this.GetValueFromString(strText);
+}
+
+

STNodePropertyDescriptor.GetBytesFromValue() calls STNodePropertyDescriptor.GetStringFromValue() by default to get the string value of the property and then converts the string to byte[].

+

If GetStringFromValue() and GetValueFromString(strText) can run correctly, then the property value can be saved correctly using the default processing method.

+
+

OnLoadNode(dic)

+
+

When restoring a node, STNodeEditor creates a node through (STNode)Activator.CreateInstance(stNodeType) and then calls OnLoadNode(dic).

+
+
protected internal virtual void OnLoadNode(Dictionary<string, byte[]> dic) {
+    if (dic.ContainsKey("AutoSize")) this._AutoSize = dic["AutoSize"][0] == 1;
+    if (dic.ContainsKey("LockOption")) this._LockOption = dic["LockOption"][0] == 1;
+    if (dic.ContainsKey("LockLocation")) this._LockLocation = dic["LockLocation"][0] == 1;
+    if (dic.ContainsKey("Guid")) this._Guid = new Guid(dic["Guid"]);
+    if (dic.ContainsKey("Left")) this._Left = BitConverter.ToInt32(dic["Left"], 0);
+    if (dic.ContainsKey("Top")) this._Top = BitConverter.ToInt32(dic["Top"], 0);
+    if (dic.ContainsKey("Width") && !this._AutoSize) this._Width = BitConverter.ToInt32(dic["Width"], 0);
+    if (dic.ContainsKey("Height") && !this._AutoSize) this._Height = BitConverter.ToInt32(dic["Height"], 0);
+    if (dic.ContainsKey("Mark")) this.Mark = Encoding.UTF8.GetString(dic["Mark"]);
+    Type t = this.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 InvalidOperationException("[STNodePropertyAttribute.Type]参数值必须为[STNodePropertyDescriptor]或者其子类的类型");
+            var desc = (STNodePropertyDescriptor)Activator.CreateInstance(attr.DescriptorType);
+            desc.Node = this;
+            desc.PropertyInfo = p;
+            try {
+                if (dic.ContainsKey(p.Name)) desc.SetValue(dic[p.Name]);
+            } catch (Exception ex) {
+                string strErr = "属性[" + this.Title + "." + p.Name + "]的值无法被还原 可通过重写[STNodePropertyAttribute.GetBytesFromValue(),STNodePropertyAttribute.GetValueFromBytes(byte[])]确保保存和加载时候的二进制数据正确";
+                Exception e = ex;
+                while (e != null) {
+                    strErr += "\r\n----\r\n[" + e.GetType().Name + "] -> " + e.Message;
+                    e = e.InnerException;
+                }
+                throw new InvalidOperationException(strErr, ex);
+            }
+        }
+    }
+}
+
+

The default OnLoadNode(dic) only restores its own basic properties and properties marked by STNodePropertyAttribute.

+

For OnLoadNode(dic) of STNodeHub

+
+
protected internal override void OnLoadNode(Dictionary<string, byte[]> dic) {
+    base.OnLoadNode(dic);//First restore its own properties.
+    int nCount = BitConverter.ToInt32(dic["count"], 0);
+    while (this.InputOptionsCount < nCount && this.InputOptionsCount != nCount) this.Addhub();
+}
+
+
+

OnEditorLoadCompleted

+
+

If some nodes need some initialization operations after the canvas is restored, you can override OnEditorLoadCompleted to complete the operation.

+
+
/// <summary>
+/// Occurs when the editor has finished loading all nodes.
+/// </summary>
+protected internal virtual void OnEditorLoadCompleted();
+
+

THE END

+

Thank you for reading. Thank you for choosing STNodeEditor. If you think STNodeEditor is not bad, you can recommend it to your friends.

+ +
+
+ + \ No newline at end of file