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 FoldingIc
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值