Added STNodeEditor and translated to english

This commit is contained in:
dexy 2022-01-28 04:16:19 +11:00
parent 8207da8d70
commit 7eaf984005
17 changed files with 8026 additions and 0 deletions

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<object> m_lst_item = new List<object>();
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
Point pt = m_descriptor.Control.PointToScreen(m_descriptor.RectangleR.Location);
pt.Y += m_descriptor.Control.ScrollOffset;
this.Location = pt;
if (m_descriptor.PropertyInfo.PropertyType.IsEnum) {
foreach (var v in Enum.GetValues(m_descriptor.PropertyInfo.PropertyType)) m_lst_item.Add(v);
} else if (m_descriptor.PropertyInfo.PropertyType == m_t_bool) {
m_lst_item.Add(true);
m_lst_item.Add(false);
} else {
this.Close();
return;
}
this.Height = m_lst_item.Count * m_nItemHeight;
Rectangle rect = Screen.GetWorkingArea(this);
if (this.Bottom > rect.Bottom) this.Top -= (this.Bottom - rect.Bottom);
this.MouseLeave += (s, ea) => this.Close();
this.LostFocus += (s, ea) => this.Close();
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
Rectangle rect_back = new Rectangle(0, 0, this.Width, m_nItemHeight);
Rectangle rect_font = new Rectangle(10, 0, this.Width - 13, m_nItemHeight);
int nIndex = 0;
string strVal = m_descriptor.GetStringFromValue();
foreach (var v in m_lst_item) {
m_brush.Color = nIndex++ % 2 == 0 ? m_clr_item_1 : m_clr_item_2;
g.FillRectangle(m_brush, rect_back);
if (v == m_item_hover) {
m_brush.Color = m_descriptor.Control.ItemHoverColor;
g.FillRectangle(m_brush, rect_back);
}
if (v.ToString() == strVal) {
m_brush.Color = m_descriptor.Control.ItemSelectedColor;
g.FillRectangle(m_brush, 4, rect_back.Top + 10, 5, 5);
}
m_brush.Color = m_descriptor.Control.ForeColor;
g.DrawString(v.ToString(), m_descriptor.Control.Font, m_brush, rect_font, m_sf);
rect_back.Y += m_nItemHeight;
rect_font.Y += m_nItemHeight;
}
g.DrawRectangle(m_pen, 0, 0, this.Width - 1, this.Height - 1);
}
protected override void OnMouseMove(MouseEventArgs e) {
base.OnMouseMove(e);
int nIndex = e.Y / m_nItemHeight;
if (nIndex < 0 || nIndex >= m_lst_item.Count) return;
var item = m_lst_item[e.Y / m_nItemHeight];
if (m_item_hover == item) return;
m_item_hover = item;
this.Invalidate();
}
protected override void OnMouseClick(MouseEventArgs e) {
base.OnMouseClick(e);
this.Close();
int nIndex = e.Y / m_nItemHeight;
if (nIndex < 0) return;
if (nIndex > m_lst_item.Count) return;
try {
m_descriptor.SetValue(m_lst_item[nIndex], null);
} catch (Exception ex) {
m_descriptor.OnSetValueError(ex);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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
{
/// <summary>
/// STNode node characteristics
/// Used to describe STNode developer information and some behaviors
/// </summary>
public class STNodeAttribute : Attribute
{
private string _Path;
/// <summary>
/// Get the path that the STNode node expects in the tree control
/// </summary>
public string Path {
get { return _Path; }
}
private string _Author;
/// <summary>
/// Get the author name of the STNode node
/// </summary>
public string Author {
get { return _Author; }
}
private string _Mail;
/// <summary>
/// Get the author mailbox of the STNode node
/// </summary>
public string Mail {
get { return _Mail; }
}
private string _Link;
/// <summary>
/// Get the author link of the STNode node
/// </summary>
public string Link {
get { return _Link; }
}
private string _Description;
/// <summary>
/// Get the description information of the STNode node
/// </summary>
public string Description {
get { return _Description; }
}
private static char[] m_ch_splitter = new char[] { '/', '\\' };
private static Regex m_reg = new Regex(@"^https?://", RegexOptions.IgnoreCase);
/// <summary>
/// Constructs an STNode property
/// </summary>
/// <param name="strPath">expected path</param>
public STNodeAttribute(string strPath) : this(strPath, null, null, null, null) { }
/// <summary>
/// Constructs an STNode property
/// </summary>
/// <param name="strPath">expected path</param>
/// <param name="strDescription">Description</param>
public STNodeAttribute(string strPath, string strDescription) : this(strPath, null, null, null, strDescription) { }
/// <summary>
/// Constructs an STNode property
/// </summary>
/// <param name="strPath">expected path</param>
/// <param name="strAuthor">STNode author name</param>
/// <param name="strMail">STNode author mailbox</param>
/// <param name="strLink">STNode author link</param>
/// <param name="strDescription">STNode node description information</param>
public STNodeAttribute(string strPath, string strAuthor, string strMail, string strLink, string strDescription) {
if (!string.IsNullOrEmpty(strPath))
strPath = strPath.Trim().Trim(m_ch_splitter).Trim();
this._Path = strPath;
this._Author = strAuthor;
this._Mail = strMail;
this._Description = strDescription;
if (string.IsNullOrEmpty(strLink) || strLink.Trim() == string.Empty) return;
strLink = strLink.Trim();
if (m_reg.IsMatch(strLink))
this._Link = strLink;
else
this._Link = "http://" + strLink;
}
private static Dictionary<Type, MethodInfo> m_dic = new Dictionary<Type, MethodInfo>();
/// <summary>
/// Get type helper function
/// </summary>
/// <param name="stNodeType">Node Type</param>
/// <returns>Function information</returns>
public static MethodInfo GetHelpMethod(Type stNodeType) {
if (m_dic.ContainsKey(stNodeType)) return m_dic[stNodeType];
var mi = stNodeType.GetMethod("ShowHelpInfo");
if (mi == null) return null;
if (!mi.IsStatic) return null;
var ps = mi.GetParameters ();
if (ps.Length != 1) return null;
if (ps[0].ParameterType != typeof(string)) return null;
m_dic.Add(stNodeType, mi);
return mi;
}
/// <summary>
/// Execute the helper function for the corresponding node type
/// </summary>
/// <param name="stNodeType">Node Type</param>
public static void ShowHelp(Type stNodeType) {
var mi = STNodeAttribute.GetHelpMethod (stNodeType);
if (mi == null) return;
mi.Invoke(null, new object[] { stNodeType.Module.FullyQualifiedName });
}
}
}

View File

@ -0,0 +1,253 @@
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("Owner cannot be null");
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("Add object cannot be null");
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("Add object cannot be null");
this.EnsureSpace(nodes.Length);
foreach (var n in nodes) {
if (n == null) throw new ArgumentNullException("Add object cannot be null");
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); //The coordinate system returns when there is no node
m_owner.MoveCanvas(10, 10, true, CanvasMoveArgs.All);
m_owner.Invalidate(); //If the canvas position and zoom are in the initial state, the above two lines of code will not cause the control to redraw
}
public bool Contains(STNode node) {
return this.IndexOf(node) != -1;
}
public int IndexOf(STNode node) {
return Array.IndexOf<STNode>(m_nodes, node);
}
public void Insert(int nIndex, STNode node) {
if (nIndex < 0 || nIndex >= this._Count)
throw new IndexOutOfRangeException("Index out of bounds");
if (node == null)
throw new ArgumentNullException("Insert object cannot be null");
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("Index out of bounds");
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) { //The coordinate system returns when there is no node
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("Index out of bounds");
return m_nodes[nIndex];
}
set { throw new InvalidOperationException("No reassignment of elements"); }
}
public void CopyTo(Array array, int index) {
if (array == null)
throw new ArgumentNullException("Array cannot be empty");
m_nodes.CopyTo(array, index);
}
public bool IsSynchronized {
get { return true; }
}
public object SyncRoot {
get { return this; }
}
public IEnumerator GetEnumerator() {
for (int i = 0, Len = this._Count; i < Len; i++)
yield return m_nodes[i];
}
/// <summary>
/// Check if there is enough space to expand the capacity
/// </summary>
/// <param name="elements">Number of elements to be added</param>
private void EnsureSpace(int elements) {
if (elements + this._Count > m_nodes.Length) {
STNode[] arrTemp = new STNode[Math.Max(m_nodes.Length * 2, elements + this._Count)];
m_nodes.CopyTo(arrTemp, 0);
m_nodes = arrTemp;
}
}
//============================================================================
int IList.Add(object value) {
return this.Add((STNode)value);
}
void IList.Clear() {
this.Clear();
}
bool IList.Contains(object value) {
return this.Contains((STNode)value);
}
int IList.IndexOf(object value) {
return this.IndexOf((STNode)value);
}
void IList.Insert(int index, object value) {
this.Insert(index, (STNode)value);
}
bool IList.IsFixedSize {
get { return this.IsFixedSize; }
}
bool IList.IsReadOnly {
get { return this.IsReadOnly; }
}
void IList.Remove(object value) {
this.Remove((STNode)value);
}
void IList.RemoveAt(int index) {
this.RemoveAt(index);
}
object IList.this[int index] {
get {
return this[index];
}
set {
this[index] = (STNode)value;
}
}
void ICollection.CopyTo(Array array, int index) {
this.CopyTo(array, index);
}
int ICollection.Count {
get { return this._Count; }
}
bool ICollection.IsSynchronized {
get { return this.IsSynchronized; }
}
object ICollection.SyncRoot {
get { return this.SyncRoot; }
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
public STNode[] ToArray() {
STNode[] nodes = new STNode[this._Count];
for (int i = 0; i < nodes.Length; i++)
nodes[i] = m_nodes[i];
return nodes;
}
}
}

View File

@ -0,0 +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.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("Value cannot be null");
_Font = value;
this.Invalidate();
}
}
private bool _Enabled = true;
public bool Enabled {
get { return _Enabled; }
set {
if (value == _Enabled) return;
_Enabled = value;
this.Invalidate();
}
}
private bool _Visable = true;
public bool Visable {
get { return _Visable; }
set {
if (value == _Visable) return;
_Visable = value;
this.Invalidate();
}
}
protected StringFormat m_sf;
public STNodeControl() {
m_sf = new StringFormat();
m_sf.Alignment = StringAlignment.Center;
m_sf.LineAlignment = StringAlignment.Center;
this._Font = new Font("courier new", 8.25f);
this.Width = 75;
this.Height = 23;
}
protected internal virtual void OnPaint(DrawingTools dt) {
Graphics g = dt.Graphics;
SolidBrush brush = dt.SolidBrush;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
brush.Color = this._BackColor;
g.FillRectangle(brush, 0, 0, this.Width, this.Height);
if (!string.IsNullOrEmpty(this._Text)) {
brush.Color = this._ForeColor;
g.DrawString(this._Text, this._Font, brush, this.ClientRectangle, m_sf);
}
if (this.Paint != null) this.Paint(this, new STNodeControlPaintEventArgs(dt));
}
public void Invalidate() {
if (this._Owner == null) return;
this._Owner.Invalidate(new Rectangle(this._Left, this._Top + this._Owner.TitleHeight, this.Width, this.Height));
}
public void Invalidate(Rectangle rect) {
if (this._Owner == null) return;
this._Owner.Invalidate(this.RectangleToParent(rect));
}
public Rectangle RectangleToParent(Rectangle rect) {
return new Rectangle(this._Left, this._Top + this._Owner.TitleHeight, this.Width, this.Height);
}
public event EventHandler GotFocus;
public event EventHandler LostFocus;
public event EventHandler MouseEnter;
public event EventHandler MouseLeave;
public event MouseEventHandler MouseDown;
public event MouseEventHandler MouseMove;
public event MouseEventHandler MouseUp;
public event MouseEventHandler MouseClick;
public event MouseEventHandler MouseWheel;
public event EventHandler MouseHWheel;
public event KeyEventHandler KeyDown;
public event KeyEventHandler KeyUp;
public event KeyPressEventHandler KeyPress;
public event EventHandler Move;
public event EventHandler Resize;
public event STNodeControlPaintEventHandler Paint;
protected internal virtual void OnGotFocus(EventArgs e) {
if (this.GotFocus != null) this.GotFocus(this, e);
}
protected internal virtual void OnLostFocus(EventArgs e) {
if (this.LostFocus != null) this.LostFocus(this, e);
}
protected internal virtual void OnMouseEnter(EventArgs e) {
if (this.MouseEnter != null) this.MouseEnter(this, e);
}
protected internal virtual void OnMouseLeave(EventArgs e) {
if (this.MouseLeave != null) this.MouseLeave(this, e);
}
protected internal virtual void OnMouseDown(MouseEventArgs e) {
if (this.MouseDown != null) this.MouseDown(this, e);
}
protected internal virtual void OnMouseMove(MouseEventArgs e) {
if (this.MouseMove != null) this.MouseMove(this, e);
}
protected internal virtual void OnMouseUp(MouseEventArgs e) {
if (this.MouseUp != null) this.MouseUp(this, e);
}
protected internal virtual void OnMouseClick(MouseEventArgs e) {
if (this.MouseClick != null) this.MouseClick(this, e);
}
protected internal virtual void OnMouseWheel(MouseEventArgs e) {
if (this.MouseWheel != null) this.MouseWheel(this, e);
}
protected internal virtual void OnMouseHWheel(MouseEventArgs e) {
if (this.MouseHWheel != null) this.MouseHWheel(this, e);
}
protected internal virtual void OnKeyDown(KeyEventArgs e) {
if (this.KeyDown != null) this.KeyDown(this, e);
}
protected internal virtual void OnKeyUp(KeyEventArgs e) {
if (this.KeyUp != null) this.KeyUp(this, e);
}
protected internal virtual void OnKeyPress(KeyPressEventArgs e) {
if (this.KeyPress != null) this.KeyPress(this, e);
}
protected internal virtual void OnMove(EventArgs e) {
if (this.Move != null) this.Move(this, e);
}
protected internal virtual void OnResize(EventArgs e) {
if (this.Resize != null) this.Resize(this, e);
}
public IAsyncResult BeginInvoke(Delegate method) { return this.BeginInvoke(method, null); }
public IAsyncResult BeginInvoke(Delegate method, params object[] args) {
if (this._Owner == null) return null;
return this._Owner.BeginInvoke(method, args);
}
public object Invoke(Delegate method) { return this.Invoke(method, null); }
public object Invoke(Delegate method, params object[] args) {
if (this._Owner == null) return null;
return this._Owner.Invoke(method, args);
}
}
public delegate void STNodeControlPaintEventHandler(object sender, STNodeControlPaintEventArgs e);
public class STNodeControlPaintEventArgs : EventArgs
{
/// <summary>
/// Drawing tools
/// </summary>
public DrawingTools DrawingTools { get; private set; }
public STNodeControlPaintEventArgs(DrawingTools dt) {
this.DrawingTools = dt;
}
}
}

View File

