// COPYRIGHT (C) Tom. ALL RIGHTS RESERVED.
// THE AntdUI PROJECT IS AN WINFORM LIBRARY LICENSED UNDER THE Apache-2.0 License.
// LICENSED UNDER THE Apache License, VERSION 2.0 (THE "License")
// YOU MAY NOT USE THIS FILE EXCEPT IN COMPLIANCE WITH THE License.
// YOU MAY OBTAIN A COPY OF THE LICENSE AT
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, SOFTWARE
// DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
// SEE THE LICENSE FOR THE SPECIFIC LANGUAGE GOVERNING PERMISSIONS AND
// LIMITATIONS UNDER THE License.
// GITEE: https://gitee.com/antdui/AntdUI
// GITHUB: https://github.com/AntdUI/AntdUI
// CSDN: https://blog.csdn.net/v_132
// QQ: 17379620
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms;
namespace AntdUI
{
///
/// Tree 树形控件
///
/// 多层次的结构列表。
[Description("Tree 树形控件")]
[ToolboxItem(true)]
[DefaultProperty("Items")]
[DefaultEvent("SelectChanged")]
public class Tree : IControl
{
#region 属性
///
/// 悬停背景颜色
///
[Description("悬停背景颜色"), Category("外观"), DefaultValue(null)]
[Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))]
public Color? BackHover { get; set; }
///
/// 激活背景颜色
///
[Description("激活背景颜色"), Category("外观"), DefaultValue(null)]
[Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))]
public Color? BackActive { get; set; }
Color? fore;
///
/// 文字颜色
///
[Description("文字颜色"), Category("外观"), DefaultValue(null)]
[Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))]
public new Color? ForeColor
{
get => fore;
set
{
if (fore == value) fore = value;
fore = value;
Invalidate();
}
}
///
/// 激活字体颜色
///
[Description("激活字体颜色"), Category("外观"), DefaultValue(null)]
[Editor(typeof(Design.ColorEditor), typeof(UITypeEditor))]
public Color? ForeActive { get; set; }
int radius = 6;
///
/// 圆角
///
[Description("圆角"), Category("外观"), DefaultValue(6)]
public int Radius
{
get => radius;
set
{
if (radius == value) return;
radius = value;
Invalidate();
}
}
int _gap = 8;
///
/// 间距
///
[Description("间距"), Category("外观"), DefaultValue(8)]
public int Gap
{
get => _gap;
set
{
if (_gap == value) return;
_gap = value;
ChangeList();
Invalidate();
}
}
bool round = false;
///
/// 圆角样式
///
[Description("圆角样式"), Category("外观"), DefaultValue(false)]
public bool Round
{
get => round;
set
{
if (round == value) return;
round = value;
Invalidate();
}
}
bool checkable = false;
///
/// 节点前添加 Checkbox 复选框
///
[Description("节点前添加 Checkbox 复选框"), Category("外观"), DefaultValue(false)]
public bool Checkable
{
get => checkable;
set
{
if (checkable == value) return;
checkable = value;
ChangeList();
Invalidate();
}
}
///
/// Checkable 状态下节点选择完全受控(父子节点选中状态不再关联)
///
[Description("Checkable 状态下节点选择完全受控(父子节点选中状态不再关联)"), Category("行为"), DefaultValue(true)]
public bool CheckStrictly { get; set; } = true;
bool blockNode = false;
///
/// 节点占据一行
///
[Description("节点占据一行"), Category("外观"), DefaultValue(false)]
public bool BlockNode
{
get => blockNode;
set
{
if (blockNode == value) return;
blockNode = value;
ChangeList();
Invalidate();
}
}
#region 集合操作
///
/// 展开全部
///
/// true 展开、false 收起
public void ExpandAll(bool value = true)
{
if (items != null && items.Count > 0) ExpandAll(items, value);
}
public void ExpandAll(TreeItemCollection items, bool value = true)
{
if (items != null && items.Count > 0)
{
foreach (var it in items)
{
it.Expand = value;
ExpandAll(it.Sub, value);
}
}
}
#region 获取项
///
/// 获取所有选中项
///
/// 是否包含 Indeterminate
public List GetCheckeds(bool Indeterminate = true)
{
if (items == null) return new List(0);
return GetCheckeds(items, Indeterminate);
}
List GetCheckeds(TreeItemCollection items, bool Indeterminate)
{
var list = new List();
if (Indeterminate)
{
foreach (var it in items)
{
if (it.CheckState != CheckState.Unchecked) list.Add(it);
if (it.items != null && it.items.Count > 0)
{
var list_sub = GetCheckeds(it.items, Indeterminate);
if (list_sub.Count > 0) list.AddRange(list_sub);
}
}
}
else
{
foreach (var it in items)
{
if (it.Checked) list.Add(it);
if (it.items != null && it.items.Count > 0)
{
var list_sub = GetCheckeds(it.items, Indeterminate);
if (list_sub.Count > 0) list.AddRange(list_sub);
}
}
}
return list;
}
///
/// 全选/全不选
///
public void SetCheckeds()
{
if (items == null) return;
var list = GetCheckeds();
SetCheckeds(list.Count == 0);
}
///
/// 全选/全不选
///
public void SetCheckeds(bool check)
{
if (items == null) return;
SetCheckeds(items, check);
}
void SetCheckeds(TreeItemCollection items, bool check)
{
foreach (var it in items)
{
it.Checked = check;
if (it.items != null && it.items.Count > 0) SetCheckeds(it.items, check);
}
}
#endregion
#region 事件
///
/// Select 属性值更改时发生
///
[Description("Select 属性值更改时发生"), Category("行为")]
public event TreeSelectEventHandler? SelectChanged = null;
///
/// Checked 属性值更改时发生
///
[Description("Checked 属性值更改时发生"), Category("行为")]
public event TreeCheckedEventHandler? CheckedChanged = null;
///
/// 点击项事件
///
[Description("点击项事件"), Category("行为")]
public event TreeSelectEventHandler? NodeMouseClick = null;
///
/// 双击项事件
///
[Description("双击项事件"), Category("行为")]
public event TreeSelectEventHandler? NodeMouseDoubleClick = null;
///
/// 移动项事件
///
[Description("移动项事件"), Category("行为")]
public event TreeHoverEventHandler? NodeMouseMove = null;
internal void OnNodeMouseMove(TreeItem item, bool hover)
{
if (NodeMouseMove == null) return;
int sx = ScrollBar.ValueX, sy = ScrollBar.ValueY;
NodeMouseMove(this, new TreeHoverEventArgs(item, new Rectangle(item.txt_rect.X, item.txt_rect.Y - sy, item.txt_rect.Width, item.txt_rect.Height), hover));
}
internal void OnSelectChanged(TreeItem item, MouseEventArgs args)
{
if (SelectChanged == null) return;
int sx = ScrollBar.ValueX, sy = ScrollBar.ValueY;
SelectChanged(this, new TreeSelectEventArgs(item, new Rectangle(item.txt_rect.X, item.txt_rect.Y - sy, item.txt_rect.Width, item.txt_rect.Height), args));
}
internal void OnNodeMouseClick(TreeItem item, MouseEventArgs args)
{
if (NodeMouseClick == null) return;
int sx = ScrollBar.ValueX, sy = ScrollBar.ValueY;
NodeMouseClick(this, new TreeSelectEventArgs(item, new Rectangle(item.txt_rect.X, item.txt_rect.Y - sy, item.txt_rect.Width, item.txt_rect.Height), args));
}
internal void OnNodeMouseDoubleClick(TreeItem item, MouseEventArgs args)
{
if (NodeMouseDoubleClick == null) return;
int sx = ScrollBar.ValueX, sy = ScrollBar.ValueY;
NodeMouseDoubleClick(this, new TreeSelectEventArgs(item, new Rectangle(item.txt_rect.X, item.txt_rect.Y - sy, item.txt_rect.Width, item.txt_rect.Height), args));
}
internal void OnCheckedChanged(TreeItem item, bool value)
{
CheckedChanged?.Invoke(this, new TreeCheckedEventArgs(item, value));
}
#endregion
#endregion
TreeItemCollection? items;
///
/// 集合
///
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Description("集合"), Category("数据")]
public TreeItemCollection Items
{
get
{
items ??= new TreeItemCollection(this);
return items;
}
set => items = value.BindData(this);
}
bool pauseLayout = false;
[Browsable(false), Description("暂停布局"), Category("行为"), DefaultValue(false)]
public bool PauseLayout
{
get => pauseLayout;
set
{
if (pauseLayout == value) return;
pauseLayout = value;
if (!value)
{
ChangeList();
Invalidate();
}
}
}
///
/// 滚动条
///
[Browsable(false)]
public ScrollBar ScrollBar;
#endregion
#region 布局
protected override void OnSizeChanged(EventArgs e)
{
var rect = ChangeList();
ScrollBar.SizeChange(rect);
base.OnSizeChanged(e);
}
internal Rectangle ChangeList()
{
var rect = ClientRectangle;
if (pauseLayout || items == null || items.Count == 0 || (rect.Width == 0 || rect.Height == 0)) return rect;
int x = 0, y = 0;
bool has = HasSub(items);
Helper.GDI(g =>
{
var size = g.MeasureString(Config.NullText, Font);
int icon_size = (int)(size.Height), gap = (int)(_gap * Config.Dpi), gapI = gap / 2, height = icon_size + gap * 2;
check_radius = icon_size * .2F;
if (CheckStrictly && has && items[0].PARENT == null && items[0].PARENTITEM == null)
{
//新数据
var dir = new List();
TestSub(ref dir, items);
foreach (var item in dir)
{
int check_count = 0;
foreach (var sub in item.Sub)
{ if (sub.CheckState == CheckState.Checked || sub.CheckState == CheckState.Indeterminate) check_count++; }
if (check_count > 0) item.CheckState = check_count == item.Sub.Count ? CheckState.Checked : CheckState.Indeterminate;
else item.CheckState = CheckState.Unchecked;
}
}
ChangeList(g, rect, null, items, has, ref x, ref y, height, icon_size, gap, gapI, 0, true);
});
ScrollBar.SetVrSize(x, y);
return rect;
}
bool HasSub(TreeItemCollection items)
{
foreach (var it in items)
{
if (it.CanExpand) return true;
}
return false;
}
void TestSub(ref List dir, TreeItemCollection items)
{
foreach (var it in items)
{
if (it.CanExpand)
{
dir.Insert(0, it);
TestSub(ref dir, it.Sub);
}
}
}
void ChangeList(Graphics g, Rectangle rect, TreeItem? Parent, TreeItemCollection items, bool has_sub, ref int x, ref int y, int height, int icon_size, int gap, int gapI, int depth, bool expand)
{
foreach (var it in items)
{
it.PARENT = this;
it.PARENTITEM = Parent;
it.SetRect(g, Font, depth, checkable, blockNode, has_sub, new Rectangle(0, y, rect.Width, height), icon_size, gap);
if (expand && it.txt_rect.Right > x) x = it.txt_rect.Right;
if (it.Show && it.Visible)
{
y += height + gapI;
if (it.CanExpand)
{
int y_item = y;
ChangeList(g, rect, it, it.Sub, has_sub, ref x, ref y, height, icon_size, gap, gapI, depth + 1, expand ? it.Expand : false);
it.SubY = y_item - gapI / 2;
it.SubHeight = y - y_item;
if ((it.Expand || it.ExpandThread) && it.ExpandProg > 0)
{
it.ExpandHeight = y - y_item;
y = y_item + (int)(it.ExpandHeight * it.ExpandProg);
}
else if (!it.Expand) y = y_item;
}
}
}
}
#endregion
#region 渲染
float check_radius = 0F;
public Tree()
{
ScrollBar = new ScrollBar(this, true, true);
}
protected override void OnPaint(PaintEventArgs e)
{
if (items == null || items.Count == 0) return;
var rect = ClientRectangle;
if (rect.Width == 0 || rect.Height == 0) return;
var g = e.Graphics.High();
int sx = ScrollBar.ValueX, sy = ScrollBar.ValueY;
g.TranslateTransform(-sx, -sy);
float _radius = radius * Config.Dpi;
using (var brush_fore = new SolidBrush(fore ?? Style.Db.TextBase))
using (var brush_fore_active = new SolidBrush(ForeActive ?? Style.Db.Primary))
using (var brush_hover = new SolidBrush(BackHover ?? Style.Db.FillSecondary))
using (var brush_active = new SolidBrush(BackActive ?? Style.Db.PrimaryBg))
using (var brush_TextTertiary = new SolidBrush(Style.Db.TextTertiary))
{
PaintItem(g, rect, sx, sy, items, brush_fore, brush_fore_active, brush_hover, brush_active, brush_TextTertiary, _radius);
}
g.ResetTransform();
ScrollBar.Paint(g);
this.PaintBadge(g);
base.OnPaint(e);
}
void PaintItem(Graphics g, Rectangle rect, int sx, int sy, TreeItemCollection items, SolidBrush fore, SolidBrush fore_active, SolidBrush hover, SolidBrush active, SolidBrush brushTextTertiary, float radius)
{
foreach (var it in items)
{
it.show = it.Show && it.Visible && it.rect.Y > sy - rect.Height - (it.Expand ? it.SubHeight : 0) && it.rect.Bottom < sy + rect.Height + it.rect.Height;
if (it.show)
{
PaintItem(g, it, fore, fore_active, hover, active, brushTextTertiary, radius, sx, sy);
if ((it.Expand || it.ExpandThread) && it.items != null && it.items.Count > 0)
{
var state = g.Save();
if (it.ExpandThread) g.SetClip(new RectangleF(rect.X, it.rect.Bottom, rect.Width, it.ExpandHeight * it.ExpandProg));
PaintItem(g, rect, sx, sy, it.items, fore, fore_active, hover, active, brushTextTertiary, radius);
g.Restore(state);
}
}
}
}
readonly StringFormat s_c = Helper.SF_Ellipsis(), s_l = Helper.SF_ALL(lr: StringAlignment.Near);
void PaintItem(Graphics g, TreeItem item, SolidBrush fore, SolidBrush fore_active, SolidBrush hover, SolidBrush active, SolidBrush brushTextTertiary, float radius, int sx, int sy)
{
if (item.Select)
{
if (blockNode)
{
g.ResetTransform();
g.TranslateTransform(0, -sy);
PaintBack(g, active, item.rect, radius);
g.TranslateTransform(-sx, 0);
}
else PaintBack(g, active, item.rect, radius);
if (item.CanExpand) PaintArrow(g, item, fore_active, sx, sy);
PaintItemText(g, item, fore_active, brushTextTertiary);
}
else
{
if (item.Back.HasValue)
{
using (var brush = new SolidBrush(item.Back.Value))
{
PaintBack(g, brush, item.rect, radius);
}
}
if (blockNode)
{
g.ResetTransform();
g.TranslateTransform(0, -sy);
if (item.AnimationHover)
{
using (var brush = new SolidBrush(Helper.ToColorN(item.AnimationHoverValue, hover.Color)))
{
PaintBack(g, brush, item.rect, radius);
}
}
else if (item.Hover) PaintBack(g, hover, item.rect, radius);
g.TranslateTransform(-sx, 0);
}
else
{
if (item.AnimationHover)
{
using (var brush = new SolidBrush(Helper.ToColorN(item.AnimationHoverValue, hover.Color)))
{
PaintBack(g, brush, item.rect, radius);
}
}
else if (item.Hover) PaintBack(g, hover, item.rect, radius);
}
if (item.CanExpand) PaintArrow(g, item, fore, sx, sy);
if (item.Enabled) PaintItemText(g, item, fore, brushTextTertiary);
else
{
using (var brush = new SolidBrush(Style.Db.TextQuaternary))
{
PaintItemText(g, item, brush, brushTextTertiary);
}
}
}
if (checkable)
{
using (var path_check = Helper.RoundPath(item.check_rect, check_radius, false))
{
if (item.Enabled)
{
if (item.AnimationCheck)
{
var alpha = 255 * item.AnimationCheckValue;
if (item.CheckState == CheckState.Indeterminate || (item.checkStateOld == CheckState.Indeterminate && !item.Checked))
{
using (var brush = new Pen(Style.Db.BorderColor, 2F))
{
g.DrawPath(brush, path_check);
}
using (var brush = new SolidBrush(Helper.ToColor(alpha, Style.Db.Primary)))
{
g.FillRectangle(brush, PaintBlock(item.check_rect));
}
}
else
{
float dot = item.check_rect.Width * 0.3F;
using (var brush = new SolidBrush(Helper.ToColor(alpha, Style.Db.Primary)))
{
g.FillPath(brush, path_check);
}
using (var brush = new Pen(Helper.ToColor(alpha, Style.Db.BgBase), 3F))
{
g.DrawLines(brush, PaintArrow(item.check_rect));
}
if (item.Checked)
{
float max = item.check_rect.Height + item.check_rect.Height * item.AnimationCheckValue, alpha2 = 100 * (1F - item.AnimationCheckValue);
using (var brush = new SolidBrush(Helper.ToColor(alpha2, Style.Db.Primary)))
{
g.FillEllipse(brush, new RectangleF(item.check_rect.X + (item.check_rect.Width - max) / 2F, item.check_rect.Y + (item.check_rect.Height - max) / 2F, max, max));
}
}
using (var brush = new Pen(Style.Db.Primary, 2F))
{
g.DrawPath(brush, path_check);
}
}
}
else if (item.CheckState == CheckState.Indeterminate)
{
using (var brush = new Pen(Style.Db.BorderColor, 2F))
{
g.DrawPath(brush, path_check);
}
using (var brush = new SolidBrush(Style.Db.Primary))
{
g.FillRectangle(brush, PaintBlock(item.check_rect));
}
}
else if (item.Checked)
{
using (var brush = new SolidBrush(Style.Db.Primary))
{
g.FillPath(brush, path_check);
}
using (var brush = new Pen(Style.Db.BgBase, 3F))
{
g.DrawLines(brush, PaintArrow(item.check_rect));
}
}
else
{
using (var brush = new Pen(Style.Db.BorderColor, 2F))
{
g.DrawPath(brush, path_check);
}
}
}
else
{
using (var brush = new SolidBrush(Style.Db.FillQuaternary))
{
g.FillPath(brush, path_check);
}
if (item.CheckState == CheckState.Indeterminate)
{
using (var brush = new SolidBrush(Style.Db.TextQuaternary))
{
g.FillRectangle(brush, PaintBlock(item.check_rect));
}
}
else if (item.Checked)
{
using (var brush = new Pen(Style.Db.TextQuaternary, 3F))
{
g.DrawLines(brush, PaintArrow(item.check_rect));
}
}
using (var brush = new Pen(Style.Db.BorderColorDisable, 2F))
{
g.DrawPath(brush, path_check);
}
}
}
}
}
void PaintItemText(Graphics g, TreeItem item, SolidBrush fore, SolidBrush brushTextTertiary)
{
Color color = fore.Color;
if (item.Fore.HasValue)
{
color = item.Fore.Value;
using (var brush = new SolidBrush(color))
{
g.DrawStr(item.Text, Font, brush, item.txt_rect, blockNode ? s_l : s_c);
}
}
else g.DrawStr(item.Text, Font, fore, item.txt_rect, blockNode ? s_l : s_c);
if (item.SubTitle != null) g.DrawStr(item.SubTitle, Font, brushTextTertiary, item.subtxt_rect, s_l);
if (item.Icon != null) g.DrawImage(item.Icon, item.ico_rect);
if (item.IconSvg != null) g.GetImgExtend(item.IconSvg, item.ico_rect, color);
}
internal RectangleF PaintBlock(RectangleF rect)
{
float size = rect.Height * 0.2F, size2 = size * 2F;
return new RectangleF(rect.X + size, rect.Y + size, rect.Width - size2, rect.Height - size2);
}
internal PointF[] PaintArrow(RectangleF rect)
{
float size = rect.Height * 0.15F, size2 = rect.Height * 0.2F, size3 = rect.Height * 0.26F;
return new PointF[] {
new PointF(rect.X+size,rect.Y+rect.Height/2),
new PointF(rect.X+rect.Width*0.4F,rect.Y+(rect.Height-size3)),
new PointF(rect.X+rect.Width-size2,rect.Y+size2),
};
}
void PaintArrow(Graphics g, TreeItem item, SolidBrush color, int sx, int sy)
{
using (var pen = new Pen(color, 2F))
{
pen.StartCap = pen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
if (item.ExpandThread) PaintArrow(g, item, pen, sx, sy, -90F + (90F * item.ExpandProg));
else if (item.Expand) g.DrawLines(pen, item.arr_rect.TriangleLines(-1, .4F));
else PaintArrow(g, item, pen, sx, sy, -90F);
}
}
void PaintArrow(Graphics g, TreeItem item, Pen pen, int sx, int sy, float rotate)
{
int size_arrow = item.arr_rect.Width / 2;
g.TranslateTransform(item.arr_rect.X + size_arrow, item.arr_rect.Y + size_arrow);
g.RotateTransform(rotate);
g.DrawLines(pen, new Rectangle(-size_arrow, -size_arrow, item.arr_rect.Width, item.arr_rect.Height).TriangleLines(-1, .4F));
g.ResetTransform();
g.TranslateTransform(-sx, -sy);
}
void PaintBack(Graphics g, SolidBrush brush, Rectangle rect, float radius)
{
if (round || radius > 0)
{
using (var path = rect.RoundPath(radius, round))
{
g.FillPath(brush, path);
}
}
else g.FillRectangle(brush, rect);
}
#endregion
#region 鼠标
TreeItem? MDown = null;
bool doubleClick = false;
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
doubleClick = e.Clicks > 1;
MDown = null;
if (ScrollBar.MouseDownY(e.Location) && ScrollBar.MouseDownX(e.Location))
{
if (items == null || items.Count == 0) return;
OnTouchDown(e.X, e.Y);
foreach (var it in items)
{
if (IMouseDown(e, it)) return;
}
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (ScrollBar.MouseUpY() && ScrollBar.MouseUpX() && OnTouchUp())
{
if (items == null || items.Count == 0 || MDown == null) return;
foreach (var it in items)
{
if (IMouseUp(e, it, MDown)) return;
}
}
}
bool IMouseDown(MouseEventArgs e, TreeItem item)
{
int down = item.Contains(e.Location, blockNode ? 0 : ScrollBar.ValueX, ScrollBar.ValueY, checkable);
if (down > 0)
{
MDown = item;
return true;
}
if (item.CanExpand && item.Expand)
{
foreach (var sub in item.Sub)
{
if (IMouseDown(e, sub)) return true;
}
}
return false;
}
bool IMouseUp(MouseEventArgs e, TreeItem item, TreeItem MDown)
{
bool can = item.CanExpand;
if (MDown == item)
{
int down = item.Contains(e.Location, blockNode ? 0 : ScrollBar.ValueX, ScrollBar.ValueY, checkable);
if (down > 0)
{
if (doubleClick) OnNodeMouseDoubleClick(item, e);
else OnNodeMouseClick(item, e);
if (blockNode)
{
if (can) item.Expand = !item.Expand;
item.Select = true;
OnSelectChanged(item, e);
Invalidate();
}
else if (down == 3 && item.Enabled)
{
item.Checked = !item.Checked;
if (CheckStrictly)
{
SetCheck(item, item.Checked);
SetCheckStrictly(item.PARENTITEM);
}
}
else if (down == 2 && can) item.Expand = !item.Expand;
else
{
item.Select = true;
OnSelectChanged(item, e);
Invalidate();
}
}
return true;
}
if (can && item.Expand)
{
foreach (var sub in item.Sub)
{
if (IMouseUp(e, sub, MDown)) return true;
}
}
return false;
}
void SetCheck(TreeItem item, bool value)
{
if (item.items != null && item.items.Count > 0)
{
foreach (var it in item.items)
{
it.Checked = value;
SetCheck(it, value);
}
}
}
void SetCheckStrictly(TreeItem? item)
{
if (item == null) return;
int check_count = 0;
foreach (var sub in item.Sub)
{
if (sub.CheckState == CheckState.Checked || sub.CheckState == CheckState.Indeterminate) check_count++;
}
if (check_count > 0) item.CheckState = check_count == item.Sub.Count ? CheckState.Checked : CheckState.Indeterminate;
else item.CheckState = CheckState.Unchecked;
SetCheckStrictly(item.PARENTITEM);
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (ScrollBar.MouseMoveY(e.Location) && ScrollBar.MouseMoveX(e.Location))
{
if (OnTouchMove(e.X, e.Y))
{
if (items == null || items.Count == 0) return;
int hand = 0;
foreach (var it in items) IMouseMove(it, e.Location, ref hand);
SetCursor(hand > 0);
}
}
else ILeave();
}
void IMouseMove(TreeItem item, Point point, ref int hand)
{
if (item.show)
{
if (item.Contains(point, blockNode ? 0 : ScrollBar.ValueX, ScrollBar.ValueY, checkable) > 0) hand++;
if (item.items != null) foreach (var sub in item.items) IMouseMove(sub, point, ref hand);
}
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
ScrollBar.Leave();
ILeave();
}
protected override void OnLeave(EventArgs e)
{
base.OnLeave(e);
ScrollBar.Leave();
ILeave();
}
protected override void OnMouseWheel(MouseEventArgs e)
{
ScrollBar.MouseWheel(e.Delta);
base.OnMouseWheel(e);
}
protected override bool OnTouchScrollX(int value) => ScrollBar.MouseWheelX(value);
protected override bool OnTouchScrollY(int value) => ScrollBar.MouseWheelY(value);
void ILeave()
{
SetCursor(false);
if (items == null || items.Count == 0) return;
int count = 0;
foreach (var it in items)
{
ILeave(it, ref count);
}
if (count > 0) Invalidate();
}
void ILeave(TreeItem item, ref int count)
{
if (item.Hover) count++;
item.Hover = false;
if (item.items != null)
foreach (var sub in item.items) ILeave(sub, ref count);
}
internal void IUSelect()
{
if (items == null || items.Count == 0) return;
foreach (var it in items) IUSelect(it);
}
void IUSelect(TreeItem item)
{
item.Select = false;
if (item.items != null) foreach (var sub in item.items) IUSelect(sub);
}
#endregion
protected override void Dispose(bool disposing)
{
ScrollBar.Dispose();
base.Dispose(disposing);
}
}
public class TreeItemCollection : iCollection
{
public TreeItemCollection(Tree it)
{
BindData(it);
}
public TreeItemCollection(TreeItem it)
{
BindData(it);
}
internal TreeItemCollection BindData(Tree it)
{
action = render =>
{
if (render) it.ChangeList();
it.Invalidate();
};
return this;
}
internal TreeItemCollection BindData(TreeItem it)
{
action = render =>
{
if (it.PARENT == null) return;
if (render) it.PARENT.ChangeList();
it.PARENT.Invalidate();
};
return this;
}
}
public class TreeItem : NotifyProperty
{
public TreeItem() { }
public TreeItem(string text)
{
Text = text;
}
public TreeItem(string text, Image? icon)
{
Text = text;
Icon = icon;
}
Image? icon = null;
///
/// 图标
///
[Description("图标"), Category("外观"), DefaultValue(null)]
public Image? Icon
{
get => icon;
set
{
if (icon == value) return;
icon = value;
OnPropertyChanged("Icon");
}
}
string? iconSvg = null;
///
/// 图标
///
[Description("图标SVG"), Category("外观"), DefaultValue(null)]
public string? IconSvg
{
get => iconSvg;
set
{
if (iconSvg == value) return;
iconSvg = value;
OnPropertyChanged("IconSvg");
}
}
///
/// 是否包含图片
///
internal bool HasIcon
{
get => iconSvg != null || Icon != null;
}
///
/// 名称
///
[Description("名称"), Category("数据"), DefaultValue(null)]
public string? Name { get; set; }
string? text = null;
///
/// 文本
///
[Description("文本"), Category("外观"), DefaultValue(null)]
public string? Text
{
get => text;
set
{
if (text == value) return;
text = value;
Invalidates();
}
}
string? subTitle = null;
///
/// 子标题
///
[Description("子标题"), Category("外观"), DefaultValue(null)]
public string? SubTitle
{
get => subTitle;
set
{
if (string.IsNullOrEmpty(value)) value = null;
if (subTitle == value) return;
subTitle = value;
Invalidates();
}
}
bool visible = true;
///
/// 是否显示
///
[Description("是否显示"), Category("外观"), DefaultValue(true)]
public bool Visible
{
get => visible;
set
{
if (visible == value) return;
visible = value;
Invalidates();
}
}
///
/// 用户定义数据
///
[Description("用户定义数据"), Category("数据"), DefaultValue(null)]
public object? Tag { get; set; }
internal TreeItemCollection? items;
///
/// 获取列表中所有列表项的集合
///
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Description("子集合"), Category("外观")]
public TreeItemCollection Sub
{
get
{
items ??= new TreeItemCollection(this);
return items;
}
set => items = value.BindData(this);
}
#region 禁用
bool enabled = true;
///
/// 禁掉响应
///
[Description("禁掉响应"), Category("行为"), DefaultValue(true)]
public bool Enabled
{
get => enabled;
set
{
if (enabled == value) return;
enabled = value;
Invalidate();
}
}
#endregion
#region 展开
ITask? ThreadExpand = null;
bool expand = false;
///
/// 展开
///
[Description("展开"), Category("行为"), DefaultValue(false)]
public bool Expand
{
get => expand;
set
{
if (expand == value) return;
expand = value;
if (items != null && items.Count > 0)
{
if (PARENT != null && PARENT.IsHandleCreated && Config.Animation)
{
ThreadExpand?.Dispose();
float oldval = -1;
if (ThreadExpand?.Tag is float oldv) oldval = oldv;
ExpandThread = true;
if (value)
{
int time = ExpandCount(this) * 10;
if (time < 100) time = 100;
else if (time > 1000) time = 1000;
int t = Animation.TotalFrames(10, time);
ThreadExpand = new ITask(false, 10, t, oldval, AnimationType.Ball, (i, val) =>
{
ExpandProg = val;
Invalidates();
}, () =>
{
ExpandProg = 1F;
ExpandThread = false;
Invalidates();
});
}
else
{
var t = Animation.TotalFrames(10, 200);
ThreadExpand = new ITask(true, 10, t, oldval, AnimationType.Ball, (i, val) =>
{
ExpandProg = val;
Invalidates();
}, () =>
{
ExpandProg = 1F;
ExpandThread = false;
Invalidates();
});
}
}
else
{
ExpandProg = 1F;
Invalidates();
}
}
else
{
ExpandProg = 1F;
Invalidates();
}
}
}
internal int ExpandCount(TreeItem it)
{
int count = 0;
if (it.items != null && it.items.Count > 0)
{
count += it.items.Count;
foreach (var item in it.items)
{
if (item.Expand) count += ExpandCount(item);
}
}
return count;
}
[Description("是否可以展开"), Category("行为"), DefaultValue(false)]
public bool CanExpand
{
get => visible && items != null && items.Count > 0;
}
#endregion
#region 选中状态
internal bool AnimationCheck = false;
internal float AnimationCheckValue = 0;
//ITask? ThreadHover = null;
ITask? ThreadCheck = null;
bool _checked = false;
[Description("选中状态"), Category("行为"), DefaultValue(false)]
public bool Checked
{
get => _checked;
set
{
if (_checked == value) return;
_checked = value;
PARENT?.OnCheckedChanged(this, value);
OnCheck();
CheckState = value ? CheckState.Checked : CheckState.Unchecked;
}
}
internal CheckState checkStateOld = CheckState.Unchecked;
CheckState checkState = CheckState.Unchecked;
[Description("选中状态"), Category("行为"), DefaultValue(CheckState.Unchecked)]
public CheckState CheckState
{
get => checkState;
set
{
if (checkState == value) return;
checkState = value;
bool __checked = value == CheckState.Checked;
if (_checked != __checked)
{
_checked = __checked;
PARENT?.OnCheckedChanged(this, __checked);
OnCheck();
}
if (value != CheckState.Unchecked) checkStateOld = value;
}
}
void OnCheck()
{
ThreadCheck?.Dispose();
if (PARENT != null && PARENT.IsHandleCreated && (PARENTITEM == null || PARENTITEM.expand) && show && Config.Animation)
{
AnimationCheck = true;
if (_checked)
{
ThreadCheck = new ITask(PARENT, () =>
{
AnimationCheckValue = AnimationCheckValue.Calculate(0.2F);
if (AnimationCheckValue > 1) { AnimationCheckValue = 1F; return false; }
Invalidate();
return true;
}, 20, () =>
{
AnimationCheck = false;
Invalidate();
});
}
else
{
ThreadCheck = new ITask(PARENT, () =>
{
AnimationCheckValue = AnimationCheckValue.Calculate(-0.2F);
if (AnimationCheckValue <= 0) { AnimationCheckValue = 0F; return false; }
Invalidate();
return true;
}, 20, () =>
{
AnimationCheck = false;
Invalidate();
});
}
}
else AnimationCheckValue = _checked ? 1F : 0F;
Invalidate();
}
#endregion
#region 样式
Color? fore;
///
/// 文本颜色
///
[Description("文本颜色"), Category("外观"), DefaultValue(null)]
public Color? Fore
{
get => fore;
set
{
if (fore == value) return;
fore = value;
Invalidate();
}
}
Color? back;
///
/// 背景颜色
///
[Description("背景颜色"), Category("外观"), DefaultValue(null)]
public Color? Back
{
get => back;
set
{
if (back == value) return;
back = value;
Invalidate();
}
}
#endregion
void Invalidate()
{
PARENT?.Invalidate();
}
void Invalidates()
{
if (PARENT == null) return;
PARENT.ChangeList();
PARENT.Invalidate();
}
internal float SubY { get; set; }
internal float SubHeight { get; set; }
internal float ExpandHeight { get; set; }
internal float ExpandProg { get; set; }
internal bool ExpandThread { get; set; }
internal bool show { get; set; }
internal bool Show { get; set; }
bool hover = false;
///
/// 是否移动
///
internal bool Hover
{
get => hover;
set
{
if (hover == value) return;
hover = value;
PARENT?.OnNodeMouseMove(this, value);
if (Config.Animation)
{
ThreadHover?.Dispose();
AnimationHover = true;
var t = Animation.TotalFrames(20, 200);
if (value)
{
ThreadHover = new ITask((i) =>
{
AnimationHoverValue = Animation.Animate(i, t, 1F, AnimationType.Ball);
Invalidate();
return true;
}, 20, t, () =>
{
AnimationHover = false;
AnimationHoverValue = 1;
Invalidate();
});
}
else
{
ThreadHover = new ITask((i) =>
{
AnimationHoverValue = 1F - Animation.Animate(i, t, 1F, AnimationType.Ball);
Invalidate();
return true;
}, 20, t, () =>
{
AnimationHover = false;
AnimationHoverValue = 0;
Invalidate();
});
}
}
else Invalidate();
}
}
bool select = false;
[Description("激活状态"), Category("行为"), DefaultValue(false)]
public bool Select
{
get => select;
set
{
if (select == value) return;
if (value) PARENT?.IUSelect();
select = value;
Invalidate();
}
}
public int Depth { get; private set; }
internal Tree? PARENT { get; set; }
public TreeItem? PARENTITEM { get; set; }
internal void SetRect(Graphics g, Font font, int depth, bool checkable, bool blockNode, bool has_sub, Rectangle _rect, int icon_size, int gap)
{
Depth = depth;
rect = _rect;
int x = _rect.X + gap + (icon_size * depth), y = _rect.Y + (_rect.Height - icon_size) / 2;
if (has_sub)
{
arr_rect = new Rectangle(x, y, icon_size, icon_size);
x += icon_size + gap;
}
if (checkable)
{
check_rect = new Rectangle(x, y, icon_size, icon_size);
x += icon_size + gap;
}
if (HasIcon)
{
ico_rect = new Rectangle(x, y, icon_size, icon_size);
x += icon_size + gap;
}
var size = g.MeasureString(Text, font).Size();
txt_rect = new Rectangle(x, _rect.Y + (_rect.Height - size.Height) / 2, size.Width, size.Height);
if (SubTitle != null)
{
var sizesub = g.MeasureString(SubTitle, font).Size();
subtxt_rect = new Rectangle(txt_rect.Right, txt_rect.Y, sizesub.Width, txt_rect.Height);
if (!blockNode) rect = new Rectangle(txt_rect.X, txt_rect.Y, txt_rect.Width + subtxt_rect.Width, subtxt_rect.Height);
}
else if (!blockNode) rect = txt_rect;
Show = true;
}
internal Rectangle rect { get; set; }
internal Rectangle arr_rect { get; set; }
internal int Contains(Point point, int x, int y, bool checkable)
{
var p = new Point(point.X + x, point.Y + y);
if (rect.Contains(p))
{
Hover = true;
return 1;
}
else if (arr_rect.Contains(p) && CanExpand)
{
Hover = rect.Contains(arr_rect);
return 2;
}
else if (checkable && check_rect.Contains(p))
{
Hover = rect.Contains(arr_rect);
return 3;
}
else
{
Hover = false;
return 0;
}
}
internal float AnimationHoverValue = 0;
internal bool AnimationHover = false;
ITask? ThreadHover = null;
internal Rectangle check_rect { get; set; }
internal Rectangle txt_rect { get; set; }
internal Rectangle subtxt_rect { get; set; }
internal Rectangle ico_rect { get; set; }
public override string? ToString() => text;
}
}