C# winform 自绘折叠左侧菜单

C# winform 自绘折叠左侧菜单

说明

最近无聊花了2天时间写了一个左侧菜单控件,测试648万条数据,数据占内存2.9GB,滚动数据无任何卡顿。
控件是完全绘制的,没有叠加其他的控件。因为只是简单写了一些,所以功能上可能不是很完善,需要自己修改,如果没有能力修改的,那就只能这样使用了。
刚大概浏览了下代码,由于写出来有几天时间了,当时还写了一个功能是:选择框模式,即可以像TreeView一样提供选择功能,功能虽然写了,但在绘制选择框图标时,会多次绘制图标,一直出现内存流的错误,这个没修改过来,就取消了这个功能删除了响应的代码。刚才浏览代码发现还有部分的未删除,但不影响控件的正常运行。
所有代码都在后面。

截图

在这里插入图片描述

代码

由于我不是专业的程序员,完全是自学的,写出的代码可能存在命名奇怪,单词错误等,请见谅。
在这里插入图片描述

ClickItemEventArgs.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
    public class ClickItemEventArgs : EventArgs
    {
        /// <summary>
        /// 点击的鼠标
        /// </summary>
        public MouseButtons Button { get; set; }

        /// <summary>
        /// 点击的项
        /// </summary>
        public TreeMenuItem TreeMenuItem { get; set; }

        public ClickItemEventArgs(MouseButtons buttons, TreeMenuItem item)
        {
            this.Button = buttons;  
            this.TreeMenuItem = item;
        }
    }
}
FoldingIcon.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
    /// <summary>
    /// 折叠项图标样式
    /// </summary>
    public enum FoldingIcon
    {
        None,
        /// <summary>
        /// 实心△图标
        /// </summary>
        Caret,
        /// <summary>
        /// 圆圈内有个">"的图标
        /// </summary>
        ChevronCircle,
        /// <summary>
        /// ">"符号图标
        /// </summary>
        Angle,
        /// <summary>
        /// ">"符号加粗图标
        /// </summary>
        Chevron,
    }

}