@ -0,0 +1,214 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace ST.Library.UI.NodeEditor
{
public class STNodeControlCollection: IList, ICollection, IEnumerable
{
/*
* To ensure security, only inheritors can access the collection in 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("Owner cannot be null");
m_owner = owner;
m_controls = new STNodeControl[4];
}
public int Add(STNodeControl control) {
if (control == null) throw new ArgumentNullException("Add object cannot be null");
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("Add object cannot be null");
this.EnsureSpace(controls.Length);
foreach (var op in controls) {
if (op == null) throw new ArgumentNullException("Add object cannot be null");
if (-1 == this.IndexOf(op)) {
op.Owner = m_owner;
m_controls[this._Count++] = op;
}
}
this.Redraw();
}
public void Clear() {
for (int i = 0; i < this._Count; i++) m_controls[i].Owner = null;
this._Count = 0;
m_controls = new STNodeControl[4];
this.Redraw();
}
public bool Contains(STNodeControl option) {
return this.IndexOf(option) != -1;
}
public int IndexOf(STNodeControl option) {
return Array.IndexOf<STNodeControl>(m_controls, option);
}
public void Insert(int index, STNodeControl control) {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("Index out of bounds");
if (control == null)
throw new ArgumentNullException("Insert object cannot be null");
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("Index out of bounds");
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("Index out of bounds");
return m_controls[index];
}
set { throw new InvalidOperationException("No reassignment of elements"); }
}
public void CopyTo(Array array, int index) {
if (array == null)
throw new ArgumentNullException("Array cannot be empty");
m_controls.CopyTo(array, index);
}
public bool IsSynchronized {
get { return true; }
}
public object SyncRoot {
get { return this; }
}
public IEnumerator GetEnumerator() {
for (int i = 0, Len = this._Count; i < Len; i++)
yield return m_controls[i];
}
/// <summary>
/// Check if there is enough space to expand the capacity
/// </summary>
/// <param name="elements">Number of elements to be added</param>
private void EnsureSpace(int elements) {
if (elements + this._Count > m_controls.Length) {
STNodeControl[] arrTemp = new STNodeControl[Math.Max(m_controls.Length * 2, elements + this._Count)];
m_controls.CopyTo(arrTemp, 0);
m_controls = arrTemp;
}
}
protected void Redraw() {
if (m_owner != null && m_owner.Owner != null) {
//m_owner.BuildSize();
m_owner.Owner.Invalidate(m_owner.Owner.CanvasToControl(m_owner.Rectangle));
}
}
//===================================================================================
int IList.Add(object value) {
return this.Add((STNodeControl)value);
}
void IList.Clear() {
this.Clear();
}
bool IList.Contains(object value) {
return this.Contains((STNodeControl)value);
}
int IList.IndexOf(object value) {
return this.IndexOf((STNodeControl)value);
}
void IList.Insert(int index, object value) {
this.Insert(index, (STNodeControl)value);
}
bool IList.IsFixedSize {
get { return this.IsFixedSize; }
}
bool IList.IsReadOnly {
get { return this.IsReadOnly; }
}
void IList.Remove(object value) {
this.Remove((STNodeControl)value);
}
void IList.RemoveAt(int index) {
this.RemoveAt(index);
}
object IList.this[int index] {
get {
return this[index];
}
set {
this[index] = (STNodeControl)value;
}
}
void ICollection.CopyTo(Array array, int index) {
this.CopyTo(array, index);
}
int ICollection.Count {
get { return this._Count; }
}
bool ICollection.IsSynchronized {
get { return this.IsSynchronized; }
}
object ICollection.SyncRoot {
get { return this.SyncRoot; }
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Drawing;
namespace ST.Library.UI.NodeEditor
{
public enum ConnectionStatus
{
/// <summary>
/// No owner exists
/// </summary>
[Description("No owner exists")]
NoOwner,
/// <summary>
/// same owner
/// </summary>
[Description("same owner")]
SameOwner,
/// <summary>
/// Both are input or output options
/// </summary>
[Description("both input or output options")]
SameInputOrOutput,
/// <summary>
/// Different data types
/// </summary>
[Description("Different data types")]
ErrorType,
/// <summary>
/// Single connection node
/// </summary>
[Description("Single connection node")]
SingleOption,
/// <summary>
/// A circular path appears
/// </summary>
[Description("A circular path appears")]
Loop,
/// <summary>
/// Existing connection
/// </summary>
[Description("existing connection")]
Exists,
/// <summary>
/// blank options
/// </summary>
[Description("Blank option")]
EmptyOption,
/// <summary>
/// already connected
/// </summary>
[Description("Connected")]
Connected,
/// <summary>
/// The connection is disconnected
/// </summary>
[Description("The connection was disconnected")]
DisConnected,
/// <summary>
/// Node is locked
/// </summary>
[Description("Node is locked")]
Locked,
/// <summary>
/// Operation rejected
/// </summary>
[Description("Operation denied")]
Reject,
/// <summary>
/// is being connected
/// </summary>
[Description("being connected")]
Connecting,
/// <summary>
/// Disconnecting
/// </summary>
[Description("Disconnecting")]
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 //View the parameters needed when moving the canvas ->MoveCanvas()
{
Left = 1, //indicates that only the X coordinate is moved
Top = 2, //Indicates that only the Y coordinate is moved
All = 4 //Indicates that XY move at the same time
}
public struct NodeFindInfo
{
public STNode Node;
public STNodeOption NodeOption;
public string Mark;
public string[] MarkLines;
}
public struct ConnectionInfo
{
public STNodeOption Input;
public STNodeOption Output;
}
public delegate void STNodeOptionEventHandler(object sender, STNodeOptionEventArgs e);
public class STNodeOptionEventArgs : EventArgs
{
private STNodeOption _TargetOption;
/// <summary>
/// The corresponding Option that triggers this event
/// </summary>
public STNodeOption TargetOption {
get { return _TargetOption; }
}
private ConnectionStatus _Status;
/// <summary>
/// Connection status between Option
/// </summary>
public ConnectionStatus Status {
get { return _Status; }
internal set { _Status = value; }
}
private bool _IsSponsor;
/// <summary>
/// Whether it is the initiator of this behavior
/// </summary>
public bool IsSponsor {
get { return _IsSponsor; }
}
public STNodeOptionEventArgs(bool isSponsor, STNodeOption opTarget, ConnectionStatus cr) {
this._IsSponsor = isSponsor;
this._TargetOption = opTarget;
this._Status = cr;
}
}
public delegate void STNodeEditorEventHandler(object sender, STNodeEditorEventArgs e);
public delegate void STNodeEditorOptionEventHandler(object sender, STNodeEditorOptionEventArgs e);
public class STNodeEditorEventArgs : EventArgs
{
private STNode _Node;
public STNode Node {
get { return _Node; }
}
public STNodeEditorEventArgs(STNode node) {
this._Node = node;
}
}
public class STNodeEditorOptionEventArgs : STNodeOptionEventArgs
{
private STNodeOption _CurrentOption;
/// <summary>
/// Option to actively trigger events
/// </summary>
public STNodeOption CurrentOption {
get { return _CurrentOption; }
}
private bool _Continue = true;
/// <summary>
/// Whether to continue the downward operation for Begin(Connecting/DisConnecting) whether to continue the backward operation
/// </summary>
public bool Continue {
get { return _Continue; }
set { _Continue = value; }
}
public STNodeEditorOptionEventArgs(STNodeOption opTarget, STNodeOption opCurrent, ConnectionStatus cr)
: base(false, opTarget, cr) {
this._CurrentOption = opCurrent;
}
}
}

View File

@ -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;
/// <summary>
/// Gets or sets whether it is the left layout
/// </summary>
[Description("Get or set whether it is the left layout"), DefaultValue(true)]
public bool LeftLayout {
get { return _LeftLayout; }
set {
if (value == _LeftLayout) return;
_LeftLayout = value;
this.SetLocation();
this.Invalidate();
}
}
private Color _SplitLineColor = Color.Black;
/// <summary>
/// Gets or this is the color of the dividing line
/// </summary>
[Description("Get or set the color of the dividing line"), DefaultValue(typeof(Color), "Black")]
public Color SplitLineColor {
get { return _SplitLineColor; }
set { _SplitLineColor = value; }
}
private Color _HandleLineColor = Color.Gray;
/// <summary>
/// Gets or sets the color of the dividing line handle
/// </summary>
[Description("Get or set the color of the dividing line handle"), DefaultValue(typeof(Color), "Gray")]
public Color HandleLineColor {
get { return _HandleLineColor; }
set { _HandleLineColor = value; }
}
private bool _ShowScale = true;
/// <summary>
/// Get or set the display scale when the editor is zoomed
/// </summary>
[Description("Get or set the display scale when the editor is zoomed"), DefaultValue(true)]
public bool ShowScale {
get { return _ShowScale; }
set { _ShowScale = value; }
}
private bool _ShowConnectionStatus = true;
/// <summary>
/// Get or set whether to display the status when the node is connected
/// </summary>
[Description("Get or set whether to display the status when the node is connected"), DefaultValue(true)]
public bool ShowConnectionStatus {
get { return _ShowConnectionStatus; }
set { _ShowConnectionStatus = value; }
}
private int _X;
/// <summary>
/// Gets or sets the horizontal width of the dividing line
/// </summary>
[Description("Get or set the horizontal width of the dividing line"), DefaultValue(201)]
public int X {
get { return _X; }
set {
if (value < 122) value = 122;
else if (value > this.Width - 122) value = this.Width - 122;
if (_X == value) return;
_X = value;
this.SetLocation();
}
}
private int _Y;
/// <summary>
/// Gets or sets the vertical height of the dividing line
/// </summary>
[Description("Get or set the vertical height of the dividing line")]
public int Y {
get { return _Y; }
set {
if (value < 122) value = 122;
else if (value > this.Height - 122) value = this.Height - 122;
if (_Y == value) return;
_Y = value;
this.SetLocation();
}
}
/// <summary>
/// Get the STNodeEditor in the panel
/// </summary>
[Description("Get the STNodeEditor in the panel"), Browsable(false)]
public STNodeEditor Editor {
get { return m_editor; }
}
/// <summary>
/// Get the STNodeTreeView in the panel
/// </summary>
[Description("Get the STNodeTreeView in the panel"), Browsable(false)]
public STNodeTreeView TreeView {
get { return m_tree; }
}
/// <summary>
/// Get the STNodePropertyGrid in the panel
/// </summary>
[Description("Get the STNodePropertyGrid in the panel"), Browsable(false)]
public STNodePropertyGrid PropertyGrid {
get { return m_grid; }
}
private Point m_pt_down;
private bool m_is_mx;
private bool m_is_my;
private Pen m_pen;
private bool m_nInited;
private Dictionary<ConnectionStatus, string> m_dic_status_key = new Dictionary<ConnectionStatus, string>();
private STNodeEditor m_editor;
private STNodeTreeView m_tree;
private STNodePropertyGrid m_grid;
public override Size MinimumSize {
get {
return base.MinimumSize;
}
set {
value = new Size(250, 250);
base.MinimumSize = value;
}
}
[DllImport("user32.dll")]
private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int w, int h, bool bRedraw);
public STNodeEditorPannel() {
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
m_editor = new STNodeEditor();
m_tree = new STNodeTreeView();
m_grid = new STNodePropertyGrid();
m_grid.Text = "NodeProperty";
this.Controls.Add(m_editor);
this.Controls.Add(m_tree);
this.Controls.Add(m_grid);
this.Size = new Size(500, 500);
this.MinimumSize = new Size(250, 250);
this.BackColor = Color.FromArgb(255, 34, 34, 34);
m_pen = new Pen(this.BackColor, 3);
Type t = typeof(ConnectionStatus);
var vv = Enum.GetValues(t);
var vvv = vv.GetValue(0);
foreach (var f in t.GetFields()) {
if (!f.FieldType.IsEnum) continue;
foreach (var a in f.GetCustomAttributes(true)) {
if (!(a is DescriptionAttribute)) continue;
m_dic_status_key.Add((ConnectionStatus)f.GetValue(f), ((DescriptionAttribute)a).Description);
}
}
m_editor.ActiveChanged += (s, e) => m_grid.SetNode(m_editor.ActiveNode);
m_editor.CanvasScaled += (s, e) => {
if (this._ShowScale)
m_editor.ShowAlert(m_editor.CanvasScale.ToString("F2"), Color.White, Color.FromArgb(127, 255, 255, 0));
};
m_editor.OptionConnected += (s, e) => {
if (this._ShowConnectionStatus)
m_editor.ShowAlert(m_dic_status_key[e.Status], Color.White, e.Status == ConnectionStatus.Connected ? Color.FromArgb(125, Color.Lime) : Color.FromArgb(125, Color.Red));
};
}
protected override void OnResize(EventArgs e) {
base.OnResize(e);
if (!m_nInited) {
this._Y = this.Height / 2;
if (this._LeftLayout)
this._X = 201;
else
this._X = this.Width - 202;
m_nInited = true;
this.SetLocation();
return;
}
this.SetLocation();
}
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
if (width < 250) width = 250;
if (height < 250) height = 250;
base.SetBoundsCore(x, y, width, height, specified);
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
m_pen.Width = 3;
m_pen.Color = this._SplitLineColor;
g.DrawLine(m_pen, this._X, 0, this._X, this.Height);
int nX = 0;
if (this._LeftLayout) {
g.DrawLine(m_pen, 0, this._Y, this._X - 1, this._Y);
nX = this._X / 2;
} else {
g.DrawLine(m_pen, this._X + 2, this._Y, this.Width, this._Y);
nX = this._X + (this.Width - this._X) / 2;
}
m_pen.Width = 1;
this._HandleLineColor = Color.Gray;
m_pen.Color = this._HandleLineColor;
g.DrawLine(m_pen, this._X, this._Y - 10, this._X, this._Y + 10);
g.DrawLine(m_pen, nX - 10, this._Y, nX + 10, this._Y);
}
private void SetLocation() {
if (this._LeftLayout) {
//m_tree.Location = Point.Empty;
//m_tree.Size = new Size(m_sx - 1, m_sy - 1);
STNodeEditorPannel.MoveWindow(m_tree.Handle, 0, 0, this._X - 1, this._Y - 1, false);
//m_grid.Location = new Point (0, m_sy + 2);
//m_grid.Size = new Size(m_sx - 1, this.Height - m_sy - 2);
STNodeEditorPannel.MoveWindow(m_grid.Handle, 0, this._Y + 2, this._X - 1, this.Height - this._Y - 2, false);
//m_editor.Location = new Point(m_sx + 2, 0);
//m_editor.Size = new Size(this.Width - m_sx - 2, this.Height);
STNodeEditorPannel.MoveWindow(m_editor.Handle, this._X + 2, 0, this.Width - this._X - 2, this.Height, false);
} else {
STNodeEditorPannel.MoveWindow(m_editor.Handle, 0, 0, this._X - 1, this.Height, false);
STNodeEditorPannel.MoveWindow(m_tree.Handle, this._X + 2, 0, this.Width - this._X - 2, this._Y - 1, false);
STNodeEditorPannel.MoveWindow(m_grid.Handle, this._X + 2, this._Y + 2, this.Width - this._X - 2, this.Height - this._Y - 2, false);
}
}
protected override void OnMouseDown(MouseEventArgs e) {
base.OnMouseDown(e);
m_pt_down = e.Location;
m_is_mx = m_is_my = false;
if (this.Cursor == Cursors.VSplit) {
m_is_mx = true;
} else if (this.Cursor == Cursors.HSplit) {
m_is_my = true;
}
}
protected override void OnMouseMove(MouseEventArgs e) {
base.OnMouseMove(e);
if (e.Button == MouseButtons.Left) {
int nw = 122;// (int)(this.Width * 0.1f);
int nh = 122;// (int)(this.Height * 0.1f);
if (m_is_mx) {
this._X = e.X;// -m_pt_down.X;
if (this._X < nw) this._X = nw;
else if (_X + nw > this.Width) this._X = this.Width - nw;
} else if (m_is_my) {
this._Y = e.Y;
if (this._Y < nh) this._Y = nh;
else if (this._Y + nh > this.Height) this._Y = this.Height - nh;
}
//m_rx = this.Width - m_sx;// (float)m_sx / this.Width;
//m_fh = (float)m_sy / this.Height;
this.SetLocation();
this.Invalidate();
return;
}
if (Math.Abs(e.X - this._X) < 2)
this.Cursor = Cursors.VSplit;
else if (Math.Abs(e.Y - this._Y) < 2)
this.Cursor = Cursors.HSplit;
else this.Cursor = Cursors.Arrow;
}
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave(e);
m_is_mx = m_is_my = false;
this.Cursor = Cursors.Arrow;
}
/// <summary>
/// Add a STNode to the tree control
/// </summary>
/// <param name="stNodeType">STNode type</param>
/// <returns>Whether the addition is successful</returns>
public bool AddSTNode(Type stNodeType) {
return m_tree.AddNode(stNodeType);
}
/// <summary>
/// Load STNode from assembly
/// </summary>
/// <param name="strFileName">Assembly path</param>
/// <returns>Add success number</returns>
public int LoadAssembly(string strFileName) {
m_editor.LoadAssembly(strFileName);
return m_tree.LoadAssembly(strFileName);
}
/// <summary>
/// Sets the text for the editor to display the connection status
/// </summary>
/// <param name="status">Connection status</param>
/// <param name="strText">Corresponding display text</param>
/// <returns>Old text</returns>
public string SetConnectionStatusText(ConnectionStatus status, string strText) {
string strOld = null;
if (m_dic_status_key.ContainsKey(status)) {
strOld = m_dic_status_key[status];
m_dic_status_key[status] = strText;
return strOld;
}
m_dic_status_key.Add(status, strText);
return strText;
}
}
}

View File

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

View File

@ -0,0 +1,456 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace ST.Library.UI.NodeEditor
{
public class STNodeOption
{
#region Properties
public static readonly STNodeOption Empty = new STNodeOption();
private STNode _Owner;
/// <summary>
/// Get the Node to which the current Option belongs
/// </summary>
public STNode Owner {
get { return _Owner; }
internal set {
if (value == _Owner) return;
if (_Owner != null) this.DisConnectionAll(); //Disconnect all current connections when the owner changes
_Owner = value;
}
}
private bool _IsSingle;
/// <summary>
/// Get whether the current Option can only be connected once
/// </summary>
public bool IsSingle {
get { return _IsSingle; }
}
private bool _IsInput;
/// <summary>
/// Get whether the current Option is an input option
/// </summary>
public bool IsInput {
get { return _IsInput; }
internal set { _IsInput = value; }
}
private Color _TextColor = Color.White;
/// <summary>
/// Gets or sets the current Option text color
/// </summary>
public Color TextColor {
get { return _TextColor; }
internal set {
if (value == _TextColor) return;
_TextColor = value;
this.Invalidate();
}
}
private Color _DotColor = Color.Transparent;
/// <summary>
/// Gets or sets the color of the current Option connection point
/// </summary>
public Color DotColor {
get { return _DotColor; }
internal set {
if (value == _DotColor) return;
_DotColor = value;
this.Invalidate();
}
}
private string _Text;
/// <summary>
/// Gets or sets the current Option display text
/// This property cannot be modified when AutoSize is set
/// </summary>
public string Text {
get { return _Text; }
internal set {
if (value == _Text) return;
_Text = value;
if (this._Owner == null) return;
this._Owner.BuildSize(true, true, true);
}
}
private int _DotLeft;
/// <summary>
/// Get the left coordinate of the current Option connection point
/// </summary>
public int DotLeft {
get { return _DotLeft; }
internal set { _DotLeft = value; }
}
private int _DotTop;
/// <summary>
/// Get the upper coordinate of the current Option connection point
/// </summary>
public int DotTop {
get { return _DotTop; }
internal set { _DotTop = value; }
}
private int _DotSize;
/// <summary>
/// Get the width of the current Option connection point
/// </summary>
public int DotSize {
get { return _DotSize; }
protected set { _DotSize = value; }
}
private Rectangle _TextRectangle;
/// <summary>
/// Get the current Option text area
/// </summary>
public Rectangle TextRectangle {
get { return _TextRectangle; }
internal set { _TextRectangle = value; }
}
private object _Data;
/// <summary>
/// Get or set the data contained in the current Option
/// </summary>
public object Data {
get { return _Data; }
set {
if (value != null) {
if (this._DataType == null) return;
var t = value.GetType();
if (t != this._DataType && !t.IsSubclassOf(this._DataType)) {
throw new ArgumentException("Invalid data type The data type must be the specified data type or its subclass");
}
}
_Data = value;
}
}
private Type _DataType;
/// <summary>
/// Get the current Option data type
/// </summary>
public Type DataType {
get { return _DataType; }
internal set { _DataType = value; }
}
//private Rectangle _DotRectangle;
/// <summary>
/// Get the area of the current Option connection point
/// </summary>
public Rectangle DotRectangle {
get {
return new Rectangle(this._DotLeft, this._DotTop, this._DotSize, this._DotSize);
}
}
/// <summary>
/// Get the current number of Option connected
/// </summary>
public int ConnectionCount {
get { return m_hs_connected.Count; }
}
/// <summary>
/// Get the Option collection that the current Option is connected to
/// </summary>
internal HashSet<STNodeOption> ConnectedOption {
get { return m_hs_connected; }
}
#endregion Properties
/// <summary>
/// Save the points that have been connected
/// </summary>
protected HashSet<STNodeOption> m_hs_connected;
#region Constructor
private STNodeOption() { }
/// <summary>
/// Constructs an Option
/// </summary>
/// <param name="strText">Display text</param>
/// <param name="dataType">Data Type</param>
/// <param name="bSingle">Whether it is a single connection</param>
public STNodeOption(string strText, Type dataType, bool bSingle) {
if (dataType == null) throw new ArgumentNullException("The specified data type cannot be null");
this._DotSize = 10;
m_hs_connected = new HashSet<STNodeOption>();
this._DataType = dataType;
this._Text = strText;
this._IsSingle = bSingle;
}
#endregion Builder
#region Event
/// <summary>
/// Occurs when connected
/// </summary>
public event STNodeOptionEventHandler Connected;
/// <summary>
/// Occurs when a connection starts happening
/// </summary>
public event STNodeOptionEventHandler Connecting;
/// <summary>
/// Occurs when the connection is disconnected
/// </summary>
public event STNodeOptionEventHandler DisConnected;
/// <summary>
/// Occurs when the connection starts to drop
/// </summary>
public event STNodeOptionEventHandler DisConnecting;
/// <summary>
/// Occurs when data is passed
/// </summary>
public event STNodeOptionEventHandler DataTransfer;
#endregion Event
#region protected
/// <summary>
/// Redraw the entire control
/// </summary>
protected void Invalidate() {
if (this._Owner == null) return;
this._Owner.Invalidate();
}
/*
* At first I thought that only input type options should have events because input is passive and output is active
* But later found that events are used for output nodes in STNodeHub, for example
* Just in case, it is not very problematic to comment the code here. The output option does not register the event, and the same effect
*/
protected internal virtual void OnConnected(STNodeOptionEventArgs e) {
if (this.Connected != null/* && this._IsInput*/) this.Connected(this, e);
}
protected internal virtual void OnConnecting(STNodeOptionEventArgs e) {
if (this.Connecting != null) this.Connecting(this, e);
}
protected internal virtual void OnDisConnected(STNodeOptionEventArgs e) {
if (this.DisConnected != null/* && this._IsInput*/) this.DisConnected(this, e);
}
protected internal virtual void OnDisConnecting(STNodeOptionEventArgs e) {
if (this.DisConnecting != null) this.DisConnecting(this, e);
}
protected internal virtual void OnDataTransfer(STNodeOptionEventArgs e) {
if (this.DataTransfer != null/* && this._IsInput*/) this.DataTransfer(this, e);
}
protected void STNodeEidtorConnected(STNodeEditorOptionEventArgs e) {
if (this._Owner == null) return;
if (this._Owner.Owner == null) return;
this._Owner.Owner.OnOptionConnected(e);
}
protected void STNodeEidtorDisConnected(STNodeEditorOptionEventArgs e) {
if (this._Owner == null) return;
if (this._Owner.Owner == null) return;
this._Owner.Owner.OnOptionDisConnected(e);
}
/// <summary>
/// The current Option starts to connect to the target Option
/// </summary>
/// <param name="op">Option to connect</param>
/// <returns>Are you allowed to continue the operation?</returns>
protected virtual bool ConnectingOption(STNodeOption op) {
if (this._Owner == null) return false;
if (this._Owner.Owner == null) return false;
STNodeEditorOptionEventArgs e = new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Connecting);
this._Owner.Owner.OnOptionConnecting(e);
this.OnConnecting(new STNodeOptionEventArgs(true, op, ConnectionStatus.Connecting));
op.OnConnecting(new STNodeOptionEventArgs(false, this, ConnectionStatus.Connecting));
return e.Continue;
}
/// <summary>
/// The current Option starts to disconnect the target Option
/// </summary>
/// <param name="op">Option to be disconnected</param>
/// <returns>Are you allowed to continue the operation?</returns>
protected virtual bool DisConnectingOption(STNodeOption op) {
if (this._Owner == null) return false;
if (this._Owner.Owner == null) return false;
STNodeEditorOptionEventArgs e = new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.DisConnecting);
this._Owner.Owner.OnOptionDisConnecting(e);
this.OnDisConnecting(new STNodeOptionEventArgs(true, op, ConnectionStatus.DisConnecting));
op.OnDisConnecting(new STNodeOptionEventArgs(false, this, ConnectionStatus.DisConnecting));
return e.Continue;
}
#endregion protected
#region public
/// <summary>
/// The current Option is connected to the target Option
/// </summary>
/// <param name="op">Option to connect</param>
/// <returns>Connection result</returns>
public virtual ConnectionStatus ConnectOption(STNodeOption op) {
if (!this.ConnectingOption(op)) {
this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Reject));
return ConnectionStatus.Reject;
}
var v = this.CanConnect(op);
if (v != ConnectionStatus.Connected) {
this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v));
return v;
}
v = op.CanConnect(this);
if (v != ConnectionStatus.Connected) {
this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v));
return v;
}
op.AddConnection(this, false);
this.AddConnection(op, true);
this.ControlBuildLinePath();
this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v));
return v;
}
/// <summary>
/// Check whether the current Option can connect to the target Option
/// </summary>
/// <param name="op">Option to connect</param>
/// <returns>Test results</returns>
public virtual ConnectionStatus CanConnect(STNodeOption op) {
if (this == STNodeOption.Empty || op == STNodeOption.Empty) return ConnectionStatus.EmptyOption;
if (this._IsInput == op.IsInput) return ConnectionStatus.SameInputOrOutput;
if (op.Owner == null || this._Owner == null) return ConnectionStatus.NoOwner;
if (op.Owner == this._Owner) return ConnectionStatus.SameOwner;
if (this._Owner.LockOption || op._Owner.LockOption) return ConnectionStatus.Locked;
if (this._IsSingle && m_hs_connected.Count == 1) return ConnectionStatus.SingleOption;
if (op.IsInput && STNodeEditor.CanFindNodePath(op.Owner, this._Owner)) return ConnectionStatus.Loop;
if (m_hs_connected.Contains(op)) return ConnectionStatus.Exists;
if (this._IsInput && op._DataType != this._DataType && !op._DataType.IsSubclassOf(this._DataType)) return ConnectionStatus.ErrorType;
return ConnectionStatus.Connected;
}
/// <summary>
/// The current Option disconnects the target Option
/// </summary>
/// <param name="op">Option to be disconnected</param>
/// <returns></returns>
public virtual ConnectionStatus DisConnectOption(STNodeOption op) {
if (!this.DisConnectingOption(op)) {
this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Reject));
return ConnectionStatus.Reject;
}
if (op.Owner == null) return ConnectionStatus.NoOwner;
if (this._Owner == null) return ConnectionStatus.NoOwner;
if (op.Owner.LockOption && this._Owner.LockOption) {
this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Locked));
return ConnectionStatus.Locked;
}
op.RemoveConnection(this, false);
this.RemoveConnection(op, true);
this.ControlBuildLinePath();
this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.DisConnected));
return ConnectionStatus.DisConnected;
}
/// <summary>
/// Disconnect all connections of the current Option
/// </summary>
public void DisConnectionAll() {
if (this._DataType == null) return;
var arr = m_hs_connected.ToArray();
foreach (var v in arr) {
this.DisConnectOption(v);
}
}
/// <summary>
/// Get the Option collection that the current Option is connected to
/// </summary>
/// <returns>If it is null, it means that there is no owner, otherwise it returns the collection</returns>
public List<STNodeOption> GetConnectedOption() {
if (this._DataType == null) return null;
if (!this._IsInput)
return m_hs_connected.ToList();
List<STNodeOption> lst = new List<STNodeOption>();
if (this._Owner == null) return null;
if (this._Owner.Owner == null) return null;
foreach (var v in this._Owner.Owner.GetConnectionInfo()) {
if (v.Output == this) lst.Add(v.Input);
}
return lst;
}
/// <summary>
/// Post data to all Option connected to the current Option
/// </summary>
public void TransferData() {
if (this._DataType == null) return;
foreach (var v in m_hs_connected) {
v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected));
}
}
/// <summary>
/// Post data to all Option connected to the current Option
/// </summary>
/// <param name="data">Data to be delivered</param>
public void TransferData(object data) {
if (this._DataType == null) return;
this.Data = data; //not this._Data
foreach (var v in m_hs_connected) {
v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected));
}
}
/// <summary>
/// Post data to all Option connected to the current Option
/// </summary>
/// <param name="data">Data to be delivered</param>
/// <param name="bDisposeOld">Whether to release old data</param>
public void TransferData(object data, bool bDisposeOld) {
if (bDisposeOld && this._Data != null) {
if (this._Data is IDisposable) ((IDisposable)this._Data).Dispose();
this._Data = null;
}
this.TransferData(data);
}
#endregion public
#region internal
private bool AddConnection(STNodeOption op, bool bSponsor) {
if (this._DataType == null) return false;
bool b = m_hs_connected.Add(op);
this.OnConnected(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected));
if (this._IsInput) this.OnDataTransfer(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected));
return b;
}
private bool RemoveConnection(STNodeOption op, bool bSponsor) {
if (this._DataType == null) return false;
bool b = false;
if (m_hs_connected.Contains(op)) {
b = m_hs_connected.Remove(op);
if (this._IsInput) this.OnDataTransfer(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.DisConnected));
this.OnDisConnected(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected));
}
return b;
}
#endregion internal
#region private
private void ControlBuildLinePath() {
if (this.Owner == null) return;
if (this.Owner.Owner == null) return;
this.Owner.Owner.BuildLinePath();
}
#endregion
}
}

View File

@ -0,0 +1,238 @@
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
{
/*
* Although the collection provides a complete data interface such as: Add,Remove,...
* But try not to use some removal operations such as: Remove,RemoveAt,Clear,this[index] = value,...
* Because the Owner of each Option is strictly bound in my definition, some operations such as removal or replacement will affect the change of the Owner
* So all the original connections will be disconnected and the DisConnect event will be triggered
* To ensure security, only inheritors can access the collection in STNode
*/
private int _Count;
public int Count { get { return _Count; } }
private STNodeOption[] m_options;
private STNode m_owner;
private bool m_isInput; //Whether the current collection stores the input point
internal STNodeOptionCollection(STNode owner, bool isInput) {
if (owner == null) throw new ArgumentNullException("Owner cannot be null");
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("Add object cannot be null");
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("Add object cannot be null");
this.EnsureSpace(options.Length);
foreach (var op in options) {
if (op == null) throw new ArgumentNullException("Add object cannot be null");
if (-1 == this.IndexOf(op)) {
op.Owner = m_owner;
op.IsInput = m_isInput;
m_options[this._Count++] = op;
}
}
this.Invalidate();
}
public void Clear() {
for (int i = 0; i < this._Count; i++) m_options[i].Owner = null;
this._Count = 0;
m_options = new STNodeOption[4];
this.Invalidate();
}
public bool Contains(STNodeOption option) {
return this.IndexOf(option) != -1;
}
public int IndexOf(STNodeOption option) {
return Array.IndexOf<STNodeOption>(m_options, option);
}
public void Insert(int index, STNodeOption option) {
if (index < 0 || index >= this._Count)
throw new IndexOutOfRangeException("Index out of bounds");
if (option == null)
throw new ArgumentNullException("Insert object cannot be null");
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("Index out of bounds");
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("Index out of bounds");
return m_options[index];
}
set { throw new InvalidOperationException("No reassignment of elements"); }
}
public void CopyTo(Array array, int index) {
if (array == null)
throw new ArgumentNullException("Array cannot be empty");
m_options.CopyTo(array, index);
}
public bool IsSynchronized {
get { return true; }
}
public object SyncRoot {
get { return this; }
}
public IEnumerator GetEnumerator() {
for (int i = 0, Len = this._Count; i < Len; i++)
yield return m_options[i];
}
/// <summary>
/// Check if there is enough space to expand the capacity
/// </summary>
/// <param name="elements">Number of elements to be added</param>
private void EnsureSpace(int elements) {
if (elements + this._Count > m_options.Length) {
STNodeOption[] arrTemp = new STNodeOption[Math.Max(m_options.Length * 2, elements + this._Count)];
m_options.CopyTo(arrTemp, 0);
m_options = arrTemp;
}
}
protected void Invalidate() {
if (m_owner != null && m_owner.Owner != null) {
m_owner.BuildSize(true, true, true);
//m_owner.Invalidate();//.Owner.Invalidate();
}
}
//===================================================================================
int IList.Add(object value) {
return this.Add((STNodeOption)value);
}
void IList.Clear() {
this.Clear();
}
bool IList.Contains(object value) {
return this.Contains((STNodeOption)value);
}
int IList.IndexOf(object value) {
return this.IndexOf((STNodeOption)value);
}
void IList.Insert(int index, object value) {
this.Insert(index, (STNodeOption)value);
}
bool IList.IsFixedSize {
get { return this.IsFixedSize; }
}
bool IList.IsReadOnly {
get { return this.IsReadOnly; }
}
void IList.Remove(object value) {
this.Remove((STNodeOption)value);
}
void IList.RemoveAt(int index) {
this.RemoveAt(index);
}
object IList.this[int index] {
get {
return this[index];
}
set {
this[index] = (STNodeOption)value;
}
}
void ICollection.CopyTo(Array array, int index) {
this.CopyTo(array, index);
}
int ICollection.Count {
get { return this._Count; }
}
bool ICollection.IsSynchronized {
get { return this.IsSynchronized; }
}
object ICollection.SyncRoot {
get { return this.SyncRoot; }
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
public STNodeOption[] ToArray() {
STNodeOption[] ops = new STNodeOption[this._Count];
for (int i = 0; i < ops.Length; i++)
ops[i] = m_options[i];
return ops;
}
}
}