ItemStyle.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
    /// <summary>
    /// 列表项的样式
    /// </summary>
    public class ItemStyle
    {
        /// <summary>
        /// 列表项的级别
        /// </summary>
        public int Level { get; private set; } = 1;
        /// <summary>
        /// 列表项字体格式
        /// </summary>
        public Font Font { get; set; } = new Font("Microsoft YaHei UI", 9f, unit: GraphicsUnit.Point);

        /// <summary>
        /// 列表项字体颜色
        /// </summary>
        public Color ForeColor { get; set; } = Color.Black;

        /// <summary>
        /// 列表项背景颜色
        /// </summary>
        public Color BackColor { get; set; } = Color.Transparent;

        /// <summary>
        /// 圆形矩形的弧度
        /// <para>当<=0时,背景或选中都是矩形</para>
        /// </summary>
        public int Radius { get; set; } = 5;

        public ItemStyle(int level = 1)
        {
            Level = level;
        }

        public ItemStyle() 
        {
            Level = 1;
        }
    }
}
ListItem.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
    /// <summary>
    /// 重定义的列表泛型,此类主要是不能直接添加元素,不能直接删除元素
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ListItem<T>:IEnumerable<T>
    {
        private List<T> items = new List<T>();


        public T this[int index] { get => this.items[index]; }

        public int Count { get { return this.items.Count; } }

        /// <summary>
        /// 筛选数据
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public ListItem<T> Where(Func<T, bool> predicate)
        {
            return new ListItem<T>(this.items.Where(predicate).ToList());
        }

        /// <summary>
        /// ForEach 委托
        /// </summary>
        /// <param name="action"></param>
        public void ForEach(Action<T> action)
        {
            this.items.ForEach(action);
        }

        public ListItem() { }

        private ListItem(List<T> items)
        {
            this.items = items;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return this.items.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
TreeMenuItemCollection.cs 文件

namespace XiaoHeiControls.CollapseMenu
{
    public class TreeMenuItemCollection:IList<TreeMenuItem>
    {
        /// <summary>
        /// 定义数组
        /// </summary>
        private List<TreeMenuItem> _items;

        protected TreeMenuItem _root;

        internal TreeMenuItem Root { get { return _root; } }

        /// <summary>
        /// 绑定的控件
        /// </summary>
        private TreeMenu _menu;

        public TreeMenuItemCollection(TreeMenu owner)
        {
            _menu = owner;
            _items = new List<TreeMenuItem>();
            _root = new TreeMenuItem("根节点");
            _root._indexs = new List<int>();
            _root.SubItem = _items;
        }

        /// <summary>
        /// 获取指定索引的项(仅仅能获取一级)
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public TreeMenuItem this[int index] { get => this._items[index];
            set { 
                this._items[index] = value;
                this._items[index]._indexs = new List<int>() { index };
                this._items[index].ResetIndexs();
            }
        }

        public TreeMenuItem? FindItem(string id)
        {
            var ids = this.DecomposeId(id);
            TreeMenuItem item;
            if (this._items.Count < ids[0]) item = this._items[0];
            else return null;

            for (int i = 1; i < ids.Count; i++)
            {
                if (item.SubItem.Count < ids[i])
                {
                    item = item.SubItem[i];
                }
                else return null;
            }
            return item;
        }

        /// <summary>
        /// 分解Id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        protected List<int> DecomposeId(string id)
        {
            List<int> result = new List<int>();
            List<string> ids = id.Split('-').ToList();
            ids.ForEach(x => result.Add(int.Parse(x)));
            return result;
        }

        /// <summary>
        /// 获取所有子项的数量
        /// </summary>
        public int Count { get => this._items.Count; }

        public bool IsReadOnly => throw new NotImplementedException();

        /// <summary>
        /// 添加项
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public int Add(TreeMenuItem value)
        {
            value._indexs = new List<int>() { this._items.Count };       // 这里还没有添加到列表。因此不用+1
            value._parent = this._root;
            value.ResetIndexs();
            this._items.Add(value);
            return this._items.Count - 1;
        }

        /// <summary>
        /// 添加项
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <param name="icon"></param>
        /// <returns></returns>
        public TreeMenuItem Add(string name, string value = "", IconType? icon = null)
        {
            TreeMenuItem item = new TreeMenuItem(name, value, icon);
            this.Add(item);
            return item;
        }

        /// <summary>
        /// 清空所有
        /// </summary>
        public void Clear()
        {
            this._items.Clear();
        }

        /// <summary>
        /// 判断是否包含,只对Id判断。判断Id是否存在
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool Contains(TreeMenuItem value)
        {
            var item = this.FindItem(value.Id);
            return item != null;
        }

        /// <summary>
        /// 复制到Array
        /// </summary>
        /// <param name="array"></param>
        /// <param name="index"></param>
        public void CopyTo(Array array, int index)
        {
            Array arr = this._items.ToArray();
            arr.CopyTo(array, index);
        }

        /// <summary>
        /// 复制到TreeMenuItem[]
        /// </summary>
        /// <param name="array"></param>
        /// <param name="arrayIndex"></param>
        public void CopyTo(TreeMenuItem[] array, int arrayIndex)
        {
            this._items.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// 查找,仅支持查找顶级
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public int IndexOf(TreeMenuItem value)
        {
            for (int i = 0; i < this._items.Count; i++)
            {
                if (value.Id == this._items[i].Id) return i;
            }
            return -1;
        }

        public void Insert(int index, TreeMenuItem value)
        {
            
            this._items.Insert(index, value);
            throw new InvalidOperationException();
        }

        public void Remove(TreeMenuItem? value)
        {
            throw new NotImplementedException();
        }

        public void RemoveAt(int index)
        {
            throw new NotImplementedException();
        }

        void ICollection<TreeMenuItem>.Add(TreeMenuItem item)
        {
            this.Add(item);
        }

        bool ICollection<TreeMenuItem>.Remove(TreeMenuItem item)
        {
            this.Remove(item);
            return true;
        }

        public IEnumerator<TreeMenuItem> GetEnumerator()
        {
            throw new NotImplementedException();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }


        /// <summary>
        /// 根据当前项,向后查找下一个需要显示的项(只考虑需要显示的,并不考虑可视和折叠分组问题)
        /// </summary>
        /// <param name="item"></param>
        /// <returns>返回null则是已经查找到最后了</returns>
        internal TreeMenuItem? FindNextItem(TreeMenuItem item)
        {

            TreeMenuItem? newItem;
            if (item.HasChlid == true)
            {
                newItem = item.SubItem.First();
            }
            else
            {
                // 如果父类为null 则已经找到root级别
                if (item.IsRoot == true) return null;

                newItem = item.Next();      // 获取下一个
                // 当为null 则是已经到了最后一个,再查找就需要查找父类的下一个
                if (newItem == null)
                {
                    newItem = item;     // 重新定义,方便循环
                    bool flg = true;
                    while (flg)
                    {
                        // 如果当前项为root 则退出
                        if (newItem.IsRoot == true) return null;        
                        // 当前项不是root,则需要查找其父类的下一个
                        var res = newItem.Parent.Next();                // 获取当前对象父类的下一个(同时也父类不是root)
                        // 父类的下一个为null,则说明当前项父类已经是最后一个,需要查找更为高一个父类的下一个
                        if (res == null)                             // 当这个获取还是null,说明已经没有下一项了
                        {
                            // 将父类重新赋值给newItem,方便下次循环时调用
                            newItem = newItem.Parent;

                            /// 需要再次验证,尤其是1级最后一项
                            /// 因为如果是最后一项,向前循环查到1级时,这个时候它还不是root,但是root的最后一项
                            if (newItem.Level == 1 && newItem.IsLast)
                            {
                                return null;
                            }
                            // 这里没有使用IsLastItem判断是因为,使用此方法,则每一个级别的最后一个元素都要调用一次,会导致增加循环次数

                        }
                        else
                        {
                            // 找到了则标记并返回
                            flg = false;
                            newItem = res;
                        }
                    }

                }
            }
            return newItem;
        }

        internal TreeMenuItem? FindPreviousItem(TreeMenuItem item)
        {
            if (item.Level == 1 && item.IsFrist) return null;       // 当前项已经是可显示的第一个了


            TreeMenuItem? newItem;
            if (item.IsFrist == true)
            {
                return item.Parent;
            }
            else
            {
                newItem = item.Previous();
                if (newItem?.HasChlid == true)
                {
                    return this.GetLastChildItem(newItem);
                }
                else
                {
                    return newItem;
                }

            }
            return newItem;
        }

        /// <summary>
        /// 判断传入项是否为最后一个项
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool IsLastItem(TreeMenuItem item)
        {
            TreeMenuItem newItem = item;
            if(newItem.IsLast == false) return false;      // 必须是所有同级别中最后一个
            while (true) 
            {
                if (newItem.Parent != null)
                {
                    newItem = newItem.Parent;
                    if (newItem.IsLast == false) return false;      // 必须是所有同级别中最后一个
                }
                else 
                { 
                    return true;
                }
            }
        }

 
        /// <summary>
        /// 获取指定项中所有子项、子子项中最后一个项
        /// </summary>
        /// <param name="item">指定的项,当为null,获取所有项的最后一个项</param>
        /// <returns>当不存在节点时返回null</returns>
        public TreeMenuItem? GetLastChildItem(TreeMenuItem? item = null)
        {
            if (item == null) item = this._root;
            if (item.HasChlid == false) return null;            // 当不存在子项,直接返回null
            TreeMenuItem? record = item.LastChild;                     // 记录当前正在查找的项
            if (record == null) return null;        // 因为肯定有子项,因此不会是null
            TreeMenuItem? res = record.LastChild;           // 记录查找当前项最后一个子项的结果
            while (true)
            {
                if (res == null)
                {
                    return record;
                }
                else
                {
                    record = res;                                  // 重新赋值
                    res = res.LastChild;                        // 再次查找子项
                }
            }
        }

    }
}
ShowItem.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
    /// <summary>
    /// 当前显示项
    /// </summary>
    public class ShowItem
    {
        /// <summary>
        /// 显示的项
        /// </summary>
        public TreeMenuItem Item { get; set; }

        /// <summary>
        /// 整个项的区域
        /// </summary>
        public Rectangle Rect { get; set; }

        /// <summary>
        /// 整个项左侧图标区域
        /// </summary>
        public Rectangle LeftIconRect { get; set; }

        /// <summary>
        /// 右侧折叠图标区域
        /// </summary>
        public Rectangle FoldIconRect { get; set; }

        /// <summary>
        /// 标题文字绘图区域
        /// </summary>
        public Rectangle StringRect { get; set; }

        /// <summary>
        /// 整个项的背景区域(一般会必绘图区小一点)
        /// </summary>
        public Rectangle BackgroundRect {  get; set; }

        /// <summary>
        /// 整个项的绘图区域
        /// </summary>
        public Rectangle DrawArea {  get; set; }

        /// <summary>
        /// 当前项的绘制样式
        /// </summary>
        public ItemStyle Style { get; set; }

        public ShowItem(TreeMenuItem item, Rectangle rect)
        {
            Item = item;
            Rect = rect;
        }
    }
}
TreeMenu.cs 文件

namespace XiaoHeiControls.CollapseMenu
{
    public partial class TreeMenu : UserControl
    {
        /// <summary>
        /// 项的最小高度
        /// </summary>
        public const int MIN_ITEM_HEIGHT = 24;

        /// <summary>
        /// 子项的最小缩进间距
        /// </summary>
        public const int MIN_SINDENT = 8;


        #region ===成员===
        /// <summary>
        /// 第一个显示的控件
        /// <para>X坐标表示父类项,Y表示其子项</para>
        /// </summary>
        private TreeMenuItem? _fristItem = null;

        /// <summary>
        /// 控件中的列表项
        /// </summary>
        private TreeMenuItemCollection _items;

        /// <summary>
        /// 项高度
        /// </summary>
        private int _itemHight = 28;

        /// <summary>
        /// 列表项默认样式
        /// </summary>
        private ItemStyle _defaultItemStyle;

        /// <summary>
        /// 列表项样式
        /// </summary>
        private List<ItemStyle> _itemStyle = new List<ItemStyle>();

        /// <summary>
        /// 选中项的样式
        /// </summary>
        private ItemStyle _selectItemStyle;

        /// <summary>
        /// 光标悬停经过时项的样式
        /// </summary>
        private ItemStyle _hoverItemStyle ;

        /// <summary>
        /// 折叠图标
        /// </summary>
        private FoldingIcon _foldIcon = FoldingIcon.Angle;

        /// <summary>
        /// 无图标占用空间(当某个项没有图标时,则图标位为空,字符串会左移,和其他的图标对其)
        /// </summary>
        private bool _noIconOccupyingSpace = true;

        /// <summary>
        /// 子项缩进宽度
        /// </summary>
        private int _sindent = 15;

        /// <summary>
        /// 当前选中的项
        /// </summary>
        private ShowItem? _selectItem = null;

        /// <summary>
        /// 当前悬停所在的项
        /// </summary>
        private ShowItem? _hoverItem = null;

        /// <summary>
        /// 是否可以显示左侧图标
        /// </summary>
        private bool _showLeftIcon = true;

        /// <summary>
        /// 显示的项,及对应的位置
        /// </summary>
        private List<ShowItem> _showItems = new List<ShowItem>();

        /// <summary>
        /// 左侧图标颜色(包含图标和选择框),优先级高于项字体颜色,当为null或Empty时颜色与项的字体颜色相同。
        /// </summary>
        private Color? _leftIconColor = null;

        /// <summary>
        /// 是否点击折叠图标才能折叠项
        /// </summary>
        private bool _isClickIconFoldItem = false;

        #endregion

        #region ===公开属性===

        public TreeMenuItemCollection Items { get => _items; set => _items = value; }

        [Category("样式")]
        [Description("左侧图标颜色(包含图标和选择框),优先级高于项字体颜色,当为null或Empty时颜色与项的字体颜色相同。")]
        [Browsable(true)]
        /// <summary>
        /// 左侧图标颜色(包含图标和选择框),优先级高于项字体颜色,当为null或Empty时颜色与项的字体颜色相同。
        /// </summary>
        public Color? LeftIconColor
        {
            get=>this._leftIconColor;
            set {
                if (_leftIconColor == value) return;
                _leftIconColor = value;
                this.Refresh();     // 重绘前会自动计算缩进
            }
        }


        /// <summary>
        /// 当前选中项
        /// </summary>
        public TreeMenuItem? SelectItem { get => this._selectItem?.Item; }

        [Category("其他")]
        [Description("是否点击折叠图标才能折叠项,设置为true则要设置折叠图标,否则将无法通过点击鼠标折叠。当然这样也是一种禁止折叠的设置。")]
        [Browsable(true)]
        /// <summary>
        /// 是否点击折叠图标才能折叠项
        /// </summary>
        public bool IsClickIconFoldItem { get => this._isClickIconFoldItem; set => this._isClickIconFoldItem = value; }

        [Category("其他")]
        [Description("是否可以显示左侧图标")]
        [Browsable(true)]
        /// <summary>
        /// 是否可以显示左侧图标
        /// </summary>
        public bool IsShowLeftIcon { get => this._showLeftIcon; set => this._showLeftIcon = value; }

        /// <summary>
        /// 当前显示区内的第一个显示项
        /// </summary>
        public TreeMenuItem? FirstShowItem { get => this._fristItem; }

        [Category("样式")]
        [Description("无图标占用空间(当某个项没有图标时,则图标位为空,字符串会左移,和其他的图标对其)")]
        [Browsable(true)]
        /// <summary>
        /// 无图标占用空间(当某个项没有图标时,则图标位为空,字符串会左移,和其他的图标对其)
        /// </summary>
        public bool NoIconOccupyingSpace
        {
            get => _noIconOccupyingSpace;
            set 
            {
                if (_noIconOccupyingSpace == value) return;
                _noIconOccupyingSpace = value;
                this.Refresh();     // 重绘前会自动计算缩进
            }
        }


        [Category("样式")]
        [Description("项高度,建议和项的字体匹配,否则会很丑!")]
        [Browsable(true)]
        /// <summary>
        /// 设置项高度
        /// </summary>
        public int ItemHight { get => _itemHight; 
            set  {
                // 限制最小值
                _itemHight = value <= MIN_ITEM_HEIGHT ? MIN_ITEM_HEIGHT : value;
                // 大小设置后需要重新计算显示区的
                this.RefreshVisualArea();   // 重新计算显示区
                this.Refresh();
            } 
        }

        [Category("样式")]
        [Description("子项的缩进,最小值15")]
        [Browsable(true)]
        /// <summary>
        /// 设置或获取子项的缩进距离
        /// </summary>
        public int Sindent
        {
            get => _sindent;
            set
            {
                // 限制最小值
                _sindent = value <= MIN_SINDENT ? MIN_SINDENT : value;
                this.Refresh();     // 重绘前会自动计算缩进
            }
        }

        [Category("样式")]
        [Description("折叠图标,当为None不显示折叠图标,但是如果没有禁用折叠,将可以继续折叠")]
        [Browsable(true)]
        public FoldingIcon FoldIcon { get => this._foldIcon; set=> this._foldIcon = value; }

        [Category("样式")]
        [Description("未设置项样式的默认样式")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]          // 这两句,可以在设计器中显示类的属性
        [TypeConverter(typeof(ExpandableObjectConverter))]
        /// <summary>
        /// 未设置项样式的默认样式
        /// </summary>
        public ItemStyle DefaultItemStyle { get => _defaultItemStyle; set => _defaultItemStyle = value; }

        [Category("样式")]
        [Description("设置项的样式,如果同一个Level设置了多个样式,只会取最后一个设置的")]
        [Browsable(true)]
        /// <summary>
        /// 设置项的样式,如果同一个Level设置了多个样式,只会取最后一个设置的
        /// </summary>
        public List<ItemStyle> ItemStyle { get => _itemStyle; set => _itemStyle = value; }

        [Category("样式")]
        [Description("选中项的样式")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]          // 这两句,可以在设计器中显示类的属性
        [TypeConverter(typeof(ExpandableObjectConverter))]
        /// <summary>
        /// 选中项的样式
        /// </summary>
        public ItemStyle SelectItemStyle { get => _selectItemStyle; set => _selectItemStyle = value; }

        [Category("样式")]
        [Description("光标悬停经过时项的样式")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]          // 这两句,可以在设计器中显示类的属性
        [TypeConverter(typeof(ExpandableObjectConverter))]
        /// <summary>
        /// 光标悬停经过时项的样式
        /// </summary>
        public ItemStyle HoverItemStyle { get => _hoverItemStyle; set => _hoverItemStyle = value; }

        #endregion

        #region ===声明事件===
        [Category("Item事件")]
        [Description("鼠标左键点击列表项事件。")]
        [Browsable(true)]
        /// <summary>
        /// 鼠标左键点击列表项事件
        /// <para>点击后如果是可以选中的,则默认优先选中,再调用此事件。</para>
        /// <para>点击后如果是不可选中的,则默认优先调用折叠等方法,再调用此事件。</para>
        /// </summary>
        public event ClickItemHandler? LeftMouseClickItem;

        public event ClickItemHandler? RightMouseClickItem;

        [Category("Item事件")]
        [Description("鼠标点击折叠或者展开项的事件(仅鼠标操作才触发)。")]
        [Browsable(true)]
        /// <summary>
        /// 鼠标点击折叠或者展开项的事件(仅鼠标操作才触发)。
        /// <para>需要知道是折叠操作还是展开操作,只需要判断项当前的状态,如果是展开状态,则就是进行折叠的操作。反之。</para>
        /// </summary>
        public event FoldItemHandler? MouseClickFoldItem;

        [Category("Item事件")]
        [Description("鼠标点击列表项的Check值被改变的事件(仅鼠标操作才触发)。")]
        [Browsable(true)]
        /// <summary>
        /// 列表项的Check值被点击改变(仅鼠标操作才触发)。
        /// <para>值改变前事件,因此可以取消其改变事件</para>
        /// </summary>
        public event ItemCheckedChangeHandler? MouseItemCheckedChange;

        #endregion

        public TreeMenu()
        {
            this._items  = new TreeMenuItemCollection(this);        // 创建项控制器
            this.DoubleBuffered = true;
            InitializeComponent();

            // 初始化一些默认的样式
            this._itemStyle.Add(new ItemStyle(1) { ForeColor = Color.Black });
            this._itemStyle.Add(new ItemStyle(2) { ForeColor = Color.FromArgb(102, 102, 102) });
            this._hoverItemStyle = new ItemStyle(-1) { BackColor = Color.FromArgb(50, Color.Red) };
            this._selectItemStyle = new ItemStyle(-1) { BackColor = Color.FromArgb(234,85,0) };
            this._defaultItemStyle = new ItemStyle(-1) { ForeColor = Color.FromArgb(102,102,102) };


            // 绘制透明背景【但由于绘制后显示的文字不清晰,因此就不使用这个功能了。全局有两处相关代码,分别在控件初始化、控件绘制方法】
            // 设置控件样式,支持透明背景
            //SetStyle(ControlStyles.SupportsTransparentBackColor |
            //         ControlStyles.UserPaint |
            //         ControlStyles.AllPaintingInWmPaint |
            //         ControlStyles.OptimizedDoubleBuffer, true);
             设置背景色为透明
            //this.BackColor = Color.Transparent;
             其他初始化代码...
        }



        #region ===绘制项===

        /// <summary>
        /// 调用方法前,需要提前计算好数据
        /// </summary>
        protected virtual void DrawItems(Graphics g)
        {
            if (this._showItems.Count == 0) 
            {
                this.RefreshVisualArea();   // 重新计算显示区
            }
            if (this._showItems.Count > 0)
            {
                if (this._showItems[0].Item.Id != this._fristItem?.Id)
                {
                    this.RefreshVisualArea();   // 重新计算显示区
                }
            }
           
            foreach (var item in this._showItems)
            {
                this.DrawItem(g, item);
            }
        }

        /// <summary>
        /// 画列表项
        /// </summary>
        /// <param name="g"></param>
        /// <param name="rect"></param>
        /// <param name="item"></param>
        protected virtual void DrawItem(Graphics g, ShowItem showItem) 
        {
            // 【测试】仅仅测试时进行检查使用
            //g.DrawRectangles(Pens.Black, rects.Values.ToArray());

            // 绘制项的背景色
            this.OnDrawItemBackground(g, showItem.BackgroundRect, showItem);

            // 绘制左侧图标
            this.OnDrawItemLeftIcon(g, showItem.LeftIconRect, showItem);

            // 绘制文字图标
            this.OnDrawItemString(g, showItem.StringRect, showItem);

            // g.DrawRectangle(Pens.Green, showItem.FoldIconRect);

            // 绘制右侧折叠图标
            this.OnDrawItemFoldIcon(g, showItem.FoldIconRect, showItem);
        }

        /// <summary>
        /// 绘制项背景
        /// </summary>
        /// <param name="g">画板</param>
        /// <param name="rect">绘图区域,可能为空(rect.IsEmpty)为空则不应该画图</param>
        /// <param name="item">正在绘制的项</param>
        /// <param name="style">绘制这个项的样式</param>
        protected virtual void OnDrawItemBackground(Graphics g, Rectangle rect, ShowItem item)
        {
            // 绘制项的背景色
            if (!(item.Style.BackColor.IsEmpty || item.Style.BackColor == Color.Transparent))
            {
                using (Brush brush = new SolidBrush(item.Style.BackColor))
                {
                    if (item.Style.Radius > 0)
                    {
                        var r = DrawPath.RoundedRectangle(rect, item.Style.Radius);
                        g.FillPath(brush, r);
                    }
                    else
                    {
                        g.FillRectangle(brush, rect);
                    }
                }
            }
        }

        /// <summary>
        /// 绘制项左侧图标
        /// </summary>
        /// <param name="g">画板</param>
        /// <param name="rect">绘图区域,可能为空(rect.IsEmpty)为空则不应该画图</param>
        /// <param name="item">正在绘制的项</param>
        /// <param name="style">绘制这个项的样式</param>
        protected virtual void OnDrawItemLeftIcon(Graphics g, Rectangle rect, ShowItem item)
        {
            // 不用绘制则退出
            if (this._showLeftIcon == false || rect.IsEmpty) return;
            // 定义绘制的颜色
            Color color = this._leftIconColor==null ? item.Style.ForeColor: (Color)this._leftIconColor;
            if (item.Item.Icon != null)
            {
                Icon icon = IconHelper.GetFontIcon((IconType)item.Item.Icon, color, rect.Height);
                g.DrawIcon(icon, rect);
            }
        }

        /// <summary>
        /// 绘制项文字
        /// </summary>
        /// <param name="g">画板</param>
        /// <param name="rect">绘图区域,可能为空(rect.IsEmpty)为空则不应该画图</param>
        /// <param name="item">正在绘制的项</param>
        /// <param name="style">绘制这个项的样式</param>
        protected virtual void OnDrawItemString(Graphics g, Rectangle rect, ShowItem item)
        {
            if (string.IsNullOrEmpty(item.Item.Name) == false)
            {
                Font font = item.Style.Font;
                string text = item.Item.Name;
                // 使用MeasureString测量文本宽度
                SizeF textSize = g.MeasureString(text, font);
                using (Brush brush = new SolidBrush(item.Style.ForeColor))
                {
                    StringFormat format = new StringFormat()
                    {
                        Alignment = StringAlignment.Near,
                        LineAlignment = StringAlignment.Center,
                        FormatFlags = StringFormatFlags.NoWrap,
                    };
                    g.DrawString(text, font, brush, rect, format);
                }
            }
        }

        /// <summary>
        /// 绘制项右侧图标
        /// </summary>
        /// <param name="g">画板</param>
        /// <param name="rect">绘图区域,可能为空(rect.IsEmpty)为空则不应该画图</param>
        /// <param name="item">正在绘制的项</param>
        /// <param name="style">绘制这个项的样式</param>
        protected virtual void OnDrawItemFoldIcon(Graphics g, Rectangle rect, ShowItem item)
        {
            if (rect.IsEmpty == false)
            {
                IconType iconTypeRight, iconTypeDown;
                switch (this._foldIcon)
                {
                    case FoldingIcon.None:
                        return;
                    case FoldingIcon.Caret:
                        iconTypeRight = IconType.CaretRight;
                        iconTypeDown = IconType.CaretDown;
                        break;
                    case FoldingIcon.ChevronCircle:
                        iconTypeRight = IconType.ChevronCircleRight;
                        iconTypeDown = IconType.ChevronCircleDown;
                        break;
                    case FoldingIcon.Angle:
                        iconTypeRight = IconType.AngleRight;
                        iconTypeDown = IconType.AngleDown;
                        break;
                    case FoldingIcon.Chevron:
                        iconTypeRight = IconType.ChevronRight;
                        iconTypeDown = IconType.ChevronDown;
                        break;
                    default:
                        return;
                }
                IconType iconType = item.Item.Expand ? iconTypeDown : iconTypeRight;

                // 获取一个默认图标
                Icon icon = IconHelper.GetFontIcon(iconType, item.Style.ForeColor, rect.Height);
                // 绘制右侧图标
                g.DrawIcon(icon, rect);
            }
        }

        #endregion


        #region ===重写功能事件===

        protected override void OnPaint(PaintEventArgs e)
        {

            base.OnPaint(e);
            // 绘制透明背景【但由于绘制后显示的文字不清晰,因此就不使用这个功能了。全局有两处相关代码,分别在控件初始化、控件绘制方法】
            //e.Graphics.FillRectangle(new SolidBrush(this.BackColor), e.ClipRectangle);
             绘制前的设置,确保文字渲染清晰
            //e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;

            e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
            e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;

            // 每次重新绘图前必须重新计算是否选中等样式的计算
            this._showItems.ForEach(x => x.Style = this.GetItemStyle(x.Item));

            // 重新绘制
            e.Graphics.Clear(this.BackColor);
            this.DrawItems(e.Graphics);

        }

        protected override void OnMouseWheel(MouseEventArgs e)
        {
            base.OnMouseWheel(e);
            if (this._fristItem == null || this._items.Count == 0) return;


            Point mousePos = Cursor.Position; // 获取当前鼠标位置
            mousePos = this.PointToClient(mousePos); // 转换为控件相对坐标
            if (this.ClientRectangle.Contains(mousePos))
            {
                if (e.Delta > 0)
                {
                    var item = this._items.FindPreviousItem(this._fristItem);
                    if (item == null) return;   // 当前已经是第一个了
                    this._fristItem = item;
                    this._hoverItem = null;     // 只要滚动移位,原本悬停的位置就变动了
                    this.Refresh();
                }
                else if (e.Delta < 0)
                {
                    int maxCount = (int)((this.Height - 1) / this._itemHight) + 1;      // 计算显示项的数量

                    if (this._showItems.Count < maxCount - 1) return;           // 当前选中的项少于了最大项-1,则就不能向下滚动了


                    var item = this._items.FindNextItem(this._fristItem);
                    if (item == null) return;   // 当前已经是最后一个
                    this._fristItem = item;
                    this._hoverItem = null;     // 只要滚动移位,原本悬停的位置就变动了
                    this.Refresh();
                }
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            //Point mousePos = Cursor.Position; // 获取当前鼠标位置
            //mousePos = this.PointToClient(mousePos); // 转换为控件相对坐标
            
            if (this._hoverItem == null)
            {
                for (int i = 0; i < this._showItems.Count; i++)
                {
                    if (this._showItems[i].Rect.Contains(e.Location))
                    {
                        this._hoverItem = this._showItems[i];
                        this.Refresh();
                    }
                }
            }
            else
            {
                if (this._hoverItem.Rect.Contains(e.Location))
                {
                    return;
                }
                else
                {
                    for (int i = 0; i < this._showItems.Count; i++)
                    {
                        if (this._showItems[i].Rect.Contains(e.Location))
                        {
                            this._hoverItem = this._showItems[i];
                            this.Refresh();
                        }
                    }
                }
            }
        }

        protected override void OnClientSizeChanged(EventArgs e)
        {
            base.OnClientSizeChanged(e);
            // 大小设置后需要重新计算显示区的
            this.RefreshVisualArea();   // 重新计算显示区
        }

        protected override void OnMouseClick(MouseEventArgs e)
        {
            base.OnMouseClick(e);
            for (int i = 0; i < this._showItems.Count; i++)
            {
                if (this._showItems[i].Rect.Contains(e.Location))
                {
                    if (e.Button == MouseButtons.Right)
                    {
                        if (this.RightMouseClickItem != null) this.RightMouseClickItem(this, this._showItems[i].Item);
                    }
                    else if (e.Button == MouseButtons.Left)
                    {
                        bool change_item = false;
                        // 这里的前提是可以被选中(如分组就不能被选中)
                        if (this._selectItem?.Item.Id != this._showItems[i].Item.Id)
                        {
                            change_item = true;     // 更换了选中项
                        }
                        this._selectItem = this._showItems[i];      // 设置选中项


                        // 判断是否为可折叠的节点
                        bool folding = this.UnfoldOrCollapseItemOperation(this._showItems[i],e.Location);

                       
                        if (folding) this.RefreshVisualArea();      // 折叠后还需要重新计算绘图区

                        // 更换选中项后需要重新绘制、折叠或展开后也是要重新绘制
                        if (change_item == true || folding == true) this.Refresh();


                        if (this.LeftMouseClickItem != null)
                        {
                            this.LeftMouseClickItem(this, this._showItems[i].Item);
                        }
                    }
                    
                }
            }

            
        }

        protected override void OnMouseDoubleClick(MouseEventArgs e)
        {
            base.OnMouseDoubleClick(e);
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);
            this._hoverItem = null;
            this.Refresh();
        }



        #endregion

        /// <summary>
        /// 获取指定位置的项
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public TreeMenuItem? GetItemByPoint(Point point)
        {
            for (int i = 0; i < this._showItems.Count; i++)
            {
                if (this._showItems[i].Rect.Contains(point))
                {
                    return this._showItems[i].Item;
                }
            }
            return null;
        }


        #region ===其他私有方法===

        /// <summary>
        /// 计算每个项的绘图区域
        /// </summary>
        /// <param name="rect"></param>
        /// <param name="item"></param>
        /// <param name="style"></param>
        /// <returns></returns>
        protected Dictionary<string, Rectangle> GetItemDrawRect(TreeMenuItem item,Rectangle rect, ItemStyle style)
        {
            Dictionary<string, Rectangle> dr = new Dictionary<string, Rectangle>();

            //定义绘图区
            Rectangle r = rect.Shifting(1, 1, -4, -2);
            dr["background"] = r;
            dr["background"].Shifting(0, -1, 0, 0);        // 做一个偏移,要不然画出的图形有点偏

            // 根据字体的高度,重新计算绘图区域(绘图区域不是整个项的全部区域)
            if (r.Height > style.Font.Height)
            {
                int diff = r.Height - style.Font.Height;
                r.Y = r.Y + diff / 2;
                r.Height = style.Font.Height;
            }
            dr["drawArea"] = r;     // 保存绘图区域

            // 计算缩进
            int sindent = (int)((item.Level - 1) * this._sindent);
            r.X += sindent;
            r.Width -= sindent;

            
            // 计算左侧图标
            dr["left"] = new Rectangle(r.X, r.Y, r.Height, r.Height);
            // 判断当前列表项是否设置了图标
            if ((this._noIconOccupyingSpace == true && item.Icon == null) || this._showLeftIcon == false)
            {
                dr["left"] = Rectangle.Empty;
            }


            // 计算展开与折叠图标位置
            dr["fold_icon"] = new Rectangle(r.Right - r.Height, r.Y, r.Height, r.Height);
            if (item.SubItem.Count == 0) dr["fold_icon"] = Rectangle.Empty;


            // 正常情况显示
            dr["string"] = new Rectangle(r.X + 1 + dr["left"].Width + 1, r.Y, r.Width - dr["fold_icon"].Width - (dr["left"].X + 2), r.Height);

            return dr;
        }

        /// <summary>
        /// 刷新显示区(重新计算显示区域)
        /// </summary>
        protected void RefreshVisualArea()
        {
            List<TreeMenuItem> items = this.CalculateDisplayItems();
            this._showItems.Clear();

            for (int i = 0; i < items.Count; i++)
            {
                // 绘制 一级目录
                Rectangle rect = new Rectangle(0, i * this.ItemHight, this.Width - 1, this.ItemHight);
                var showItem = new ShowItem(items[i], rect);
                showItem.Style = this.GetItemStyle(items[i]);                           // 设置样式
                var dict = this.GetItemDrawRect(items[i], rect, showItem.Style);        // 计算绘图区域
                // 设置获取的值
                showItem.BackgroundRect = dict["background"];
                showItem.DrawArea = dict["drawArea"];
                showItem.LeftIconRect = dict["left"];
                showItem.StringRect = dict["string"];
                showItem.FoldIconRect = dict["fold_icon"];
                this._showItems.Add(showItem);

            }
        }

        /// <summary>
        /// 根据不同的项获取不同的设置样式
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private ItemStyle GetItemStyle(TreeMenuItem item)
        {
            // 获取当前项的样式
            var styles = this._itemStyle.Where(x => x.Level == item.Level).ToList();
            ItemStyle style = styles.Count > 0 ? styles.Last() : style = this._defaultItemStyle;
            // 判断当前项是否悬停选择的项
            if (this._hoverItem?.Item.Id == item.Id) style = this._hoverItemStyle;
            // 判断是否是选中的项
            if (this._selectItem?.Item.Id == item.Id) style = this._selectItemStyle;
            return style;
        }

        /// <summary>
        /// 折叠或展开项操作(事件)
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private bool UnfoldOrCollapseItemOperation(ShowItem item,Point point)
        {
            if (this._isClickIconFoldItem == true)      // 判断是否必须点击折叠图标在可以
            {
                if (item.FoldIconRect.Contains(point) == false) return false;   // 没有点击图标,则跳过
            }
            // 如果是点击任何部分都可以折叠的执行后续
            if (item.Item.HasChlid == false) return false;       // 验证是否有子项,没有则不用折叠
            if (item.Item.SubItem.Where(x => x.Visible == true).Count() == 0) return false;  // 当列表项中没有可视项,也会被返回
            bool has = true;
            if (this.MouseClickFoldItem != null)
            {
                this.MouseClickFoldItem(this, item.Item,ref has);
            }
            if (has == false) return false;
            item.Item.Expand = !item.Item.Expand;             // 进行取反操作
            return true;
        }

        /// <summary>
        /// 勾选选项操作(事件)
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private bool ChoiceItemOperation(ShowItem item, Point point)
        {
            if (item.LeftIconRect.Contains(point) == false) return false;

            var checkedChange = true;
            // 调用事件
            if (this.MouseItemCheckedChange != null) this.MouseItemCheckedChange(this, item.Item, ref checkedChange);
            if (checkedChange == true)
            {
                // null→true→false→true→false...顺序设置
                if (item.Item.Checked == null) item.Item.Checked = true;
                else if (item.Item.Checked == true) item.Item.Checked = false;
                else item.Item.Checked = true;
            }
            return checkedChange;
        }

        /// <summary>
        /// 递归查找第一个索引
        /// </summary>
        /// <param name="item"></param>
        /// <param name="indexs"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        private TreeMenuItem? _FindItem(TreeMenuItem item, List<int> indexs, int index)
        {
            // 当前查找的索引长度已经超过了查找的深度,这个时候还没有返回,那么真的就没有了
            if (indexs.Count - 1 < index)
            {
                return null;
            }
            else if (indexs.Count - 1 == index)
            {
                // 当前查找的深度刚好是定位索引引列表的最后一个

                // 定位索引的,查找的索引值是否超出了边界,超过了则就是没有对应的项
                if (indexs[index] > item.SubItem.Count - 1) return null;        // 结束了查找
                // 找到了对应的值
                return item.SubItem[indexs[index]];

            }
            else
            {
                // 定位索引,还没有找到最后一位

                // 定位索引的,查找的索引值是否超出了边界,超过了则就是没有对应的项
                if (indexs[index] > item.SubItem.Count - 1) return null;

                //没有的则继续查找
                return _FindItem(item.SubItem[indexs[index]], indexs, index + 1);
            }
        }

        #endregion


        #region ===计算绘图区===

        /// <summary>
        /// 计算显示的项
        /// </summary>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private List<TreeMenuItem> CalculateDisplayItems()
        {
            List<TreeMenuItem> items = new List<TreeMenuItem>();
            
            if (this._items.Count == 0) return items;                  // 这里应当返回示例数据

            // 只有初始没有设置值,进行一个初始设置
            if (this._fristItem == null) this._fristItem = this._items[0];

            int maxCount = (int)((this.Height - 1) / this._itemHight) + 1;      // 计算显示项的数量
   
            TreeMenuItem? item = this._FindItem(this._items.Root, this._fristItem.Indexs, 0);
            if (item == null) throw new Exception("当前第一个显示项的定位索引未找到数据!可以重置索引!");

            
            items.Add(item);
            var it = item;
            // 第一个添加完毕也是需要对折叠进行一个逻辑判断
            if (it.Expand == false)
            {
                // 这里不能直接使用next寻找下一个项,寻找下一个项的任务必须由 FindNextItem(it)函数完成。
                if (it.HasChlid == true)
                {
                    it = it.DeepestLastChild;       // 获取子子项的最末尾,方便FindNextItem(it)调用,然后直接找到it的next对象
                }
            }
            int count = 1;

            while (true)
            {
                if (count >= maxCount) return items;        // 招够数量了就返回

                it = this._items.FindNextItem(it);
                if (it != null)
                {
                    // 当前项是隐藏项,那么查询下一个则为当前项的下一个
                    if (it.Visible == false)
                    {
                        // 这里不能直接使用next寻找下一个项,寻找下一个项的任务必须由 FindNextItem(it)函数完成。
                        if (it.HasChlid == true)
                        {
                            it = it.DeepestLastChild;       // 获取子子项的最末尾,方便FindNextItem(it)调用,然后直接找到it的next对象
                        }
                        continue;
                    }
                    // 不管展开与不展开,都是要显示的。
                    items.Add(it);
                    count++;

                    // 根据折叠或展开再判断
                    if (it.Expand == false)
                    {
                        // 这里不能直接使用next寻找下一个项,寻找下一个项的任务必须由 FindNextItem(it)函数完成。
                        if (it.HasChlid == true)
                        {
                            it = it.DeepestLastChild;       // 获取子子项的最末尾,方便FindNextItem(it)调用,然后直接找到it的next对象
                        }
                    }
                    
                }
                else
                {
                    return items;
                }
            }
        }

        #endregion
    }



    /// <summary>
    /// 点击列表项事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public delegate void ClickItemHandler(object sender, TreeMenuItem e);

    /// <summary>
    /// 折叠或展开列表子项事件(仅鼠标点击才触发,编程设置后刷新显示不触发)
    /// </summary>
    /// <param name="sender">控件对象</param>
    /// <param name="e">被点击的项</param>
    /// <param name="agree">是否同意折叠或展开。默认是true</param>
    public delegate void FoldItemHandler(object sender, TreeMenuItem e, ref bool agree);

    /// <summary>
    /// 列表项的Check值被点击改变(仅鼠标点击才触发,编程设置后刷新显示不触发)
    /// </summary>
    /// <param name="sender">控件对象</param>
    /// <param name="e">被点击的项</param>
    /// <param name="agree">是否同意改变。默认是true</param>
    public delegate void ItemCheckedChangeHandler(object sender, TreeMenuItem e, ref bool agree);


}
TreeMenuItem.cs 文件
namespace XiaoHeiControls.CollapseMenu
{
    public class TreeMenuItem
    {
        /// <summary>
        /// Id为默认值,创建后不可修改
        /// </summary>
        private string _id = Guid.NewGuid().ToString();

        internal List<int> _indexs = new List<int>();

        private string _name = string.Empty;

        private string _value = string.Empty;

        private IconType? _icon = null;

        private object? _tag = null;

        private bool? _checked = false;

        private bool _expand = true;

        private bool _visible = true;

        internal TreeMenuItem? _parent = null;

        private List<TreeMenuItem> _sub = new List<TreeMenuItem>();

        #region ===公开属性===

        /// <summary>
        /// 项的Id
        /// </summary>
        public string Id { get { return _id; } }

        /// <summary>
        /// 项的索引(移动项需要重置索引的)
        /// </summary>
        public List<int> Indexs { get { return _indexs; } }

        /// <summary>
        /// 名称
        /// </summary>
        public string Name { get => _name; set => _name = value; }

        /// <summary>
        /// 值
        /// </summary>
        public string Value { get => _value; set => _value = value; }
        /// <summary>
        /// 图标
        /// </summary>
        public IconType? Icon { get => _icon; set => _icon = value; }
        /// <summary>
        /// 绑定数据
        /// </summary>
        public object? Tag { get => _tag; set => _tag = value; }
        /// <summary>
        /// 深度等级
        /// </summary>
        public int Level { get => this._indexs.Count;}
        /// <summary>
        /// 是否选中,设置值,只能是bool类型,不能是null
        /// </summary>
        public bool? Checked { get => _checked; 
            set {
                if (value == null) return;
                this.CheckedSubItem((bool)value);
                this.CheckedParetItem();
            } 
        }

        /// <summary>
        /// 是否展开了子项
        /// </summary>
        public bool Expand { get => _expand; set => _expand = value; }

        /// <summary>
        /// 是否可视的
        /// </summary>
        public bool Visible { get => _visible; set => _visible = value; }

        /// <summary>
        /// 当前项的父类,如果为null,则没有父类
        /// </summary>
        public TreeMenuItem? Parent { get => _parent; }


        /// <summary>
        /// 全部子项【虽然这里是List,是方便调用循环,请勿从这里添加子项】
        /// </summary>
        public List<TreeMenuItem> SubItem { get => _sub; set => _sub = value; }

        /// <summary>
        /// 是否有子类
        /// </summary>
        public bool HasChlid { get=>this._sub.Count > 0;}

        /// <summary>
        /// 当前节点是否是根节点(如果直接新建项,没有使用Add方法添加的节点也是根节点)
        /// </summary>
        internal bool IsRoot { get => this.Level == 0; }

        /// <summary>
        /// 当前节点是否是同级别(同一个父类分支)的最后一项
        /// <para>使用前可以判断下是否为Root,Root是没有IsFrist或IsLast</para>
        /// </summary>
        public bool IsLast { get => this.Next() == null; }

        /// <summary>
        /// 当前节点是否是同级别(同一个父类分支)的第一项。
        /// <para>使用前可以判断下是否为Root,Root是没有IsFrist或IsLast</para>
        /// </summary>
        public bool IsFrist {  get => this.Previous() == null; }

        #endregion

        /// <summary>
        /// 创建项清单
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <param name="icon"></param>
        public TreeMenuItem(string name, string value = "", IconType? icon = null)
        {
            _name = name;
            _value = value;
            _icon = icon;
        }

        /// <summary>
        /// 添加子项
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <param name="icon"></param>
        /// <returns></returns>
        public TreeMenuItem AddSubItem(string name, string value = "", IconType? icon = null)
        {
            var sub = new TreeMenuItem(name, value, icon);
            return this.AddSubItem(sub);
        }

        /// <summary>
        /// 添加子项
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public TreeMenuItem AddSubItem(TreeMenuItem item)
        {
            if (item.Level == 9) throw new NotSupportedException("MenuItem添加子项失败!原因:MenuItem最大深度为9级!9级不能添加子项!");
            item._parent = this;
            item._indexs = this._indexs.ToList();       // 使用ToList方法复制一个
            item._indexs.Add(this._sub.Count);          // 添加所在位置
            _sub.Add(item);
            return item;
        }

        /// <summary>
        /// 删除子项
        /// </summary>
        /// <param name="index"></param>
        public void DelSubItem(int index)
        {
            _sub.RemoveAt(index);
        }

        /// <summary>
        /// 删除子项
        /// </summary>
        /// <param name="item"></param>
        public void DelSubItem(TreeMenuItem item)
        {
            _sub.Remove(item);
        }

        /// <summary>
        /// 根据当前项的index,递归重置所有子项的索引
        /// </summary>
        internal void ResetIndexs()
        {
            for (int i = 0; i < this._sub.Count; i++)
            {
                this._sub[i]._indexs = this._indexs.ToList();       // 使用ToList方法复制一个
                this._sub[i]._indexs.Add(i);          // 添加所在位置
                // 调用子项方法 递归 设置子子项indexs
                this._sub[i].ResetIndexs();
            }
        }

        /// <summary>
        /// 查找子项
        /// </summary>
        /// <param name="text">查找的文本,仅支持Name字段</param>
        /// <param name="isAll">是否查找所有的子项。true:递归查找所有子项,false:只查找当前项的子项</param>
        /// <param name="isLike">是否以包含的方式查找。true:只要Name字段内容包含text即可,false:必须完全匹配</param>
        /// <returns></returns>
        public List<TreeMenuItem> FindChilds(string text,bool isAll = true, bool isLike = false)
        {
            List<TreeMenuItem> items = new List<TreeMenuItem>();
            for (int i = 0; i < this._sub.Count; i++)
            {
                if(isLike == true)
                {
                    if (this._sub[i].Name.IndexOf(text) >= 0) items.Add(this._sub[i]);
                }
                else
                {
                    if (this._sub[i].Name == text) items.Add(this._sub[i]);
                }

                if (isAll == true) items.AddRange(this._sub[i].FindChilds(text, isAll, isLike));
            }
            return items;
        }

        /// <summary>
        /// 查找子项
        /// </summary>
        /// <param name="id">查找的id,仅支持Name字段</param>
        /// <param name="isAll">是否查找所有的子项。true:递归查找所有子项,false:只查找当前项的子项</param>
        /// <returns></returns>
        public TreeMenuItem? FindChildById(string id, bool isAll = true)
        {
            for (int i = 0; i < this._sub.Count; i++)
            {
                if (this._sub[i].Id == id) return this._sub[i];

                if (isAll == true) 
                {
                    var subRes = this._sub[i].FindChildById(id, isAll);
                    if (subRes != null) return subRes;
                }
            }
            return null;
        }

        /// <summary>
        /// 获取下一级别(只能获取同一个父类中的同级别子项)
        /// </summary>
        /// <returns>只有父类为null,或者已经是同级别最后一个,也就不能查找到下一个对象</returns>
        /// <exception cref="Exception"></exception>
        public TreeMenuItem? Next()
        {
            if (this._parent == null) throw new Exception("当前项没有父类,无法计算下一个!");      // 顶级父类为null则没有下一级
            int index = this._parent._sub.FindIndex(x=>x.Id == this._id);
            if (index == -1) throw new Exception("在父类查找子项没有找到,出现了莫名其妙的错误!");
            if (index == this._parent._sub.Count -1) return null;       // 已经是最后一项了
            return this._parent._sub[index + 1];
        }

        /// <summary>
        /// 获取上一个级别(只能获取同一个父类中的同级别子项)
        /// </summary>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public TreeMenuItem? Previous()
        {
            if (this._parent == null) throw new Exception("当前项没有父类,无法计算上一个!");
            int index = this._parent._sub.FindIndex(x => x.Id == this._id);
            if (index == -1) throw new Exception("在父类查找子项没有找到,出现了莫名其妙的错误!");
            if (index == 0) return null;       // 已经是第一项了
            return this._parent._sub[index - 1];
        }

        /// <summary>
        /// 获取当前项的最后一个子项
        /// </summary>
        /// <returns></returns>
        public TreeMenuItem? LastChild
        {
            get {
                if (this.HasChlid == false) return null;
                return this._sub[_sub.Count - 1];
            }
        }

        /// <summary>
        /// 获取当前项的第一个子项
        /// </summary>
        /// <returns></returns>
        public TreeMenuItem? FirstChild
        {
            get {
                if (this.HasChlid == false) return null;
                return this._sub[0];
            }
        }

        /// <summary>
        /// 获取当前项最后最后子子项的那一个(当前项的LastChild,获取后再获取LastChild)
        /// </summary>
        /// <returns></returns>
        public TreeMenuItem? DeepestLastChild
        {
            get {
                var sub = this.LastChild;      // 当前项的子项
                while (sub?.HasChlid == true)
                {
                    sub = sub.LastChild;        // 循环获取最后一个,直至没有了子项
                }
                return sub;
            }
        }

        /// <summary>
        /// 递归设置子类的值
        /// </summary>
        /// <param name="val"></param>
        private void CheckedSubItem(bool val)
        {
            this._checked = val;    
            this.SubItem.ForEach(x => x.CheckedSubItem(val));
        }

        /// <summary>
        /// 递归设置父类值,
        /// </summary>
        private void CheckedParetItem()
        {
            if (this.IsRoot == true) return;        // 到达root即便就需要停止设置
            // 当所有子项还有是半选中状态,则父类肯定还是半选中状态
            if (this.Parent?.SubItem.Where(x => x.Checked == null).Count() > 0)
            {
                this.Parent._checked = null;
            }
            else 
            {
                // 全部都是选中的,则父类也应该是选中的
                if (this.Parent?.SubItem.Where(x => x.Checked == true).Count() == 0)
                {
                    this.Parent._checked = false;
                }
                // 全部没有选中的,则父类也应该是没有选中的
                else if (this.Parent?.SubItem.Where(x => x.Checked == false).Count() == 0)
                {
                    this.Parent._checked = true;
                }
                else 
                {
                    // 不是这种情况,那么肯定是既有选中,又有没有选中
                    if (this.Parent!=null) this.Parent._checked = null;
                }
            }
            this.Parent?.CheckedParetItem();
        }
        
        }
}

=Comm文件夹=======
以下代码不是我写的,在网上找到,当时也没找到作者。

IconHelper.cs
namespace XiaoHeiControls.Comm
{
    /// <summary>
    /// 图标帮助类
    /// </summary>
    public sealed class IconHelper
    {
        public static Bitmap GetFontImage(IconType type, Color color, int size)
        {
            var bmp = new Bitmap(size, size);
            var g = Graphics.FromImage(bmp);
            g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
            g.InterpolationMode = InterpolationMode.HighQualityBilinear;
            g.PixelOffsetMode = PixelOffsetMode.HighQuality;
            g.SmoothingMode = SmoothingMode.HighQuality;

            var ch = char.ConvertFromUtf32((int)type);
            var font = GetAdjustedFont(g, ch, size, size, 4, true);
            var stringSize = g.MeasureString(ch, font, size);
            float w = stringSize.Width;
            float h = stringSize.Height;
            // center icon
            float left = (size - w) / 2;
            float top = (size - h) / 2;
            // Draw string to screen.
            using (var brush = new SolidBrush(color))
            {
                g.DrawString(ch, font, brush, new PointF(left, top));
            }
            return bmp;
        }

        public static Icon GetFontIcon(IconType type, Color color, int size)
        {
            return Icon.FromHandle(GetFontImage(type,color,size).GetHicon());
        }

        /// <summary>
        /// 获取调整后的图标字体
        /// </summary>
        /// <param name="g">画板</param>
        /// <param name="graphicString">图形字符串</param>
        /// <param name="containerWidth">宽度</param>
        /// <param name="maxFontSize">最大字体尺寸</param>
        /// <param name="minFontSize">最小字体尺寸</param>
        /// <param name="smallestOnFail">是否最小故障</param>
        /// <returns></returns>
        private static Font GetAdjustedFont(Graphics g, string graphicString, int containerWidth, int maxFontSize, int minFontSize, bool smallestOnFail)
        {
            for (double adjustedSize = maxFontSize; adjustedSize >= minFontSize; adjustedSize = adjustedSize - 0.5)
            {
                Font testFont = GetIconFont((float)adjustedSize);
                // Test the string with the new size
                SizeF adjustedSizeNew = g.MeasureString(graphicString, testFont);
                if (containerWidth > Convert.ToInt32(adjustedSizeNew.Width))
                {
                    // Fits! return it
                    return testFont;
                }
            }

            // Could not find a font size
            // return min or max or maxFontSize?
            return GetIconFont(smallestOnFail ? minFontSize : maxFontSize);
        }

        /// <summary>
        /// 获取图标字体
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        private static Font GetIconFont(float size)
        {
            return new Font(Fonts.Families[0], size, GraphicsUnit.Point);
        }

        static IconHelper()
        {
            InitialiseFont();
        }

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        private static extern IntPtr AddFontMemResourceEx(IntPtr pbFont, uint cbFont, IntPtr pdv, [System.Runtime.InteropServices.In] ref uint pcFonts);

        private static readonly PrivateFontCollection Fonts = new PrivateFontCollection();

        private static void InitialiseFont()
        {
            try
            {
                unsafe
                {
                    fixed (byte* pFontData = Resource1.fontawesome_webfont)
                    {
                        uint dummy = 0;
                        Fonts.AddMemoryFont((IntPtr)pFontData, Resource1.fontawesome_webfont.Length);
                        AddFontMemResourceEx((IntPtr)pFontData, (uint)Resource1.fontawesome_webfont.Length, IntPtr.Zero, ref dummy);
                    }
                }
            }
            catch (Exception)
            {
                // log?
            }
        }
    }
    /// <summary>
    /// 图标类型
    /// <para>具体图标可以查询:https://fontawesome.dashgame.com/#google_vignette</para>
    /// </summary>
    public enum IconType
    {
        Adjust = 0xf042,
        Adn = 0xf170,
        AlignCenter = 0xf037,
        AlignJustify = 0xf039,
        AlignLeft = 0xf036,
        AlignRight = 0xf038,
        Ambulance = 0xf0f9,
        Anchor = 0xf13d,
        Android = 0xf17b,
        AngleDoubleDown = 0xf103,
        AngleDoubleLeft = 0xf100,
        AngleDoubleRight = 0xf101,
        AngleDoubleUp = 0xf102,
        AngleDown = 0xf107,
        AngleLeft = 0xf104,
        AngleRight = 0xf105,
        AngleUp = 0xf106,
        Apple = 0xf179,
        Archive = 0xf187,
        ArrowCircleDown = 0xf0ab,
        ArrowCircleLeft = 0xf0a8,
        ArrowCircleODown = 0xf01a,
        ArrowCircleOLeft = 0xf190,
        ArrowCircleORight = 0xf18e,
        ArrowCircleOUp = 0xf01b,
        ArrowCircleRight = 0xf0a9,
        ArrowCircleUp = 0xf0aa,
        ArrowDown = 0xf063,
        ArrowLeft = 0xf060,
        ArrowRight = 0xf061,
        ArrowUp = 0xf062,
        Arrows = 0xf047,
        ArrowsAlt = 0xf0b2,
        ArrowsH = 0xf07e,
        ArrowsV = 0xf07d,
        Asterisk = 0xf069,
        Automobile = 0xf1b9,
        Backward = 0xf04a,
        Ban = 0xf05e,
        Bank = 0xf19c,
        BarChartO = 0xf080,
        Barcode = 0xf02a,
        Bars = 0xf0c9,
        Beer = 0xf0fc,
        Behance = 0xf1b4,
        BehanceSquare = 0xf1b5,
        Bell = 0xf0f3,
        BellO = 0xf0a2,
        Bitbucket = 0xf171,
        BitbucketSquare = 0xf172,
        Bitcoin = 0xf15a,
        Bold = 0xf032,
        Bolt = 0xf0e7,
        Bomb = 0xf1e2,
        Book = 0xf02d,
        Bookmark = 0xf02e,
        BookmarkO = 0xf097,
        Briefcase = 0xf0b1,
        Btc = 0xf15a,
        Bug = 0xf188,
        Building = 0xf1ad,
        BuildingO = 0xf0f7,
        Bullhorn = 0xf0a1,
        Bullseye = 0xf140,
        Cab = 0xf1ba,
        Calendar = 0xf073,
        CalendarO = 0xf133,
        Calculator = 0xf1ec,
        Camera = 0xf030,
        CameraRetro = 0xf083,
        Car = 0xf1b9,
        CaretDown = 0xf0d7,
        CaretLeft = 0xf0d9,
        CaretRight = 0xf0da,
        CaretSquareODown = 0xf150,
        CaretSquareOLeft = 0xf191,
        CaretSquareORight = 0xf152,
        CaretSquareOUp = 0xf151,
        CaretUp = 0xf0d8,
        Certificate = 0xf0a3,
        Chain = 0xf0c1,
        ChainBroken = 0xf127,
        Check = 0xf00c,
        CheckCircle = 0xf058,
        CheckCircleO = 0xf05d,
        CheckSquare = 0xf14a,
        CheckSquareO = 0xf046,
        ChevronCircleDown = 0xf13a,
        ChevronCircleLeft = 0xf137,
        ChevronCircleRight = 0xf138,
        ChevronCircleUp = 0xf139,
        ChevronDown = 0xf078,
        ChevronLeft = 0xf053,
        ChevronRight = 0xf054,
        ChevronUp = 0xf077,
        Child = 0xf1ae,
        Circle = 0xf111,
        CircleO = 0xf10c,
        CircleONotch = 0xf1ce,
        CircleThin = 0xf1db,
        Clipboard = 0xf0ea,
        ClockO = 0xf017,
        Cloud = 0xf0c2,
        CloudDownload = 0xf0ed,
        CloudUpload = 0xf0ee,
        Cny = 0xf157,
        Code = 0xf121,
        CodeFork = 0xf126,
        Codepen = 0xf1cb,
        Coffee = 0xf0f4,
        Cog = 0xf013,
        Cogs = 0xf085,
        Columns = 0xf0db,
        Comment = 0xf075,
        CommentO = 0xf0e5,
        Comments = 0xf086,
        CommentsO = 0xf0e6,
        Compass = 0xf14e,
        Compress = 0xf066,
        Copy = 0xf0c5,
        CreditCard = 0xf09d,
        Crop = 0xf125,
        Crosshairs = 0xf05b,
        Css3 = 0xf13c,
        Cube = 0xf1b2,
        Cubes = 0xf1b3,
        Cut = 0xf0c4,
        Cutlery = 0xf0f5,
        Dashboard = 0xf0e4,
        Database = 0xf1c0,
        Dedent = 0xf03b,
        Delicious = 0xf1a5,
        Desktop = 0xf108,
        Deviantart = 0xf1bd,
        Digg = 0xf1a6,
        Dollar = 0xf155,
        DotCircleO = 0xf192,
        Download = 0xf019,
        Dribbble = 0xf17d,
        Dropbox = 0xf16b,
        Drupal = 0xf1a9,
        Edit = 0xf044,
        Eject = 0xf052,
        EllipsisH = 0xf141,
        EllipsisV = 0xf142,
        Empire = 0xf1d1,
        Envelope = 0xf0e0,
        EnvelopeO = 0xf003,
        EnvelopeSquare = 0xf199,
        Eraser = 0xf12d,
        Eur = 0xf153,
        Euro = 0xf153,
        Exchange = 0xf0ec,
        Exclamation = 0xf12a,
        ExclamationCircle = 0xf06a,
        ExclamationTriangle = 0xf071,
        Expand = 0xf065,
        ExternalLink = 0xf08e,
        ExternalLinkSquare = 0xf14c,
        Eye = 0xf06e,
        EyeSlash = 0xf070,
        Facebook = 0xf09a,
        FacebookSquare = 0xf082,
        FastBackward = 0xf049,
        FastForward = 0xf050,
        Fax = 0xf1ac,
        Female = 0xf182,
        FighterJet = 0xf0fb,
        File = 0xf15b,
        FileArchiveO = 0xf1c6,
        FileAudioO = 0xf1c7,
        FileCodeO = 0xf1c9,
        FileExcelO = 0xf1c3,
        FileImageO = 0xf1c5,
        FileMovieO = 0xf1c8,
        FileO = 0xf016,
        FilePdfO = 0xf1c1,
        FilePhotoO = 0xf1c5,
        FilePictureO = 0xf1c5,
        FilePowerpointO = 0xf1c4,
        FileSoundO = 0xf1c7,
        FileText = 0xf15c,
        FileTextO = 0xf0f6,
        FileVideoO = 0xf1c8,
        FileWordO = 0xf1c2,
        FileZipO = 0xf1c6,
        FilesO = 0xf0c5,
        Film = 0xf008,
        Filter = 0xf0b0,
        Fire = 0xf06d,
        FireExtinguisher = 0xf134,
        Flag = 0xf024,
        FlagCheckered = 0xf11e,
        FlagO = 0xf11d,
        Flash = 0xf0e7,
        Flask = 0xf0c3,
        Flickr = 0xf16e,
        FloppyO = 0xf0c7,
        Folder = 0xf07b,
        FolderO = 0xf114,
        FolderOpen = 0xf07c,
        FolderOpenO = 0xf115,
        Font = 0xf031,
        Forward = 0xf04e,
        Foursquare = 0xf180,
        FrownO = 0xf119,
        Gamepad = 0xf11b,
        Gavel = 0xf0e3,
        Gbp = 0xf154,
        Ge = 0xf1d1,
        Gear = 0xf013,
        Gears = 0xf085,
        Gift = 0xf06b,
        Git = 0xf1d3,
        GitSquare = 0xf1d2,
        Github = 0xf09b,
        GithubAlt = 0xf113,
        GithubSquare = 0xf092,
        Gittip = 0xf184,
        Glass = 0xf000,
        Globe = 0xf0ac,
        Google = 0xf1a0,
        GooglePlus = 0xf0d5,
        GooglePlusSquare = 0xf0d4,
        GraduationCap = 0xf19d,
        Group = 0xf0c0,
        HSquare = 0xf0fd,
        HackerNews = 0xf1d4,
        HandODown = 0xf0a7,
        HandOLeft = 0xf0a5,
        HandORight = 0xf0a4,
        HandOUp = 0xf0a6,
        HddO = 0xf0a0,
        Header = 0xf1dc,
        Headphones = 0xf025,
        Heart = 0xf004,
        HeartO = 0xf08a,
        History = 0xf1da,
        Home = 0xf015,
        HospitalO = 0xf0f8,
        HourglassHalf = 0xf252,
        HourglassEnd = 0xf253,
        Html5 = 0xf13b,
        Image = 0xf03e,
        Inbox = 0xf01c,
        Indent = 0xf03c,
        Info = 0xf129,
        InfoCircle = 0xf05a,
        Inr = 0xf156,
        Instagram = 0xf16d,
        Institution = 0xf19c,
        Italic = 0xf033,
        Joomla = 0xf1aa,
        Jpy = 0xf157,
        Jsfiddle = 0xf1cc,
        Key = 0xf084,
        KeyboardO = 0xf11c,
        Krw = 0xf159,
        Language = 0xf1ab,
        Laptop = 0xf109,
        Leaf = 0xf06c,
        Legal = 0xf0e3,
        LemonO = 0xf094,
        LevelDown = 0xf149,
        LevelUp = 0xf148,
        LifeBouy = 0xf1cd,
        LifeRing = 0xf1cd,
        LifeSaver = 0xf1cd,
        LightbulbO = 0xf0eb,
        LineChart = 0xf201,
        Link = 0xf0c1,
        Linkedin = 0xf0e1,
        LinkedinSquare = 0xf08c,
        Linux = 0xf17c,
        List = 0xf03a,
        ListAlt = 0xf022,
        ListOl = 0xf0cb,
        ListUl = 0xf0ca,
        LocationArrow = 0xf124,
        Lock = 0xf023,
        LongArrowDown = 0xf175,
        LongArrowLeft = 0xf177,
        LongArrowRight = 0xf178,
        LongArrowUp = 0xf176,
        Magic = 0xf0d0,
        Magnet = 0xf076,
        MailForward = 0xf064,
        MailReply = 0xf112,
        MailReplyAll = 0xf122,
        Male = 0xf183,
        MapMarker = 0xf041,
        Maxcdn = 0xf136,
        Medkit = 0xf0Fa,
        MehO = 0xf11a,
        Microphone = 0xf130,
        MicrophoneSlash = 0xf131,
        Minus = 0xf068,
        MinusCircle = 0xf056,
        MinusSquare = 0xf146,
        MinusSquareO = 0xf147,
        Mobile = 0xf10b,
        MobilePhone = 0xf10b,
        Money = 0xf0d6,
        MoonO = 0xf186,
        MortarBoard = 0xf19d,
        Music = 0xf001,
        Navicon = 0xf0c9,
        Openid = 0xf19b,
        Outdent = 0xf03b,
        Pagelines = 0xf18c,
        PaperPlane = 0xf1d8,
        PaperPlaneO = 0xf1d9,
        Paperclip = 0xf0c6,
        Paragraph = 0xf1dd,
        Paste = 0xf0ea,
        Pause = 0xf04c,
        PauseCircle = 0xf28b,
        PauseCircleO = 0xf28c,
        Paw = 0xf1b0,
        Pencil = 0xf040,
        PencilSquare = 0xf14b,
        PencilSquareO = 0xf044,
        Phone = 0xf095,
        PhoneSquare = 0xf098,
        Photo = 0xf03e,
        PictureO = 0xf03e,
        PiedPiper = 0xf1a7,
        PiedPiperAlt = 0xf1a8,
        PiedPiperSquare = 0xf1a7,
        Pinterest = 0xf0d2,
        PinterestSquare = 0xf0d3,
        Plane = 0xf072,
        Play = 0xf04b,
        PlayCircle = 0xf144,
        PlayCircleO = 0xf01d,
        Plus = 0xf067,
        PlusCircle = 0xf055,
        PlusSquare = 0xf0fe,
        PlusSquareO = 0xf196,
        PowerOff = 0xf011,
        Print = 0xf02f,
        PuzzlePiece = 0xf12e,
        QQ = 0xf1d6,
        Rrcode = 0xf029,
        Ruestion = 0xf128,
        RuestionCircle = 0xf059,
        RuoteLeft = 0xf10d,
        RuoteRight = 0xf10e,
        Ra = 0xf1d0,
        Random = 0xf074,
        Rebel = 0xf1d0,
        Recycle = 0xf1b8,
        Reddit = 0xf1a1,
        RedditSquare = 0xf1a2,
        Refresh = 0xf021,
        Renren = 0xf18b,
        Reorder = 0xf0c9,
        Repeat = 0xf01e,
        Reply = 0xf112,
        ReplyAll = 0xf122,
        Retweet = 0xf079,
        Rmb = 0xf157,
        Road = 0xf018,
        Rocket = 0xf135,
        RotateLeft = 0xf0e2,
        RotateRight = 0xf01e,
        Rouble = 0xf158,
        Rss = 0xf09e,
        RssSquare = 0xf143,
        Rub = 0xf158,
        Ruble = 0xf158,
        Rupee = 0xf156,
        Save = 0xf0c7,
        Scissors = 0xf0c4,
        Search = 0xf002,
        SearchMinus = 0xf010,
        SearchPlus = 0xf00e,
        Send = 0xf1d8,
        SendO = 0xf1d9,
        Share = 0xf064,
        ShareAlt = 0xf1e0,
        ShareAltSquare = 0xf1e1,
        ShareSquare = 0xf14d,
        ShareSquareO = 0xf045,
        Shield = 0xf132,
        ShoppingCart = 0xf07a,
        SignIn = 0xf090,
        SignOut = 0xf08b,
        Signal = 0xf012,
        Sitemap = 0xf0e8,
        Skype = 0xf17e,
        Slack = 0xf198,
        Sliders = 0xf1de,
        SmileO = 0xf118,
        Sort = 0xf0dc,
        SortAlphaAsc = 0xf15d,
        SortAlphaDesc = 0xf15e,
        SortAmountAsc = 0xf160,
        SortAmountDesc = 0xf161,
        SortAsc = 0xf0de,
        SortDesc = 0xf0dd,
        SortDown = 0xf0dd,
        SortNumericAsc = 0xf162,
        SortNumericDesc = 0xf163,
        SortUp = 0xf0de,
        Soundcloud = 0xf1be,
        SpaceShuttle = 0xf197,
        Spinner = 0xf110,
        Spoon = 0xf1b1,
        Spotify = 0xf1bc,
        Square = 0xf0c8,
        SquareO = 0xf096,
        StackExchange = 0xf18d,
        StackOverflow = 0xf16c,
        Star = 0xf005,
        StarHalf = 0xf089,
        StarHalfEmpty = 0xf123,
        StarHalfFull = 0xf123,
        StarHalfO = 0xf123,
        StarO = 0xf006,
        Steam = 0xf1b6,
        SteamSquare = 0xf1b7,
        StepBackward = 0xf048,
        StepForward = 0xf051,
        Stethoscope = 0xf0f1,
        Stop = 0xf04d,
        StopCircle = 0xf28d,
        StopCircleO = 0xf28e,
        Strikethrough = 0xf0cc,
        Stumbleupon = 0xf1a4,
        StumbleuponCircle = 0xf1a3,
        Subscript = 0xf12c,
        Suitcase = 0xf0f2,
        SunO = 0xf185,
        Superscript = 0xf12b,
        Support = 0xf1cd,
        Table = 0xf0ce,
        Tablet = 0xf10a,
        Tachometer = 0xf0e4,
        Tag = 0xf02b,
        Tags = 0xf02c,
        Tasks = 0xf0ae,
        Taxi = 0xf1ba,
        TencentWeibo = 0xf1d5,
        Terminal = 0xf120,
        TextHeight = 0xf034,
        TextWidth = 0xf035,
        Th = 0xf00a,
        ThLarge = 0xf009,
        ThList = 0xf00b,
        ThumbTack = 0xf08d,
        ThumbsDown = 0xf165,
        ThumbsODown = 0xf088,
        ThumbsOUp = 0xf087,
        ThumbsUp = 0xf164,
        Ticket = 0xf145,
        Times = 0xf00d,
        TimesCircle = 0xf057,
        TimesCircleO = 0xf05c,
        Tint = 0xf043,
        ToggleDown = 0xf150,
        ToggleLeft = 0xf191,
        ToggleRight = 0xf152,
        ToggleUp = 0xf151,
        TrashO = 0xf014,
        Tree = 0xf1bb,
        Trello = 0xf181,
        Trophy = 0xf091,
        Truck = 0xf0d1,
        Try = 0xf195,
        Tumblr = 0xf173,
        TumblrSquare = 0xf174,
        TurkishLira = 0xf195,
        Twitter = 0xf099,
        TwitterSquare = 0xf081,
        Umbrella = 0xf0e9,
        Underline = 0xf0cd,
        Undo = 0xf0e2,
        University = 0xf19c,
        Unlink = 0xf127,
        Unlock = 0xf09c,
        UnlockAlt = 0xf13e,
        Unsorted = 0xf0dc,
        Upload = 0xf093,
        Usd = 0xf155,
        User = 0xf007,
        UserMd = 0xf0f0,
        Users = 0xf0c0,
        VideoCamera = 0xf03d,
        VimeoSquare = 0xf194,
        Vine = 0xf1ca,
        Vk = 0xf189,
        VolumeDown = 0xf027,
        VolumeOff = 0xf026,
        VolumeUp = 0xf028,
        Warning = 0xf071,
        Wechat = 0xf1d7,
        Weibo = 0xf18a,
        Weixin = 0xf1d7,
        Wheelchair = 0xf193,
        Windows = 0xf17a,
        Won = 0xf159,
        Wordpress = 0xf19a,
        Wrench = 0xf0ad,
        Xing = 0xf168,
        XingSquare = 0xf169,
        Yahoo = 0xf19e,
        Yen = 0xf157,
        Youtube = 0xf167,
        YoutubePlay = 0xf16a,
        YoutubeSquare = 0xf166,
    }
}

其他能用到的好像就一个计算圆角矩形的函数了

/// <summary>
/// 生成一个圆角矩形
/// </summary>
/// <param name="rect">矩形</param>
/// <param name="cRadius">弧度</param>
/// <returns></returns>
public static GraphicsPath RoundedRectangle(Rectangle rect, int cRadius)
{
    
    // 指定图形路径, 有一系列 直线/曲线 组成
    GraphicsPath myPath = new GraphicsPath();
    myPath.StartFigure();
    myPath.AddArc(new Rectangle(new Point(rect.X, rect.Y), new Size(2 * cRadius, 2 * cRadius)), 180, 90);                       // 左上角弧度
    myPath.AddLine(new Point(rect.X + cRadius, rect.Y), new Point(rect.Right - cRadius, rect.Y));                               // 顶部横线
    myPath.AddArc(new Rectangle(new Point(rect.Right - 2 * cRadius, rect.Y), new Size(2 * cRadius, 2 * cRadius)), 270, 90);     // 右上角弧度
    myPath.AddLine(new Point(rect.Right, rect.Y + cRadius), new Point(rect.Right, rect.Bottom - cRadius));                      // 右侧垂直线
    myPath.AddArc(new Rectangle(new Point(rect.Right - 2 * cRadius, rect.Bottom - 2 * cRadius), new Size(2 * cRadius, 2 * cRadius)), 0, 90);        // 右下角弧度
    myPath.AddLine(new Point(rect.Right - cRadius, rect.Bottom), new Point(rect.X + cRadius, rect.Bottom));                     // 底部横线
    myPath.AddArc(new Rectangle(new Point(rect.X, rect.Bottom - 2 * cRadius), new Size(2 * cRadius, 2 * cRadius)), 90, 90);     // 左下角弧度
    myPath.AddLine(new Point(rect.X, rect.Bottom - cRadius), new Point(rect.X, rect.Y + cRadius));                              // 左侧垂直线
    myPath.CloseFigure();
    return myPath;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值