View File

@ -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
{
/// <summary>
/// STNode node attribute characteristics
/// Used to describe STNode node attribute information and behavior on the attribute editor
/// </summary>
public class STNodePropertyAttribute : Attribute
{
private string _Name;
/// <summary>
/// Get the name of the property that needs to be displayed on the property editor
/// </summary>
public string Name {
get { return _Name; }
}
private string _Description;
/// <summary>
/// Get the description of the property that needs to be displayed on the property editor
/// </summary>
public string Description {
get { return _Description; }
}
private Type _ConverterType = typeof(STNodePropertyDescriptor);
/// <summary>
/// Get the property descriptor type
/// </summary>
public Type DescriptorType {
get { return _ConverterType; }
set { _ConverterType = value; }
}
/// <summary>
/// Constructs an STNode property attribute
/// </summary>
/// <param name="strKey">Name to be displayed</param>
/// <param name="strDesc">Description information to be displayed</param>
public STNodePropertyAttribute(string strKey, string strDesc) {
this._Name = strKey;
this._Description = strDesc;
}
//private Type m_descriptor_type_base = typeof(STNodePropertyDescriptor);
}
/// <summary>
/// STNode property descriptor
/// Used to determine how to interact with the property's value on the property editor and to determine how the property's value will be drawn and interacted on the property editor
/// </summary>
public class STNodePropertyDescriptor
{
/// <summary>
/// Get the target node
/// </summary>
public STNode Node { get; internal set; }
/// <summary>
/// Get the node attribute editor control to which it belongs
/// </summary>
public STNodePropertyGrid Control { get; internal set; }
/// <summary>
/// Get the area where the option is located
/// </summary>
public Rectangle Rectangle { get; internal set; }
/// <summary>
/// Get the area where the option name is located
/// </summary>
public Rectangle RectangleL { get; internal set; }
/// <summary>
/// Get the area where the option value is located
/// </summary>
public Rectangle RectangleR { get; internal set; }
/// <summary>
/// Get the name of the option that needs to be displayed
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Get the description information corresponding to the attribute
/// </summary>
public string Description { get; internal set; }
/// <summary>
/// Get attribute information
/// </summary>
public PropertyInfo PropertyInfo { get; internal set; }
private static Type m_t_int = typeof(int);
private static Type m_t_float = typeof(float);
private static Type m_t_double = typeof(double);
private static Type m_t_string = typeof(string);
private static Type m_t_bool = typeof(bool);
private StringFormat m_sf;
/// <summary>
/// Construct a descriptor
/// </summary>
public STNodePropertyDescriptor() {
m_sf = new StringFormat();
m_sf.LineAlignment = StringAlignment.Center;
m_sf.FormatFlags = StringFormatFlags.NoWrap;
}
/// <summary>
/// Occurs when determining the position of the STNode property on the property editor
/// </summary>
protected internal virtual void OnSetItemLocation() { }
/// <summary>
/// Converts the property value in the form of a string to the value of the property target type
/// By default, only int float double string bool and Array of the above types are supported
/// If the target type is not in the above, please rewrite this function to convert it yourself
/// </summary>
/// <param name="strText">Attribute value in string form</param>
/// <returns>The value of the real target type of the attribute</returns>
protected internal virtual object GetValueFromString(string strText) {
Type t = this.PropertyInfo.PropertyType;
if (t == m_t_int) return int.Parse(strText);
if (t == m_t_float) return float.Parse(strText);
if (t == m_t_double) return double.Parse(strText);
if (t == m_t_string) return strText;
if (t == m_t_bool) return bool.Parse(strText);
if (t.IsEnum) {
return Enum.Parse(t, strText);
} else if (t.IsArray) {
var t_1 = t.GetElementType();
if (t_1 == m_t_string) return strText.Split(',');
string[] strs = strText.Trim(new char[] { ' ', ',' }).Split(',');//add other place trim()
if (t_1 == m_t_int) {
int[] arr = new int[strs.Length];
for (int i = 0; i < strs.Length; i++) arr[i] = int.Parse(strs[i].Trim());
return arr;
}
if (t_1 == m_t_float) {
float[] arr = new float[strs.Length];
for (int i = 0; i < strs.Length; i++) arr[i] = float.Parse(strs[i].Trim());
return arr;
}
if (t_1 == m_t_int) {
double[] arr = new double[strs.Length];
for (int i = 0; i < strs.Length; i++) arr[i] = double.Parse(strs[i].Trim());
return arr;
}
if (t_1 == m_t_int) {
bool[] arr = new bool[strs.Length];
for (int i = 0; i < strs.Length; i++) arr[i] = bool.Parse(strs[i].Trim());
return arr;
}
}
throw new InvalidCastException("Unable to complete the conversion of [string] to [" + t.FullName + "], please overload [STNodePropertyDescriptor.GetValueFromString(string)]");
}
/// <summary>
/// Converts the value of the attribute target type to a value in the form of a string
/// ToString() operation on type value by default
/// If you need special processing, please rewrite this function to convert it yourself
/// </summary>
/// <returns>String form of attribute value</returns>
protected internal virtual string GetStringFromValue() {
var v = this.PropertyInfo.GetValue(this.Node, null);
var t = this.PropertyInfo.PropertyType;
if (v == null) return null;
if (t.IsArray) {
List<string> lst = new List<string>();
foreach (var item in (Array)v) lst.Add(item.ToString());
return string.Join(",", lst.ToArray());
}
return v.ToString();
}
/// <summary>
/// Convert the attribute value in binary form to the value of the attribute target type for restoring the attribute value from the data in the file store
/// Convert it to a string by default and then call GetValueFromString(string)
/// This function corresponds to GetBytesFromValue(). If the function needs to be rewritten, the two functions should be rewritten together
/// </summary>
/// <param name="byData">Binary data</param>
/// <returns>The value of the real target type of the attribute</returns>
protected internal virtual object GetValueFromBytes(byte[] byData) {
if (byData == null) return null;
string strText = Encoding.UTF8.GetString(byData);
return this.GetValueFromString(strText);
}
/// <summary>
/// Called when converting the value of the attribute target type to a binary value for file storage
/// Calls GetStringFromValue() by default and converts the string to binary data
/// For special handling, please rewrite this function to convert it yourself and rewrite GetValueFromBytes()
/// </summary>
/// <returns>Binary form of attribute value</returns>
protected internal virtual byte[] GetBytesFromValue() {
string strText = this.GetStringFromValue();
if (strText == null) return null;
return Encoding.UTF8.GetBytes(strText);
}
/// <summary>
/// This function corresponds to System.Reflection.PropertyInfo.GetValue()
/// </summary>
/// <param name="index">The optional index value of the indexed attribute should be null for non-indexed attributes</param>
/// <returns>Attribute value</returns>
protected internal virtual object GetValue(object[] index) {
return this.PropertyInfo.GetValue(this.Node, index);
}
/// <summary>
/// This function corresponds to System.Reflection.PropertyInfo.SetValue()
/// </summary>
/// <param name="value">Attribute value to be set</param>
protected internal virtual void SetValue(object value) {
this.PropertyInfo.SetValue(this.Node, value, null);
}
/// <summary>
/// This function corresponds to System.Reflection.PropertyInfo.SetValue()
/// GetValueFromString(strValue) will be processed by default before calling
/// </summary>
/// <param name="strValue">The value of the attribute string that needs to be set</param>
protected internal virtual void SetValue(string strValue) {
this.PropertyInfo.SetValue(this.Node, this.GetValueFromString(strValue), null);
}
/// <summary>
/// This function corresponds to System.Reflection.PropertyInfo.SetValue()
/// GetValueFromBytes(byte[]) will be processed by default before calling
/// </summary>
/// <param name="byData">Attribute binary data to be set</param>
protected internal virtual void SetValue(byte[] byData) {
this.PropertyInfo.SetValue(this.Node, this.GetValueFromBytes(byData), null);
}
/// <summary>
/// This function corresponds to System.Reflection.PropertyInfo.SetValue()
/// </summary>
/// <param name="value">Attribute value to be set</param>
/// <param name="index">The optional index value of the indexed attribute should be null for non-indexed attributes</param>
protected internal virtual void SetValue(object value, object[] index) {
this.PropertyInfo.SetValue(this.Node, value, index);
}
/// <summary>
/// This function corresponds to System.Reflection.PropertyInfo.SetValue()
/// GetValueFromString(strValue) will be processed by default before calling
/// </summary>
/// <param name="strValue">The value of the attribute string that needs to be set</param>
/// <param name="index">The optional index value of the indexed attribute should be null for non-indexed attributes</param>
protected internal virtual void SetValue(string strValue, object[] index) {
this.PropertyInfo.SetValue(this.Node, this.GetValueFromString(strValue), index);
}
/// <summary>
/// This function corresponds to System.Reflection.PropertyInfo.SetValue()
/// GetValueFromBytes(byte[]) will be processed by default before calling
/// </summary>
/// <param name="byData">Attribute binary data to be set</param>
/// <param name="index">The optional index value of the indexed attribute should be null for non-indexed attributes</param>
protected internal virtual void SetValue(byte[] byData, object[] index) {
this.PropertyInfo.SetValue(this.Node, this.GetValueFromBytes(byData), index);
}
/// <summary>
/// Occurs when there is an error setting the property value
/// </summary>
/// <param name="ex">Exception information</param>
protected internal virtual void OnSetValueError(Exception ex) {
this.Control.SetErrorMessage(ex.Message);
}
/// <summary>
/// Occurs when drawing the property's value area on the property editor
/// </summary>
/// <param name="dt">Drawing tool</param>
protected internal virtual void OnDrawValueRectangle(DrawingTools dt) {
Graphics g = dt.Graphics;
SolidBrush brush = dt.SolidBrush;
STNodePropertyGrid ctrl = this.Control;
//STNodePropertyItem item = this._PropertyItem;
brush.Color = ctrl.ItemValueBackColor;
g.FillRectangle(brush, this.RectangleR);
Rectangle rect = this.RectangleR;
rect.Width--; rect.Height--;
brush.Color = this.Control.ForeColor;
g.DrawString(this.GetStringFromValue(), ctrl.Font, brush, this.RectangleR, m_sf);
if (this.PropertyInfo.PropertyType.IsEnum || this.PropertyInfo.PropertyType == m_t_bool) {
g.FillPolygon(Brushes.Gray, new Point[]{
new Point(rect.Right - 13, rect.Top + rect.Height / 2 - 2),
new Point(rect.Right - 4, rect.Top + rect.Height / 2 - 2),
new Point(rect.Right - 9, rect.Top + rect.Height / 2 + 3)
});
}
}
/// <summary>
/// Occurs when the mouse enters the area where the property value is located
/// </summary>
/// <param name="e">Event parameters</param>
protected internal virtual void OnMouseEnter(EventArgs e) { }
/// <summary>
/// Occurs when the mouse is clicked in the area where the property value is located
/// </summary>
/// <param name="e">Event parameters</param>
protected internal virtual void OnMouseDown(MouseEventArgs e) {
}
/// <summary>
/// Occurs when the mouse moves in the area where the property value is located
/// </summary>
/// <param name="e">Event parameters</param>
protected internal virtual void OnMouseMove(MouseEventArgs e) { }
/// <summary>
/// Occurs when the mouse is raised in the area where the property value is located
/// </summary>
/// <param name="e">Event parameters</param>
protected internal virtual void OnMouseUp(MouseEventArgs e) { }
/// <summary>
/// Occurs when the mouse leaves the area where the property value is located
/// </summary>
/// <param name="e">Event parameters</param>
protected internal virtual void OnMouseLeave(EventArgs e) { }
/// <summary>
/// Occurs when the mouse is clicked in the area where the property value is located
/// </summary>
/// <param name="e">Event parameters</param>
protected internal virtual void OnMouseClick(MouseEventArgs e) {
Type t = this.PropertyInfo.PropertyType;
if (t == m_t_bool || t.IsEnum) {
new FrmSTNodePropertySelect(this).Show(this.Control);
return;
}
Rectangle rect = this.Control.RectangleToScreen(this.RectangleR);
new FrmSTNodePropertyInput(this).Show(this.Control);
}
/// <summary>
/// Redraw the options area
/// </summary>
public void Invalidate() {
Rectangle rect = this.Rectangle;
rect.X -= this.Control.ScrollOffset;
this.Control.Invalidate(rect);
}
}
}

View File

@ -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
{
/// <summary>
/// STNode node attribute editor
/// </summary>
public class STNodePropertyGrid : Control
{
#region properties ==========
private STNode _STNode;
/// <summary>
/// The currently displayed STNode
/// </summary>
[Description("Currently displayed STNode"), Browsable(false)]
public STNode STNode {
get { return _STNode; }
}
private Color _ItemHoverColor = Color.FromArgb(50, 125, 125, 125);
/// <summary>
/// Gets or sets the background color when the property option is hovered by the mouse
/// </summary>
[Description("Get or set the background color when the property option is hovered by the mouse")]
public Color ItemHoverColor {
get { return _ItemHoverColor; }
set { _ItemHoverColor = value; }
}
private Color _ItemSelectedColor = Color.DodgerBlue;
/// <summary>
/// Gets or sets the background color when the property option is selected. This property cannot be set when AutoColor is set
/// </summary>
[Description("Background color when get or set property option is selected and this property cannot be set when AutoColor is set"), DefaultValue(typeof(Color), "DodgerBlue")]
public Color ItemSelectedColor {
get { return _ItemSelectedColor; }
set {
if (this._AutoColor) return;
if (value == _ItemSelectedColor) return;
_ItemSelectedColor = value;
this.Invalidate();
}
}
private Color _ItemValueBackColor = Color.FromArgb(255, 80, 80, 80);
/// <summary>
/// Gets or sets the background color of the attribute option value
/// </summary>
[Description("Get or set attribute option value background color")]
public Color ItemValueBackColor {
get { return _ItemValueBackColor; }
set {
_ItemValueBackColor = value;
this.Invalidate();
}
}
private Color _TitleColor = Color.FromArgb(127, 0, 0, 0);
/// <summary>
/// Gets or sets the default title background color
/// </summary>
[Description("Get or set the default title background color")]
public Color TitleColor {
get { return _TitleColor; }
set {
_TitleColor = value;
if (!this._ShowTitle) return;
this.Invalidate(m_rect_title);
}
}
private Color _ErrorColor = Color.FromArgb(200, Color.Brown);
/// <summary>
/// Get or set the background color of the prompt message when the property is set incorrectly
/// </summary>
[Description("Get or set the background color of the message when the property is set incorrectly")]
public Color ErrorColor {
get { return _ErrorColor; }
set { _ErrorColor = value; }
}
private Color _DescriptionColor = Color.FromArgb(200, Color.DarkGoldenrod);
/// <summary>
/// Gets or sets the background color of the attribute description information
/// </summary>
[Description("Get or set the background color of attribute description information")]
public Color DescriptionColor {
get { return _DescriptionColor; }
set { _DescriptionColor = value; }
}
private bool _ShowTitle = true;
/// <summary>
/// Gets or sets whether to display the node title
/// </summary>
[Description("Get or set whether to display the node title")]
public bool ShowTitle {
get { return _ShowTitle; }
set {
_ShowTitle = value;
this.SetItemRectangle();
this.Invalidate();
}
}
private bool _AutoColor = true;
/// <summary>
/// Get or set whether to automatically set the control highlight color according to STNode
/// </summary>
[Description("Get or set whether to automatically set the control highlight color according to STNode"), DefaultValue(true)]
public bool AutoColor {
get { return _AutoColor; }
set { _AutoColor = value; }
}
private bool _InfoFirstOnDraw;
/// <summary>
/// Gets or whether the information panel is drawn first when the node is set
/// </summary>
[Description("Get or set whether to draw the information panel first when the node is set"), DefaultValue(false)]
public bool InfoFirstOnDraw {
get { return _InfoFirstOnDraw; }
set { _InfoFirstOnDraw = value; }
}
private bool _ReadOnlyModel;
/// <summary>
/// Gets or sets whether the current property editor is in read-only mode
/// </summary>
[Description("Get or set whether the current property editor is in read-only mode"), DefaultValue(false)]
public bool ReadOnlyModel {
get { return _ReadOnlyModel; }
set {
if (value == _ReadOnlyModel) return;
_ReadOnlyModel = value;
this.Invalidate(m_rect_title);
}
}
/// <summary>
/// Get the current scroll bar height
/// </summary>
[Description("Get the current scroll bar height")]
public int ScrollOffset { get { return m_nOffsetY; } }
#endregion
#region protected fields ==========
/// <summary>
/// Author link address area
/// </summary>
protected Rectangle m_rect_link;
/// <summary>
/// View the help button area
/// </summary>
protected Rectangle m_rect_help;
/// <summary>
/// Editor title area
/// </summary>
protected Rectangle m_rect_title;
/// <summary>
/// Panel toggle button area
/// </summary>
protected Rectangle m_rect_switch;
/// <summary>
/// The vertical scroll offset used by the control during drawing
/// </summary>
protected int m_nOffsetY;
/// <summary>
/// Saved info panel vertical scroll offset
/// </summary>
protected int m_nInfoOffsetY;
/// <summary>
/// Saved property panel vertical scroll offset
/// </summary>
protected int m_nPropertyOffsetY;
/// <summary>
/// The total height of the drawing area used by the control during drawing
/// </summary>
protected int m_nVHeight;
/// <summary>
/// The total height required for the saved info panel
/// </summary>
protected int m_nInfoVHeight;
/// <summary>
/// The total height required by the saved property panel
/// </summary>
protected int m_nPropertyVHeight;
/// <summary>
/// Key in the information panel displays the required horizontal width
/// </summary>
protected int m_nInfoLeft;
#endregion
private Type m_type;
private string[] m_KeysString = new string[] { "author", "email", "link", "view help" };
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);
//All property lists are saved in this List
private List<STNodePropertyDescriptor> m_lst_item = new List<STNodePropertyDescriptor>();
private STNodePropertyDescriptor m_item_hover; //The currently hovered option
private STNodePropertyDescriptor m_item_hover_value; //The current value area is hovered by the mouse
private STNodePropertyDescriptor m_item_down_value; //The option that the current value area is clicked by the mouse
private STNodePropertyDescriptor m_item_selected; //The currently selected option
private STNodeAttribute m_node_attribute; //Node parameter information
private bool m_b_hover_switch; //Whether the mouse hovers over the panel switch button
private bool m_b_current_draw_info; //The current drawing is the information panel
private Point m_pt_move; //The real-time coordinates of the mouse on the control
private Point m_pt_down; //The coordinates of the last mouse click on the control
private string m_str_err; // draw error message when set
private string m_str_desc; //Draw description information when set
private Pen m_pen;
private SolidBrush m_brush;
private StringFormat m_sf;
private DrawingTools m_dt;
/// <summary>
/// Construct a node attribute editor
/// </summary>
public STNodePropertyGrid() {
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
m_pen = new Pen(Color.Black, 1);
m_brush = new SolidBrush(Color.Black);
m_sf = new StringFormat();
m_sf.LineAlignment = StringAlignment.Center;
m_sf.FormatFlags = StringFormatFlags.NoWrap;
m_dt.Pen = m_pen;
m_dt.SolidBrush = m_brush;
this.ForeColor = Color.White;
this.BackColor = Color.FromArgb(255, 35, 35, 35);
this.MinimumSize = new Size(120, 50);
this.Size = new Size(200, 150);
}
#region private method ==========
private List<STNodePropertyDescriptor> GetProperties(STNode node) {
List<STNodePropertyDescriptor> lst = new List<STNodePropertyDescriptor>();
if (node == null) return lst;
Type t = node.GetType();
foreach (var p in t.GetProperties()) {
var attrs = p.GetCustomAttributes(true);
foreach (var a in attrs) {
if (!(a is STNodePropertyAttribute)) continue;
var attr = a as STNodePropertyAttribute;
object obj = Activator.CreateInstance(attr.DescriptorType);
if (!(obj is STNodePropertyDescriptor))
throw new ArgumentException("[STNodePropertyAttribute.DescriptorType] parameter value must be the type of [STNodePropertyDescriptor] or its subclass");
var desc = (STNodePropertyDescriptor)Activator.CreateInstance(attr.DescriptorType);
desc.Node = node;
desc.Name = attr.Name;
desc.Description = attr.Description;
desc.PropertyInfo = p;
desc.Control = this;
lst.Add(desc);
}
}
return lst;
}
private STNodeAttribute GetNodeAttribute(STNode node) {
if (node == null) return null;
Type t = node.GetType();
foreach (var v in t.GetCustomAttributes(true)) {
if (!(v is STNodeAttribute)) continue;
return (STNodeAttribute)v;
}
return null;
}
private void SetItemRectangle() {
int nw_p = 0, nw_h = 0;
using (Graphics g = this.CreateGraphics()) {
foreach (var v in m_lst_item) {
SizeF szf = g.MeasureString(v.Name, this.Font);
if (szf.Width > nw_p) nw_p = (int)Math.Ceiling(szf.Width);
}
for (int i = 0; i < m_KeysString.Length - 1; i++) {
SizeF szf = g.MeasureString(m_KeysString[i], this.Font);
if (szf.Width > nw_h) nw_h = (int)Math.Ceiling(szf.Width);
}
nw_p += 5; nw_h += 5;
nw_p = Math.Min(nw_p, this.Width >> 1);
m_nInfoLeft = Math.Min(nw_h, this.Width >> 1);
int nTitleHeight = this._ShowTitle ? m_nTitleHeight : 0;
for (int i = 0; i < m_lst_item.Count; i++) {
STNodePropertyDescriptor item = m_lst_item[i];
Rectangle rect = new Rectangle(0, i * m_item_height + nTitleHeight, this.Width, m_item_height);
item.Rectangle = rect;
rect.Width = nw_p;
item.RectangleL = rect;
rect.X = rect.Right;
rect.Width = this.Width - rect.Left - 1;
rect.Inflate(-4, -4);
item.RectangleR = rect;
item.OnSetItemLocation();
}
m_nPropertyVHeight = m_lst_item.Count * m_item_height;
if (this._ShowTitle) m_nPropertyVHeight += m_nTitleHeight;
}
}
#endregion
#region override ==========
/// <summary>
/// Occurs when the control is redrawn
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
m_dt.Graphics = g;
m_nOffsetY = m_b_current_draw_info ? m_nInfoOffsetY : m_nPropertyOffsetY;
g.TranslateTransform(0, m_nOffsetY);
if (m_b_current_draw_info) {
m_nVHeight = m_nInfoVHeight;
this.OnDrawInfo(m_dt);
} else {
m_nVHeight = m_nPropertyVHeight;
for (int i = 0; i < m_lst_item.Count; i++) {
this.OnDrawPropertyItem(m_dt, m_lst_item[i], i);
}
}
g.ResetTransform();
if (this._ShowTitle) this.OnDrawTitle(m_dt);
m_sf.FormatFlags = 0;
if (!string.IsNullOrEmpty(m_str_err)) this.OnDrawErrorInfo(m_dt);
if (!string.IsNullOrEmpty(m_str_desc)) this.OnDrawDescription(m_dt);
}
/// <summary>
/// Occurs when the mouse moves over the control
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnMouseMove(MouseEventArgs e) {
base.OnMouseMove(e);
m_pt_move = e.Location;
bool bHover = this._ShowTitle && m_rect_switch.Contains(e.Location);
if (bHover != m_b_hover_switch) {
m_b_hover_switch = bHover;
this.Invalidate(m_rect_switch);
}
Point pt = new Point(e.X, e.Y - (int)m_nOffsetY);
MouseEventArgs mea = new MouseEventArgs(e.Button, e.Clicks, pt.X, pt.Y, e.Delta);
if (m_b_current_draw_info)
this.OnProcessHelpMouseMove(mea);
else
this.OnProcessPropertyMouseMove(mea);
}
/// <summary>
/// Occurs when the mouse is clicked on the control
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnMouseDown(MouseEventArgs e) {
base.OnMouseDown(e);
m_pt_down = e.Location;
this.Focus();
bool bRedraw = false;
if (m_str_err != null) {
bRedraw = true;
m_str_err = null;
}
if (this._ShowTitle) {
if (m_rect_switch.Contains(e.Location)) {
if (m_node_attribute == null || m_lst_item.Count == 0) return;
m_b_current_draw_info = !m_b_current_draw_info;
this.Invalidate();
return;
} else if (m_rect_title.Contains(e.Location)) {
return;
}
}
if (this._ShowTitle && m_rect_switch.Contains(e.Location)) {
if (m_node_attribute == null || m_lst_item.Count == 0) return;
m_b_current_draw_info = !m_b_current_draw_info;
this.Invalidate();
return;
}
Point pt = new Point(e.X, e.Y - (int)m_nOffsetY);
MouseEventArgs mea = new MouseEventArgs(e.Button, e.Clicks, pt.X, pt.Y, e.Delta);
if (m_b_current_draw_info)
this.OnProcessInfoMouseDown(mea);
else
this.OnProcessPropertyMouseDown(mea);
if (bRedraw) this.Invalidate();
}
/// <summary>
/// Occurs when the mouse is raised over the control
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnMouseUp(MouseEventArgs e) {
base.OnMouseUp(e);
m_str_desc = null;
if (m_item_down_value != null && !this._ReadOnlyModel) {
Point pt = new Point(e.X, e.Y - (int)m_nOffsetY);
MouseEventArgs mea = new MouseEventArgs(e.Button, e.Clicks, pt.X, pt.Y, e.Delta);
m_item_down_value.OnMouseUp(mea);
if (m_pt_down == e.Location && !this._ReadOnlyModel) {
m_item_down_value.OnMouseClick(mea);
}
}
m_item_down_value = null;
this.Invalidate();
}
/// <summary>
/// Occurs when the mouse leaves the control
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave(e);
m_b_hover_switch = false;
if (m_item_hover_value != null && !this._ReadOnlyModel) m_item_hover_value.OnMouseLeave(e);
m_item_hover = null;
this.Invalidate();
}
/// <summary>
/// Occurs when the mouse rolls the wheel on the control
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnMouseWheel(MouseEventArgs e) {
base.OnMouseWheel(e);
if (e.Delta > 0) {
if (m_nOffsetY == 0) return;
m_nOffsetY += m_item_height;
if (m_nOffsetY > 0) m_nOffsetY = 0;
} else {
if (this.Height - m_nOffsetY >= m_nVHeight) return;
m_nOffsetY -= m_item_height;
}
if (m_b_current_draw_info)
m_nInfoOffsetY = m_nOffsetY;
else
m_nPropertyOffsetY = m_nOffsetY;
this.Invalidate();
}
/// <summary>
/// Occurs when the control rectangle area is set
/// </summary>
/// <param name="x">x coordinate</param>
/// <param name="y">y coordinate</param>
/// <param name="width">width</param>
/// <param name="height">height</param>
/// <param name="specified">Specify the identifier to be set</param>
//protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
// if (width < 120) width = 120;
// if (height < 50) height = 50;
// base.SetBoundsCore(x, y, width, height, specified);
//}
/// <summary>
/// Occurs when the control size changes
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnResize(EventArgs e) {
base.OnResize(e);
m_rect_title.Width = this.Width;
m_rect_title.Height = m_nTitleHeight;
if (this._ShowTitle)
m_rect_switch = new Rectangle(this.Width - m_nTitleHeight + 2, 2, m_nTitleHeight - 4, m_nTitleHeight - 4);
if (this._STNode != null) this.SetItemRectangle();
}
#endregion
#region virtual method ==========
/// <summary>
/// Occurs when drawing attribute options
/// </summary>
/// <param name="dt">Drawing tool</param>
/// <param name="item">Target attribute option descriptor</param>
/// <param name="nIndex">The index of the option</param>
protected virtual void OnDrawPropertyItem(DrawingTools dt, STNodePropertyDescriptor item, int nIndex) {
Graphics g = dt.Graphics;
m_brush.Color = (nIndex % 2 == 0) ? m_clr_item_1 : m_clr_item_2;
g.FillRectangle(m_brush, item.Rectangle);
if (item == m_item_hover || item == m_item_selected) {
m_brush.Color = this._ItemHoverColor;
g.FillRectangle(m_brush, item.Rectangle);
}
if (m_item_selected == item) {
g.FillRectangle(m_brush, item.Rectangle.X, item.Rectangle.Y, 5, item.Rectangle.Height);
if (this._AutoColor && this._STNode != null)
m_brush.Color = this._STNode.TitleColor;
else
m_brush.Color = this._ItemSelectedColor;
g.FillRectangle(m_brush, item.Rectangle.X, item.Rectangle.Y + 4, 5, item.Rectangle.Height - 8);
}
m_sf.Alignment = StringAlignment.Far;
m_brush.Color = this.ForeColor;
g.DrawString(item.Name, this.Font, m_brush, item.RectangleL, m_sf);
item.OnDrawValueRectangle(m_dt);
if (this._ReadOnlyModel) {
m_brush.Color = Color.FromArgb(125, 125, 125, 125);
g.FillRectangle(m_brush, item.RectangleR);
m_pen.Color = this.ForeColor;
//g.DrawLine(m_pen,
// item.RectangleR.Left - 2, item.RectangleR.Top + item.RectangleR.Height / 2,
// item.RectangleR.Right + 1, item.RectangleR.Top + item.RectangleR.Height / 2);
}
}
/// <summary>
/// Draw the property window title
/// </summary>
/// <param name="dt">Drawing tool</param>
protected virtual void OnDrawTitle(DrawingTools dt) {
Graphics g = dt.Graphics;
if (this._AutoColor)
m_brush.Color = this._STNode == null ? this._TitleColor : this._STNode.TitleColor;
else
m_brush.Color = this._TitleColor;
g.FillRectangle(m_brush, m_rect_title);
m_brush.Color = this._STNode == null ? this.ForeColor : this._STNode.ForeColor;
m_sf.Alignment = StringAlignment.Center;
g.DrawString(this._STNode == null ? this.Text : this._STNode.Title, this.Font, m_brush, m_rect_title, m_sf);
if (this._ReadOnlyModel) {
m_brush.Color = this.ForeColor;
g.FillRectangle(dt.SolidBrush, 4, 5, 2, 4);
g.FillRectangle(dt.SolidBrush, 6, 5, 2, 2);
g.FillRectangle(dt.SolidBrush, 8, 5, 2, 4);
g.FillRectangle(dt.SolidBrush, 3, 9, 8, 6);
}
//Whether to draw the panel switch button
if (m_node_attribute == null || m_lst_item.Count == 0) return;
if (m_b_hover_switch) {
m_brush.Color = this.BackColor;
g.FillRectangle(m_brush, m_rect_switch);
}
m_pen.Color = this._STNode == null ? this.ForeColor : this._STNode.ForeColor;
m_brush.Color = m_pen.Color;
int nT1 = m_rect_switch.Top + m_rect_switch.Height / 2 - 2;
int nT2 = m_rect_switch.Top + m_rect_switch.Height / 2 + 1;
g.DrawRectangle(m_pen, m_rect_switch.Left, m_rect_switch.Top, m_rect_switch.Width - 1, m_rect_switch.Height - 1);
g.DrawLines(m_pen, new Point[]{
new Point(m_rect_switch.Left + 2, nT1), new Point(m_rect_switch.Right - 3, nT1),
new Point(m_rect_switch.Left + 3, nT1 - 1), new Point(m_rect_switch.Right - 3, nT1 - 1)
});
g.DrawLines(m_pen, new Point[]{
new Point(m_rect_switch.Left + 2, nT2), new Point(m_rect_switch.Right - 3, nT2),
new Point(m_rect_switch.Left + 2, nT2 + 1), new Point(m_rect_switch.Right - 4, nT2 + 1),
});
g.FillPolygon(m_brush, new Point[]{
new Point(m_rect_switch.Left + 2, nT1),
new Point(m_rect_switch.Left + 7, nT1),
new Point(m_rect_switch.Left + 7, m_rect_switch.Top ),
});
g.FillPolygon(m_brush, new Point[]{
new Point(m_rect_switch.Right - 2, nT2),
new Point(m_rect_switch.Right - 7, nT2),
new Point(m_rect_switch.Right - 7, m_rect_switch.Bottom - 2 ),
});
}
/// <summary>
/// Occurs when the attribute description information needs to be drawn
/// </summary>
/// <param name="dt">Drawing tool</param>
protected virtual void OnDrawDescription(DrawingTools dt) {
if (string.IsNullOrEmpty(m_str_desc)) return;
Graphics g = dt.Graphics;
SizeF szf = g.MeasureString(m_str_desc, this.Font, this.Width - 4);
Rectangle rect_desc = new Rectangle(0, this.Height - (int)szf.Height - 4, this.Width, (int)szf.Height + 4);
m_brush.Color = this._DescriptionColor;
g.FillRectangle(m_brush, rect_desc);
m_pen.Color = this._DescriptionColor;
g.DrawRectangle(m_pen, 0, rect_desc.Top, rect_desc.Width - 1, rect_desc.Height - 1);
rect_desc.Inflate(-4, 0);
m_brush.Color = this.ForeColor;
m_sf.Alignment = StringAlignment.Near;
g.DrawString(m_str_desc, this.Font, m_brush, rect_desc, m_sf);
}
/// <summary>
/// Occurs when an error message needs to be drawn
/// </summary>
/// <param name="dt">Drawing tool</param>
protected virtual void OnDrawErrorInfo(DrawingTools dt) {
if (string.IsNullOrEmpty(m_str_err)) return;
Graphics g = dt.Graphics;
SizeF szf = g.MeasureString(m_str_err, this.Font, this.Width - 4);
Rectangle rect_desc = new Rectangle(0, 0, this.Width, (int)szf.Height + 4);
m_brush.Color = this._ErrorColor;
g.FillRectangle(m_brush, rect_desc);
m_pen.Color = this._ErrorColor;
g.DrawRectangle(m_pen, 0, rect_desc.Top, rect_desc.Width - 1, rect_desc.Height - 1);
rect_desc.Inflate(-4, 0);
m_brush.Color = this.ForeColor;
m_sf.Alignment = StringAlignment.Near;
g.DrawString(m_str_err, this.Font, m_brush, rect_desc, m_sf);
}
/// <summary>
/// Occurs when drawing node information
/// </summary>
/// <param name="dt">Drawing tool</param>
protected virtual void OnDrawInfo(DrawingTools dt) {
if (m_node_attribute == null) return;
var attr = m_node_attribute;
Graphics g = dt.Graphics;
Color clr_r = Color.FromArgb(this.ForeColor.A / 2, this.ForeColor);
m_sf.Alignment = StringAlignment.Near;
Rectangle rect = new Rectangle(0, this._ShowTitle ? m_nTitleHeight : 0, this.Width, m_item_height);
Rectangle rect_l = new Rectangle(2, rect.Top, m_nInfoLeft - 2, m_item_height);
Rectangle rect_r = new Rectangle(m_nInfoLeft, rect.Top, this.Width - m_nInfoLeft, m_item_height);
m_brush.Color = m_clr_item_2;
g.FillRectangle(m_brush, rect);
m_brush.Color = this.ForeColor;
m_sf.FormatFlags = StringFormatFlags.NoWrap;
m_sf.Alignment = StringAlignment.Near;
g.DrawString(m_KeysString[0], this.Font, m_brush, rect_l, m_sf); //author
m_brush.Color = clr_r;
g.DrawString(attr.Author, this.Font, m_brush, rect_r, m_sf);
rect.Y += m_item_height; rect_l.Y += m_item_height; rect_r.Y += m_item_height;
m_brush.Color = m_clr_item_1;
g.FillRectangle(m_brush, rect);
m_brush.Color = this.ForeColor;
g.DrawString(m_KeysString[1], this.Font, m_brush, rect_l, m_sf); //mail
m_brush.Color = clr_r;
g.DrawString(attr.Mail, this.Font, m_brush, rect_r, m_sf);
rect.Y += m_item_height; rect_l.Y += m_item_height; rect_r.Y += m_item_height;
m_brush.Color = m_clr_item_2;
g.FillRectangle(m_brush, rect);
m_brush.Color = this.ForeColor;
g.DrawString(m_KeysString[2], this.Font, m_brush, rect_l, m_sf); //link_key
m_brush.Color = clr_r;
g.DrawString(attr.Link, this.Font, Brushes.CornflowerBlue, rect_r, m_sf); //link
if (!string.IsNullOrEmpty(attr.Link)) m_rect_link = rect_r;
//fill left
m_brush.Color = Color.FromArgb(40, 125, 125, 125);
g.FillRectangle(m_brush, 0, this._ShowTitle ? m_nTitleHeight : 0, m_nInfoLeft - 1, m_item_height * 3);
rect.X = 5; rect.Y += m_item_height;
rect.Width = this.Width - 10;
if (!string.IsNullOrEmpty(m_node_attribute.Description)) {
float h = g.MeasureString(m_node_attribute.Description, this.Font, rect.Width).Height;
rect.Height = (int)Math.Ceiling(h / m_item_height) * m_item_height;
m_brush.Color = clr_r;
m_sf.FormatFlags = 0;
g.DrawString(m_node_attribute.Description, this.Font, m_brush, rect, m_sf);
}
m_nInfoVHeight = rect.Bottom;
bool bHasHelp = STNodeAttribute.GetHelpMethod(m_type) != null;
rect.X = 5; rect.Y += rect.Height;
rect.Height = m_item_height;
m_sf.Alignment = StringAlignment.Center;
m_brush.Color = Color.FromArgb(125, 125, 125, 125);
g.FillRectangle(m_brush, rect);
if (bHasHelp) m_brush.Color = Color.CornflowerBlue;
g.DrawString(m_KeysString[3], this.Font, m_brush, rect, m_sf);
if (bHasHelp) m_rect_help = rect;
else {
int w = (int)g.MeasureString(m_KeysString[3], this.Font).Width + 1;
int x = rect.X + (rect.Width - w) / 2, y = rect.Y + rect.Height / 2;
m_pen.Color = m_brush.Color;
g.DrawLine(m_pen, x, y, x + w, y);
}
m_nInfoVHeight = rect.Bottom;
}
/// <summary>
/// Occurs when the mouse is clicked on the property panel
/// </summary>
/// <param name="e">Mouse event parameters</param>
protected virtual void OnProcessPropertyMouseDown(MouseEventArgs e) {
bool bRedraw = false;
if (m_item_selected != m_item_hover) {
m_item_selected = m_item_hover;
bRedraw = true;
}
m_item_down_value = null;
if (m_item_selected == null) {
if (bRedraw) this.Invalidate();
return;
}
if (m_item_selected.RectangleR.Contains(e.Location)) {
m_item_down_value = m_item_selected;
if (!this._ReadOnlyModel)
m_item_selected.OnMouseDown(e);
else {
return;
}
} else if (m_item_selected.RectangleL.Contains(e.Location)) {
m_str_desc = m_item_selected.Description;
bRedraw = true;
}
if (bRedraw) this.Invalidate();
}
/// <summary>
/// Occurs when the mouse is clicked in the info panel
/// </summary>
/// <param name="e">Mouse event parameters</param>
protected virtual void OnProcessInfoMouseDown(MouseEventArgs e) {
try {
if (m_rect_link.Contains(e.Location)) {
System.Diagnostics.Process.Start(m_node_attribute.Link);
} else if (m_rect_help.Contains(e.Location)) {
STNodeAttribute.ShowHelp(m_type);
}
} catch (Exception ex) {
this.SetErrorMessage(ex.Message);
}
}
/// <summary>
/// Occurs when the mouse is moved in the properties panel
/// </summary>
/// <param name="e">Mouse event parameters</param>
protected virtual void OnProcessPropertyMouseMove(MouseEventArgs e) {
if (m_item_down_value != null) {
m_item_down_value.OnMouseMove(e);
return;
}
STNodePropertyDescriptor item = null;
foreach (var v in m_lst_item) {
if (v.Rectangle.Contains(e.Location)) {
item = v;
break;
}
}
if (item != null) {
if (item.RectangleR.Contains(e.Location)) {
if (m_item_hover_value != item) {
if (m_item_hover_value != null) m_item_hover_value.OnMouseLeave(e);
m_item_hover_value = item;
m_item_hover_value.OnMouseEnter(e);
}
m_item_hover_value.OnMouseMove(e);
} else {
if (m_item_hover_value != null) m_item_hover_value.OnMouseLeave(e);
}
}
if (m_item_hover != item) {
m_item_hover = item;
this.Invalidate();
}
}
/// <summary>
/// Occurs when the mouse is moved in the info panel
/// </summary>
/// <param name="e">Mouse event parameters</param>
protected virtual void OnProcessHelpMouseMove(MouseEventArgs e) {
if (m_rect_link.Contains(e.Location) || m_rect_help.Contains(e.Location)) {
this.Cursor = Cursors.Hand;
} else this.Cursor = Cursors.Arrow;
}
#endregion
#region public ==========
/// <summary>
/// Set the STNode node that needs to be displayed
/// </summary>
/// <param name="node">target node</param>
public void SetNode(STNode node) {
if (node == this._STNode) return;
m_nInfoOffsetY = m_nPropertyOffsetY = 0;
m_nInfoVHeight = m_nPropertyVHeight = 0;
m_rect_link = m_rect_help = Rectangle.Empty;
m_str_desc = m_str_err = null;
this._STNode = node;
if (node != null) {
m_type = node.GetType();
m_lst_item = this.GetProperties(node);
m_node_attribute = this.GetNodeAttribute(node);
this.SetItemRectangle();
m_b_current_draw_info = m_lst_item.Count == 0 || this._InfoFirstOnDraw;
if (this._AutoColor) this._ItemSelectedColor = this._STNode.TitleColor;
} else {
m_type = null;
m_lst_item.Clear();
m_node_attribute = null;
}
this.Invalidate();
}
/// <summary>
/// Set the display text of the Key of the information page
/// </summary>
/// <param name="strAuthor">author</param>
/// <param name="strMail">mail</param>
/// <param name="strLink">connect</param>
/// <param name="strHelp">View help</param>
public void SetInfoKey(string strAuthor, string strMail, string strLink, string strHelp) {
m_KeysString = new string[] { strAuthor, strMail, strLink, strHelp };
}
/// <summary>
/// Set the error message to display
/// </summary>
/// <param name="strText">Error message</param>
public void SetErrorMessage(string strText) {
m_str_err = strText;
this.Invalidate();
}
#endregion
}
}

View File

@ -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);
/// <summary>
/// Get or set the background color of each row attribute option
/// </summary>
[Description("Get or set the background color of each row attribute option")]
public Color ItemBackColor {
get { return _ItemBackColor; }
set {
_ItemBackColor = value;
}
}
private Color _ItemHoverColor = Color.FromArgb(50, 125, 125, 125);
/// <summary>
/// Gets or sets the background color when the property option is hovered by the mouse
/// </summary>
[Description("Get or set the background color when the property option is hovered by the mouse")]
public Color ItemHoverColor {
get { return _ItemHoverColor; }
set { _ItemHoverColor = value; }
}
private Color _TitleColor = Color.FromArgb(255, 60, 60, 60);
/// <summary>
/// Get or set the background color of the top retrieval area
/// </summary>
[Description("Get or set the background color of the top retrieval area")]
public Color TitleColor {
get { return _TitleColor; }
set {
_TitleColor = value;
this.Invalidate(new Rectangle(0, 0, this.Width, m_nItemHeight));
}
}
/// <summary>
/// Gets or sets the background color of the search text box
/// </summary>
[Description("Get or set the background color of the search text box")]
public Color TextBoxColor {
get { return m_tbx.BackColor; }
set {
m_tbx.BackColor = value;
this.Invalidate(new Rectangle(0, 0, this.Width, m_nItemHeight));
}
}
private Color _HightLightTextColor = Color.Lime;
/// <summary>
/// Gets or sets the highlighted text color when retrieving
/// </summary>
[Description("Get or set the highlighted text color during retrieval"), DefaultValue(typeof(Color), "Lime")]
public Color HightLightTextColor {
get { return _HightLightTextColor; }
set { _HightLightTextColor = value; }
}
private Color _InfoButtonColor = Color.Gray;
/// <summary>
/// Gets or sets the color of the information display button. If AutoColor is set, this property value cannot be set
/// </summary>
[Description("Get or set the color of the information display button. If AutoColor is set, this property value cannot be set"), DefaultValue(typeof(Color), "Gray")]
public Color InfoButtonColor {
get { return _InfoButtonColor; }
set { _InfoButtonColor = value; }
}
private Color _FolderCountColor = Color.FromArgb(40, 255, 255, 255);
/// <summary>
/// Get or set the text color of the count
/// </summary>
[Description("Get or set the text color of statistics")]
public Color FolderCountColor {
get { return _FolderCountColor; }
set { _FolderCountColor = value; }
}
private Color _SwitchColor = Color.LightGray;
private bool _ShowFolderCount = true;
/// <summary>
/// Get or set whether to count the number of STNodes
/// </summary>
[Description("Get or set whether to count the number of STNodes"), DefaultValue(typeof(Color), "LightGray")]
public bool ShowFolderCount {
get { return _ShowFolderCount; }
set { _ShowFolderCount = value; }
}
private bool _ShowInfoButton = true;
/// <summary>
/// Gets or sets whether to display the information button
/// </summary>
[Description("Get or set whether to display the information button"), DefaultValue(true)]
public bool ShowInfoButton {
get { return _ShowInfoButton; }
set { _ShowInfoButton = value; }
}
private bool _InfoPanelIsLeftLayout = true;
/// <summary>
/// Gets or sets whether the preview window is laid out to the left
/// </summary>
[Description("Get or set whether the preview window is a left layout"), DefaultValue(true)]
public bool InfoPanelIsLeftLayout {
get { return _InfoPanelIsLeftLayout; }
set { _InfoPanelIsLeftLayout = value; }
}
private bool _AutoColor = true;
/// <summary>
/// Get or set the title color of the STNode corresponding to some colors in the control
/// </summary>
[Description("Get or set the title color of STNode corresponding to some colors in the control"), DefaultValue(true)]
public bool AutoColor {
get { return _AutoColor; }
set {
_AutoColor = value;
this.Invalidate();
}
}
private STNodeEditor _Editor;
/// <summary>
/// STNodeEditor used to get the node preview
/// </summary>
[Description("STNodeEditor used when getting node preview"), Browsable(false)]
public STNodeEditor Editor {
get { return _Editor; }
}
private STNodePropertyGrid _PropertyGrid;
/// <summary>
/// Get the STNodePropertyGrid used when getting the node preview
/// </summary>
[Description("STNodePropertyGrid used when getting node preview"), Browsable(false)]
public STNodePropertyGrid PropertyGrid {
get { return _PropertyGrid; }
}
private int m_nItemHeight = 29;
private static Type m_type_node_base = typeof(STNode);
private static char[] m_chr_splitter = new char[] { '/', '\\' };
private STNodeTreeCollection m_items_draw;
private STNodeTreeCollection m_items_source = new STNodeTreeCollection("ROOT");
private Dictionary<Type, string> m_dic_all_type = new Dictionary<Type, string>();
private Pen m_pen;
private SolidBrush m_brush;
private StringFormat m_sf;
private DrawingTools m_dt;
private Color m_clr_item_1 = Color.FromArgb(10, 0, 0, 0);// Color.FromArgb(255, 40, 40, 40);
private Color m_clr_item_2 = Color.FromArgb(10, 255, 255, 255);// Color.FromArgb(255, 50, 50, 50);
private int m_nOffsetY; //The vertical height that needs to be offset when the control is drawn
private int m_nSourceOffsetY; //The vertical height that needs to be offset when drawing source data
private int m_nSearchOffsetY; //The vertical height that needs to be offset when drawing and retrieving data
private int m_nVHeight; //The total height required by the content in the control
private bool m_bHoverInfo; //Whether the mouse is currently hovering over the information display button
private STNodeTreeCollection m_item_hover; //The tree node currently hovered by the mouse
private Point m_pt_control; //The coordinates of the mouse on the control
private Point m_pt_offsety; //The coordinates of the mouse after the hammer is offset on the control
private Rectangle m_rect_clear; //Clear the search button area
private string m_str_search; // retrieved text
private TextBox m_tbx = new TextBox(); //Retrieve the textbox
/// <summary>
/// Construct a STNode tree control
/// </summary>
public STNodeTreeView() {
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.MinimumSize = new System.Drawing.Size(100, 60);
this.Size = new System.Drawing.Size(200, 150);
m_items_draw = m_items_source;
m_pen = new Pen(Color.Black);
m_brush = new SolidBrush(Color.White);
m_sf = new StringFormat();
m_sf.LineAlignment = StringAlignment.Center;
m_dt.Pen = m_pen;
m_dt.SolidBrush = m_brush;
this.ForeColor = Color.FromArgb(255, 220, 220, 220);
this.BackColor = Color.FromArgb(255, 35, 35, 35);
m_tbx.Left = 6;
m_tbx.BackColor = Color.FromArgb(255, 30, 30, 30);
m_tbx.ForeColor = this.ForeColor;
m_tbx.BorderStyle = BorderStyle.None;
m_tbx.MaxLength = 20;
m_tbx.TextChanged += new EventHandler(m_tbx_TextChanged);
this.Controls.Add(m_tbx);
this.AllowDrop = true;
this._Editor = new STNodeEditor();
this._PropertyGrid = new STNodePropertyGrid();
}
#region private method ==========
private void m_tbx_TextChanged(object sender, EventArgs e) {
m_str_search = m_tbx.Text.Trim().ToLower();
m_nSearchOffsetY = 0;
if (m_str_search == string.Empty) {
m_items_draw = m_items_source;
this.Invalidate();
return;
}
m_items_draw = m_items_source.Copy();
this.Search(m_items_draw, new Stack<string>(), m_str_search);
this.Invalidate();
}
private bool Search(STNodeTreeCollection items, Stack<string> stack, string strText) {
bool bFound = false;
string[] strName = new string[items.Count];
int nCounter = 0;
foreach (STNodeTreeCollection v in items) {
if (v.NameLower.IndexOf(strText) != -1) {
v.IsOpen = bFound = true;
} else {
if (!this.Search(v, stack, strText)) {
stack.Push(v.Name);
nCounter++;
} else {
v.IsOpen = bFound = true;
}
}
}
for (int i = 0; i < nCounter; i++) items.Remove(stack.Pop(), false);
return bFound;
}
private bool AddSTNode(Type stNodeType, STNodeTreeCollection items, string strLibName, bool bShowException) {
if (m_dic_all_type.ContainsKey(stNodeType)) return false;
if (stNodeType == null) return false;
if (!stNodeType.IsSubclassOf(m_type_node_base)) {
if (bShowException)
throw new ArgumentException("Unsupported type [" + stNodeType.FullName + "] [stNodeType] parameter value must be the type of [STNode] subclass");
else return false;
}
var attr = this.GetNodeAttribute(stNodeType);
if (attr == null) {
if (bShowException)
throw new InvalidOperationException("Type [" + stNodeType.FullName + "] is not marked by [STNodeAttribute]");
else return false;
}
string strPath = string.Empty;
items.STNodeCount++;
if (!string.IsNullOrEmpty(attr.Path)) {
var strKeys = attr.Path.Split (m_chr_splitter);
for (int i = 0; i < strKeys.Length; i++) {
items = items.Add(strKeys[i]);
items.STNodeCount++;
strPath += "/" + strKeys[i];
}
}
try {
STNode node = (STNode)Activator.CreateInstance(stNodeType);
STNodeTreeCollection stt = new STNodeTreeCollection(node.Title);
stt.Path = (strLibName + "/" + attr.Path).Trim('/');
stt.STNodeType = stNodeType;
items[stt.Name] = stt;
stt.STNodeTypeColor = node.TitleColor;
m_dic_all_type.Add(stNodeType, stt.Path);
this.Invalidate();
} catch (Exception ex) {
if (bShowException) throw ex;
return false;
}
return true;
}
private STNodeTreeCollection AddAssemblyPrivate(string strFile) {
strFile = System.IO.Path.GetFullPath(strFile);
var asm = Assembly.LoadFrom(strFile);
STNodeTreeCollection items = new STNodeTreeCollection(System.IO.Path.GetFileNameWithoutExtension(strFile));
foreach (var v in asm.GetTypes()) {
if (v.IsAbstract) continue;
if (v.IsSubclassOf(m_type_node_base)) this.AddSTNode(v, items, items.Name, false);
}
return items;
}
private STNodeAttribute GetNodeAttribute(Type stNodeType) {
if (stNodeType == null) return null;
foreach (var v in stNodeType.GetCustomAttributes(true)) {
if (!(v is STNodeAttribute)) continue;
return (STNodeAttribute)v;
}
return null;
}
private STNodeTreeCollection FindItemByPoint(STNodeTreeCollection items, Point pt) {
foreach (STNodeTreeCollection t in items) {
if (t.DisplayRectangle.Contains(pt)) return t;
if (t.IsOpen) {
var n = FindItemByPoint (t, pt);
if (n != null) return n;
}
}
return null;
}
#endregion
#region overide method ==========
protected override void OnCreateControl() {
base.OnCreateControl();
m_tbx.Top = (m_nItemHeight - m_tbx.Height) / 2;
}
protected override void OnResize(EventArgs e) {
base.OnResize(e);
m_tbx.Width = this.Width - 29;
m_rect_clear = new Rectangle(this.Width - 20, 9, 12, 12);
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
m_nOffsetY = string.IsNullOrEmpty(m_str_search) ? m_nSourceOffsetY : m_nSearchOffsetY;
Graphics g = e.Graphics;
m_dt.Graphics = g;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
g.TranslateTransform(0, m_nOffsetY);
int nCounter = 0;
foreach (STNodeTreeCollection v in m_items_draw)
nCounter = this.OnStartDrawItem(m_dt, v, nCounter, 0);
m_nVHeight = (nCounter + 1) * m_nItemHeight;
foreach (STNodeTreeCollection v in m_items_draw)
this.OnDrawSwitch(m_dt, v);
g.ResetTransform();
this.OnDrawSearch(m_dt);
}
protected override void OnMouseMove(MouseEventArgs e) {
base.OnMouseMove(e);
bool bRedraw = false;
m_pt_offsety = m_pt_control = e.Location;
m_pt_offsety.Y -= m_nOffsetY;
if (!string.IsNullOrEmpty(m_str_search) && m_rect_clear.Contains(e.Location))
this.Cursor = Cursors.Hand;
else
this.Cursor = Cursors.Arrow;
var node = this.FindItemByPoint(m_items_draw, m_pt_offsety);
if (m_item_hover != node) {
m_item_hover = node;
bRedraw = true;
}
if (node != null) {
bool bHoverInfo = node.InfoRectangle.Contains(m_pt_offsety);
if (bHoverInfo != m_bHoverInfo) {
m_bHoverInfo = bHoverInfo;
bRedraw = true;
}
}
if (bRedraw) this.Invalidate();
}
protected override void OnMouseDown(MouseEventArgs e) {
base.OnMouseDown(e);
this.Focus();
if (!string.IsNullOrEmpty(m_str_search) && m_rect_clear.Contains(e.Location)) {
m_tbx.Text = string.Empty;
return;
}
m_pt_offsety = m_pt_control = e.Location;
m_pt_offsety.Y -= m_nOffsetY;
if (m_item_hover == null) return;
if (m_item_hover.SwitchRectangle.Contains(m_pt_offsety)) {
m_item_hover.IsOpen = !m_item_hover.IsOpen;
this.Invalidate();
} else if (m_item_hover.InfoRectangle.Contains(m_pt_offsety)) {
Rectangle rect = this.RectangleToScreen(m_item_hover.DisplayRectangle);
FrmNodePreviewPanel frm = new FrmNodePreviewPanel(m_item_hover.STNodeType,
new Point(rect.Right - m_nItemHeight, rect.Top + m_nOffsetY),
m_nItemHeight,
this._InfoPanelIsLeftLayout,
this._Editor, this._PropertyGrid);
frm.BackColor = this.BackColor;
frm.Show(this);
} else if (m_item_hover.STNodeType != null) {
DataObject d = new DataObject("STNodeType", m_item_hover.STNodeType);
this.DoDragDrop(d, DragDropEffects.Copy);
}
}
protected override void OnMouseDoubleClick(MouseEventArgs e) {
base.OnMouseDoubleClick(e);
m_pt_offsety = m_pt_control = e.Location;
m_pt_offsety.Y -= m_nOffsetY;
STNodeTreeCollection item = this.FindItemByPoint(m_items_draw, m_pt_offsety);
if (item == null || item.STNodeType != null) return;
item.IsOpen = !item.IsOpen;
this.Invalidate();
}
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave(e);
if (m_item_hover != null) {
m_item_hover = null;
this.Invalidate();
}
}
protected override void OnMouseWheel(MouseEventArgs e) {
base.OnMouseWheel(e);
if (e.Delta > 0) {
if (m_nOffsetY == 0) return;
m_nOffsetY += m_nItemHeight;
if (m_nOffsetY > 0) m_nOffsetY = 0;
} else {
if (this.Height - m_nOffsetY >= m_nVHeight) return;
m_nOffsetY -= m_nItemHeight;
}
if (string.IsNullOrEmpty(m_str_search))
m_nSourceOffsetY = m_nOffsetY;
else
m_nSearchOffsetY = m_nOffsetY;
this.Invalidate();
}
#endregion
#region protected method ==========
/// <summary>
/// Occurs when the retrieved text area is drawn
/// </summary>
/// <param name="dt">Drawing tool</param>
protected virtual void OnDrawSearch(DrawingTools dt) {
Graphics g = dt.Graphics;
m_brush.Color = this._TitleColor;
g.FillRectangle(m_brush, 0, 0, this.Width, m_nItemHeight);
m_brush.Color = m_tbx.BackColor;
g.FillRectangle(m_brush, 5, 5, this.Width - 10, m_nItemHeight - 10);
m_pen.Color = this.ForeColor;
if (string.IsNullOrEmpty(m_str_search)) {
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.DrawEllipse(m_pen, this.Width - 17, 8, 8, 8);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
g.DrawLine(m_pen, this.Width - 13, 17, this.Width - 13, m_nItemHeight - 9);
} else {
m_pen.Color = this.ForeColor;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.DrawEllipse(m_pen, this.Width - 20, 9, 10, 10);
g.DrawLine(m_pen, this.Width - 18, 11, this.Width - 12, 17);
g.DrawLine(m_pen, this.Width - 12, 11, this.Width - 18, 17);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
}
}
/// <summary>
/// Occurs when starting to draw each node of the tree node
/// </summary>
/// <param name="dt">Drawing tool</param>
/// <param name="Items">The current collection that needs to be drawn</param>
/// <param name="nCounter">Counter of drawn counts</param>
/// <param name="nLevel">Currently located in the sub-collection of which level</param>
/// <returns>Number of already drawn</returns>
protected virtual int OnStartDrawItem(DrawingTools dt, STNodeTreeCollection Items, int nCounter, int nLevel) {
Graphics g = dt.Graphics;
Items.DisplayRectangle = new Rectangle(0, m_nItemHeight * (nCounter + 1), this.Width, m_nItemHeight);
Items.SwitchRectangle = new Rectangle(5 + nLevel * 10, (nCounter + 1) * m_nItemHeight, 10, m_nItemHeight);
if (this._ShowInfoButton && Items.STNodeType != null)
Items.InfoRectangle = new Rectangle(this.Width - 18, Items.DisplayRectangle.Top + (m_nItemHeight - 14) / 2, 14, 14);
else Items.InfoRectangle = Rectangle.Empty;
this.OnDrawItem(dt, Items, nCounter++, nLevel);
if (!Items.IsOpen) return nCounter;
foreach (STNodeTreeCollection n in Items) {
if (n.STNodeType == null)
nCounter = this.OnStartDrawItem(dt, n, nCounter++, nLevel + 1);
}
foreach (STNodeTreeCollection n in Items) {
if (n.STNodeType != null)
nCounter = this.OnStartDrawItem(dt, n, nCounter++, nLevel + 1);
}
foreach (STNodeTreeCollection v in Items) this.OnDrawSwitch(dt, v);
return nCounter;
}
/// <summary>
/// Occurs when each node of the tree node is drawn
/// </summary>
/// <param name="dt">Drawing tool</param>
/// <param name="items">The current collection that needs to be drawn</param>
/// <param name="nCounter">Counter of drawn counts</param>
/// <param name="nLevel">Currently located in the sub-collection of which level</param>
protected virtual void OnDrawItem(DrawingTools dt, STNodeTreeCollection items, int nCounter, int nLevel) {
Graphics g = dt.Graphics;
m_brush.Color = nCounter % 2 == 0 ? m_clr_item_1 : m_clr_item_2;
g.FillRectangle(m_brush, items.DisplayRectangle);
if (items == m_item_hover) {
m_brush.Color = this._ItemHoverColor;
g.FillRectangle(m_brush, items.DisplayRectangle);
}
Rectangle rect = new Rectangle(45 + nLevel * 10, items.SwitchRectangle.Top, this.Width - 45 - nLevel * 10, m_nItemHeight);
m_pen.Color = Color.FromArgb(100, 125, 125, 125);
g.DrawLine(m_pen, 9, items.SwitchRectangle.Top + m_nItemHeight / 2, items.SwitchRectangle.Left + 19, items.SwitchRectangle.Top + m_nItemHeight / 2);
if (nCounter != 0) {
for (int i = 0; i <= nLevel; i++) {
g.DrawLine(m_pen, 9 + i * 10, items.SwitchRectangle.Top - m_nItemHeight / 2, 9 + i * 10, items.SwitchRectangle.Top + m_nItemHeight / 2 - 1);
}
}
this.OnDrawItemText(dt, items, rect);
this.OnDrawItemIcon(dt, items, rect);
}
/// <summary>
/// Occurs when drawing the tree node expand and close switches
/// </summary>
/// <param name="dt">Drawing tool</param>
/// <param name="items">The current collection that needs to be drawn</param>
protected virtual void OnDrawSwitch(DrawingTools dt, STNodeTreeCollection items) {
Graphics g = dt.Graphics;
if (items.Count != 0) {
m_pen.Color = this._SwitchColor;
m_brush.Color = m_pen.Color;
int nT = items.SwitchRectangle.Y + m_nItemHeight / 2 - 4;
g.DrawRectangle(m_pen, items.SwitchRectangle.Left, nT, 8, 8);
g.DrawLine(m_pen, items.SwitchRectangle.Left + 1, nT + 4, items.SwitchRectangle.Right - 3, nT + 4);
if (items.IsOpen) return;
g.DrawLine(m_pen, items.SwitchRectangle.Left + 4, nT + 1, items.SwitchRectangle.Left + 4, nT + 7);
//if (items.IsOpen) {
// //g.FillPolygon(m_brush, new Point[]{
// // new Point(items.DotRectangle.Left + 0, items.DotRectangle.Top + m_nItemHeight / 2 - 2),
// // new Point(items.DotRectangle.Left + 9, items.DotRectangle.Top + m_nItemHeight / 2 - 2),
// // new Point(items.DotRectangle.Left + 4, items.DotRectangle.Top + m_nItemHeight / 2 + 3)
// //});
// g.DrawRectangle(m_pen, items.SwitchRectangle.Left, nT, 8, 8);
// g.DrawLine(m_pen, items.SwitchRectangle.Left + 1, nT + 4, items.SwitchRectangle.Right - 3, nT + 4);
//} else {
// //g.FillPolygon(m_brush, new Point[]{
// // new Point(items.DotRectangle.Left + 2, items.DotRectangle.Top + m_nItemHeight / 2 - 5),
// // new Point(items.DotRectangle.Left + 2, items.DotRectangle.Top + m_nItemHeight / 2 + 5),
// // new Point(items.DotRectangle.Left + 7, items.DotRectangle.Top + m_nItemHeight / 2)
// //});
// g.DrawRectangle(m_pen, items.SwitchRectangle.Left, nT, 8, 8);
// g.DrawLine(m_pen, items.SwitchRectangle.Left + 1, nT + 4, items.SwitchRectangle.Right - 3, nT + 4);
// g.DrawLine(m_pen, items.SwitchRectangle.Left + 4, nT + 1, items.SwitchRectangle.Left + 4, nT + 7);
//}
}
}
/// <summary>
/// Occurs when the text of the tree node is drawn
/// </summary>
/// <param name="dt">Drawing tool</param>
/// <param name="items">The current collection that needs to be drawn</param>
/// <param name="rect">The rectangular area where the text field is located</param>
protected virtual void OnDrawItemText(DrawingTools dt, STNodeTreeCollection items, Rectangle rect) {
Graphics g = dt.Graphics;
rect.Width -= 20;
m_sf.FormatFlags = StringFormatFlags.NoWrap;
if (!string.IsNullOrEmpty(m_str_search)) {
int nIndex = items.NameLower.IndexOf(m_str_search);
if (nIndex != -1) {
CharacterRange[] chrs = { new CharacterRange(nIndex, m_str_search.Length) };//global
m_sf.SetMeasurableCharacterRanges(chrs);
Region[] regions = g.MeasureCharacterRanges(items.Name, this.Font, rect, m_sf);
g.SetClip(regions[0], System.Drawing.Drawing2D.CombineMode.Intersect);
m_brush.Color = this._HightLightTextColor;
g.DrawString(items.Name, this.Font, m_brush, rect, m_sf);
g.ResetClip();
g.SetClip(regions[0], System.Drawing.Drawing2D.CombineMode.Exclude);
m_brush.Color = items.STNodeType == null ? Color.FromArgb(this.ForeColor.A * 1 / 2, this.ForeColor) : this.ForeColor;
g.DrawString(items.Name, this.Font, m_brush, rect, m_sf);
g.ResetClip();
return;
}
}
m_brush.Color = items.STNodeType == null ? Color.FromArgb(this.ForeColor.A * 2 / 3, this.ForeColor) : this.ForeColor;
g.DrawString(items.Name, this.Font, m_brush, rect, m_sf);
}
/// <summary>
/// Occurs when the tree node icon is drawn
/// </summary>
/// <param name="dt">Drawing tool</param>
/// <param name="items">The current collection that needs to be drawn</param>
/// <param name="rect">The rectangular area where the text field is located</param>
protected virtual void OnDrawItemIcon(DrawingTools dt, STNodeTreeCollection items, Rectangle rect) {
Graphics g = dt.Graphics;
if (items.STNodeType != null) {
m_pen.Color = this._AutoColor ? items.STNodeTypeColor : Color.DarkCyan;
m_brush.Color = Color.LightGray;
g.DrawRectangle(m_pen, rect.Left - 15, rect.Top + m_nItemHeight / 2 - 5, 11, 10);
g.FillRectangle(m_brush, rect.Left - 17, rect.Top + m_nItemHeight / 2 - 2, 5, 5);
g.FillRectangle(m_brush, rect.Left - 6, rect.Top + m_nItemHeight / 2 - 2, 5, 5);
if (m_item_hover == items && m_bHoverInfo) {
m_brush.Color = this.BackColor;
g.FillRectangle(m_brush, items.InfoRectangle);
}
m_pen.Color = this._AutoColor ? items.STNodeTypeColor : this._InfoButtonColor;
m_pen.Width = 2;
g.DrawLine(m_pen, items.InfoRectangle.X + 4, items.InfoRectangle.Y + 3, items.InfoRectangle.X + 10, items.InfoRectangle.Y + 3);
g.DrawLine(m_pen, items.InfoRectangle.X + 4, items.InfoRectangle.Y + 6, items.InfoRectangle.X + 10, items.InfoRectangle.Y + 6);
g.DrawLine(m_pen, items.InfoRectangle.X + 4, items.InfoRectangle.Y + 11, items.InfoRectangle.X + 10, items.InfoRectangle.Y + 11);
g.DrawLine(m_pen, items.InfoRectangle.X + 7, items.InfoRectangle.Y + 7, items.InfoRectangle.X + 7, items.InfoRectangle.Y + 10);
m_pen.Width = 1;
g.DrawRectangle(m_pen, items.InfoRectangle.X, items.InfoRectangle.Y, items.InfoRectangle.Width - 1, items.InfoRectangle.Height - 1);
} else {
if (items.IsLibraryRoot) {
Rectangle rect_box = new Rectangle(rect.Left - 15, rect.Top + m_nItemHeight / 2 - 5, 11, 10);
g.DrawRectangle(Pens.Gray, rect_box);
g.DrawLine(Pens.Cyan, rect_box.X - 2, rect_box.Top, rect_box.X + 2, rect_box.Top);
g.DrawLine(Pens.Cyan, rect_box.X, rect_box.Y - 2, rect_box.X, rect_box.Y + 2);
g.DrawLine(Pens.Cyan, rect_box.Right - 2, rect_box.Bottom, rect_box.Right + 2, rect_box.Bottom);
g.DrawLine(Pens.Cyan, rect_box.Right, rect_box.Bottom - 2, rect_box.Right, rect_box.Bottom + 2);
} else {
g.DrawRectangle(Pens.Goldenrod, new Rectangle(rect.Left - 16, rect.Top + m_nItemHeight / 2 - 6, 8, 3));
g.DrawRectangle(Pens.Goldenrod, new Rectangle(rect.Left - 16, rect.Top + m_nItemHeight / 2 - 3, 13, 9));
}
if (!this._ShowFolderCount) return;
m_sf.Alignment = StringAlignment.Far;
m_brush.Color = this._FolderCountColor;
rect.X -= 4;
g.DrawString("[" + items.STNodeCount.ToString() + "]", this.Font, m_brush, rect, m_sf);
m_sf.Alignment = StringAlignment.Near;
}
}
#endregion
#region public method ==========
/// <summary>
/// Retrieve the STNode in the control
/// </summary>
/// <param name="strText">Text to retrieve</param>
public void Search(string strText) {
if (strText == null) return;
if (strText.Trim() == string.Empty) return;
m_tbx.Text = strText.Trim ();
}
/// <summary>
/// Add a STNode type to the control
/// </summary>
/// <param name="stNodeType">STNode type</param>
/// <returns>Whether the addition is successful</returns>
public bool AddNode(Type stNodeType) { return this.AddSTNode(stNodeType, m_items_source, null, true); }
/// <summary>
/// Add the STNode type to the control from the file
/// </summary>
/// <param name="strFile">Specify file path</param>
/// <returns>Add success number</returns>
public int LoadAssembly(string strFile) {
strFile = System.IO.Path.GetFullPath(strFile);
var items = this.AddAssemblyPrivate(strFile);
if (items.STNodeCount == 0) return 0;
items.IsLibraryRoot = true;
m_items_source[items.Name] = items;
return items.STNodeCount;
}
/// <summary>
/// Clear all STNode types in the control
/// </summary>
public void Clear() {
m_items_source.Clear();
m_items_draw.Clear();
m_dic_all_type.Clear();
this.Invalidate();
}
/// <summary>
/// Remove an STNode type from the control
/// </summary>
/// <param name="stNodeType">STNode type</param>
/// <returns>Whether the removal is successful</returns>
public bool RemoveNode(Type stNodeType) {
if (!m_dic_all_type.ContainsKey(stNodeType)) return false;
string strPath = m_dic_all_type[stNodeType];
STNodeTreeCollection items = m_items_source;
if (!string.IsNullOrEmpty(strPath)) {
string[] strKeys = strPath.Split(m_chr_splitter);
for (int i = 0; i < strKeys.Length; i++) {
items = items[strKeys[i]];
if (items == null) return false;
}
}
try {
STNode node = (STNode)Activator.CreateInstance(stNodeType);
if (items[node.Title] == null) return false;
items.Remove(node.Title, true);
m_dic_all_type.Remove(stNodeType);
} catch { return false; }
this.Invalidate();
return true;
}
#endregion
//=================================================================================================
/// <summary>
/// A collection of each item in the STNodeTreeView control
/// </summary>
protected class STNodeTreeCollection : IEnumerable
{
private string _Name;
/// <summary>
/// Get the current tree node display name
/// </summary>
public string Name {
get {
return _Name;
}
}
/// <summary>
/// Get the lowercase string of the display name of the current tree node
/// </summary>
public string NameLower { get; private set; }
/// <summary>
/// Get the STNode type corresponding to the current tree node
/// </summary>
public Type STNodeType { get; internal set; }
/// <summary>
/// Get the parent tree node of the current tree node
/// </summary>
public STNodeTreeCollection Parent { get; internal set; }
/// <summary>
/// Get the number of STNode types owned by the current tree node
/// </summary>
public int STNodeCount { get; internal set; }
/// <summary>
/// Get the corresponding path of the STNode type corresponding to the current tree node in the tree control
/// </summary>
public string Path { get; internal set; }
/// <summary>
/// Get the current or set whether the tree node is open
/// </summary>
public bool IsOpen { get; set; }
/// <summary>
/// Get whether the current tree node is the root node of the loaded module
/// </summary>
public bool IsLibraryRoot { get; internal set; }
/// <summary>
/// Get the display area of the current tree node in the control
/// </summary>
public Rectangle DisplayRectangle { get; internal set; }
/// <summary>
/// Get the switch button area of the current tree node in the control
/// </summary>
public Rectangle SwitchRectangle { get; internal set; }
/// <summary>
/// Get the information button area of the current tree node in the control
/// </summary>
public Rectangle InfoRectangle { get; internal set; }
/// <summary>
/// Get the title color of the STNode type corresponding to the current tree node
/// </summary>
public Color STNodeTypeColor { get; internal set; }
/// <summary>
/// Get the number of child nodes contained in the current tree node
/// </summary>
public int Count { get { return m_dic.Count; } }
/// <summary>
/// Gets or sets the collection with the specified name
/// </summary>
/// <param name="strKey">specify name</param>
/// <returns>Collection</returns>
public STNodeTreeCollection this[string strKey] {
get {
if (string.IsNullOrEmpty(strKey)) return null;
if (m_dic.ContainsKey(strKey)) return m_dic[strKey];
return null;
}
set {
if (string.IsNullOrEmpty(strKey)) return;
if (value == null) return;
if (m_dic.ContainsKey(strKey)) {
m_dic[strKey] = value;
} else {
m_dic.Add(strKey, value);
}
value.Parent = this;
}
}
private SortedDictionary<string, STNodeTreeCollection> m_dic = new SortedDictionary<string, STNodeTreeCollection>();
/// <summary>
/// Construct a tree node collection
/// </summary>
/// <param name="strName">Display name of the current tree node in the control</param>
public STNodeTreeCollection(string strName) {
if (strName == null || strName.Trim() == string.Empty)
throw new ArgumentNullException("Display name cannot be null");
this._Name = strName.Trim();
this.NameLower = this._Name.ToLower();
}
/// <summary>
/// Add a child node to the current tree node
/// </summary>
/// <param name="strName">node display name</param>
/// <returns>Added set of child nodes</returns>
public STNodeTreeCollection Add(string strName) {
if (!m_dic.ContainsKey(strName))
m_dic.Add(strName, new STNodeTreeCollection(strName) { Parent = this });
return m_dic[strName];
}
/// <summary>
/// Delete a subcollection from the current tree node
/// </summary>
/// <param name="strName">Subcollection name</param>
/// <param name="isAutoDelFolder">Whether to automatically clear useless nodes recursively upwards</param>
/// <returns>Whether the deletion is successful</returns>
public bool Remove(string strName, bool isAutoDelFolder) {
if (!m_dic.ContainsKey(strName)) return false;
bool b = m_dic.Remove(strName);
var temp = this;
while (temp != null) {
temp.STNodeCount--;
temp = temp.Parent;
}
if (isAutoDelFolder && m_dic.Count == 0 && this.Parent != null)
return b && this.Parent.Remove(this.Name, isAutoDelFolder);
return b;
}
/// <summary>
/// Clear all child nodes in the current tree node
/// </summary>
public void Clear() { this.Clear(this); }
private void Clear(STNodeTreeCollection items) {
foreach (STNodeTreeCollection v in items) v.Clear(v);
m_dic.Clear();
}
/// <summary>
/// Get all the name arrays in the current tree node
/// </summary>
/// <returns></returns>
public string[] GetKeys() { return m_dic.Keys.ToArray(); }
/// <summary>
/// Copy all data in the current tree node set
/// </summary>
/// <returns>Copy copy</returns>
public STNodeTreeCollection Copy() {
STNodeTreeCollection items = new STNodeTreeCollection("COPY");
this.Copy(this, items);
return items;
}
private void Copy(STNodeTreeCollection items_src, STNodeTreeCollection items_dst) {
foreach (STNodeTreeCollection v in items_src) {
this.Copy(v, items_dst.Add(v.Name));
}
items_dst.Path = items_src.Path;
items_dst.STNodeType = items_src.STNodeType;
items_dst.IsLibraryRoot = items_src.IsLibraryRoot;
items_dst.STNodeCount = items_src.STNodeCount;
items_dst.STNodeTypeColor = items_src.STNodeTypeColor;
}
/// <summary>
/// Returns an Array of System.Collections.IEnumerator
/// </summary>
/// <returns></returns>
public IEnumerator GetEnumerator() {
foreach (var v in m_dic.Values) yield return v;
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
}
}
}