百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

(十三)C#WinFrom自定义控件系列-导航菜单

zhezhongyun 2025-05-09 22:53 4 浏览

前提

入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章。

本系列文章将讲解各种控件的开发及思路,欢迎各位批评指正。

此系列控件开发教程将全部在原生控件基础上进行重绘开发,目标的扁平化、漂亮、支持触屏。

如果有什么好的建议也可以评论留言来交流。

源码地址:

GitHub:https://github.com/kwwwvagaa/NetWinformControl

码云:
https://gitee.com/kwwwvagaa/net_winform_custom_control.git

如果觉得写的还行,请点个 star 支持一下吧

欢迎前来交流探讨: 企鹅群568015492

目录

http://toutiao.com/item/6824291838963220999/

准备工作

有时候我们需要左侧的导航菜单,那么来整一个吧

先来分析分析,导航菜单一般分为2级或多级,如果是多级的话 用前面的treeview更合适,这里只做2级,为了父子节点样式更方便控制,我们分别实现父子节点。

为了更加的Open,我们使用接口来定义一下

开始

定义一个节点数据绑定实体

 1     [Serializable]
 2     public class MenuItemEntity
 3     {
 4         /// <summary>
 5         /// 键
 6         /// </summary>
 7         public string Key { get; set; }
 8         /// <summary>
 9         /// 文字
10         /// </summary>
11         public string Text { get; set; }
12         /// <summary>
13         /// 子节点
14         /// </summary>
15         public List<MenuItemEntity> Childrens { get; set; }
16         /// <summary>
17         /// 自定义数据源,一般用于扩展展示,比如定义节点图片等
18         /// </summary>
19         public object DataSource { get; set; }
20 
21     }

再定义一个接口来约束

 1  public interface IMenuItem
 2     {
 3         event EventHandler SelectedItem;
 4         MenuItemEntity DataSource { get; set; }
 5         /// <summary>
 6         /// 设置样式
 7         /// </summary>
 8         /// <param name="styles">key:属性名称,value:属性值</param>
 9         void SetStyle(Dictionary<string, object> styles);
10         /// <summary>
11         /// 设置选中样式
12         /// </summary>
13         /// <param name="blnSelected">是否选中</param>
14         void SetSelectedStyle(bool blnSelected);
15     }

首先看父节点定义,添加一个用户控件,命名UCMenuParentItem,并且实现接口IMenuItem

  public event EventHandler SelectedItem;

        private MenuItemEntity m_dataSource;
        public MenuItemEntity DataSource
        {
            get
            {
                return m_dataSource;
            }
            set
            {
                m_dataSource = value;
                if (value != null)
                {
                    lblTitle.Text = value.Text;
                }
            }
        }
 public void SetStyle(Dictionary<string, object> styles)
        {
            Type t = this.GetType();
            foreach (var item in styles)
            {
                var pro = t.GetProperty(item.Key);
                if (pro != null && pro.CanWrite)
                {
                    try
                    {
                        pro.SetValue(this, item.Value, null);
                    }
                    catch (Exception ex)
                    {
                        throw new Exception("菜单元素设置样式异常", ex);
                    }
                }
            }
        }

        public void SetSelectedStyle(bool blnSelected)
        {
            if (blnSelected)
            {
                this.lblTitle.Image = Properties.Resources.sanjiao1;
            }
            else
            {
                this.lblTitle.Image = Properties.Resources.sanjiao2;
            }
        }

然后处理下点击事件

 lblTitle.MouseDown += lblTitle_MouseDown;

  void lblTitle_MouseDown(object sender, MouseEventArgs e)
        {
            if (SelectedItem != null)
            {
                SelectedItem(this, e);
            }
        }

这样就完事了

设计效果就是这样子的了

父节点弄好了,下面就是子节点了

添加用户控件,命名UCMenuChildrenItem,实现接口IMenuItem

 public event EventHandler SelectedItem;

        private MenuItemEntity m_dataSource;
        public MenuItemEntity DataSource
        {
            get
            {
                return m_dataSource;
            }
            set
            {
                m_dataSource = value;
                if (value != null)
                {
                    lblTitle.Text = value.Text;
                }
            }
        }

 public void SetStyle(Dictionary<string, object> styles)
        {
            Type t = this.GetType();
            foreach (var item in styles)
            {
                var pro = t.GetProperty(item.Key);
                if (pro != null && pro.CanWrite)
                {
                    try
                    {
                        pro.SetValue(this, item.Value, null);
                    }
                    catch (Exception ex)
                    {
                        throw new Exception("菜单元素设置样式异常", ex);
                    }
                }
            }
        }

处理下点击事件

1  this.lblTitle.MouseDown += lblTitle_MouseDown;
2 
3        void lblTitle_MouseDown(object sender, MouseEventArgs e)
4         {
5             if (SelectedItem != null)
6             {
7                 SelectedItem(this, null);
8             }
9         }

这样就完成了

设计效果是这样子的

你们有没有发现,父节点和子节点代码非常的相似呢?是的,基本上都是一样的,他们都实现了接口IMenuItem,

那既然如此为什么还要分为2个,这不是代码冗余了吗?这么做的好处就是你可以更方便的控制节点的设计样式,假如有一天,子节点你不想用文字了,你先够用图片呢?

到此,节点定义已经完成了,剩下的就是处理列表了,继续往下看吧。

定义一个用户控件,命名UCMenu

首先定义一个枚举

public enum MenuStyle
    {
        /// <summary>
        /// 平铺
        /// </summary>
        Fill = 1,
        /// <summary>
        /// 顶部对齐
        /// </summary>
        Top = 2,
    }

这个枚举的作用就是改变样式,默认是Fill,也就是子节点面板填充铺满,选中父节点上面的兄弟节点顶端对齐,下面的兄弟节点低端对齐,当父节点较多时候就会出现子节点无法显示的问题,这个时候使用Top就可以了,所有父节点顶端对齐

先看下有哪些属性

 1  /// <summary>
 2         /// 选中项事件
 3         /// </summary>
 4         public event EventHandler SelectedItem;
 5         private Type m_parentItemType = typeof(UCMenuParentItem);
 6         /// <summary>
 7         /// 父类节点类型
 8         /// </summary>
 9         public Type ParentItemType
10         {
11             get { return m_parentItemType; }
12             set
13             {
14                 if (value == null)
15                     return;
16                 if (!typeof(IMenuItem).IsAssignableFrom(value) || !value.IsSubclassOf(typeof(Control)))
17                     throw new Exception("节点控件没有实现IMenuItem接口");
18                 m_parentItemType = value;
19 
20             }
21         }
22 
23         private Type m_childrenItemType = typeof(UCMenuChildrenItem);
24         /// <summary>
25         /// 子类节点类型
26         /// </summary>
27         public Type ChildrenItemType
28         {
29             get { return m_childrenItemType; }
30             set
31             {
32                 if (value == null)
33                     return;
34                 if (!typeof(IMenuItem).IsAssignableFrom(value) || !value.IsSubclassOf(typeof(Control)))
35                     throw new Exception("节点控件没有实现IMenuItem接口");
36                 m_childrenItemType = value;
37             }
38         }
39 
40         private Dictionary<string, object> m_parentItemStyles;
41         /// <summary>
42         /// 父类节点样式设置,key:属性名称,value:属性值
43         /// </summary>
44         public Dictionary<string, object> ParentItemStyles
45         {
46             get { return m_parentItemStyles; }
47             set { m_parentItemStyles = value; }
48         }
49 
50         private Dictionary<string, object> m_childrenItemStyles;
51         /// <summary>
52         /// 子类节点样式设置,key:属性名称,value:属性值
53         /// </summary>
54         public Dictionary<string, object> ChildrenItemStyles
55         {
56             get { return m_childrenItemStyles; }
57             set { m_childrenItemStyles = value; }
58         }
59 
60         private List<MenuItemEntity> m_dataSource;
61         /// <summary>
62         /// 数据源
63         /// </summary>
64         public List<MenuItemEntity> DataSource
65         {
66             get { return m_dataSource; }
67             set
68             {
69                 m_dataSource = value;
70 
71                 ReloadItems();
72             }
73         }
74         private bool m_isShowFirstItem = true;
75         /// <summary>
76         /// 是否自动展开第一个节点
77         /// </summary>
78         public bool IsShowFirstItem
79         {
80             get { return m_isShowFirstItem; }
81             set { m_isShowFirstItem = value; }
82         }
83 
84         private MenuStyle m_menuStyle = MenuStyle.Fill;
85         /// <summary>
86         /// 菜单样式
87         /// </summary>
88         public MenuStyle MenuStyle
89         {
90             get { return m_menuStyle; }
91             set { m_menuStyle = value; }
92         }
93 
94         private List<Control> m_lstParentItems = new List<Control>();
95 
96         private IMenuItem m_selectParentItem = null;
97         private IMenuItem m_selectChildrenItem = null;
98 
99         private Panel m_panChildren = null;

数据源改变时需要重新加载

 1 private void ReloadItems()
 2         {
 3             try
 4             {
 5                 ControlHelper.FreezeControl(this, true);
 6                 this.Controls.Clear();
 7                 m_lstParentItems.Clear();
 8                 if (m_dataSource != null && m_dataSource.Count > 0)
 9                 {
10                     foreach (var parent in m_dataSource)
11                     {
12                         IMenuItem parentItem = (IMenuItem)Activator.CreateInstance(m_parentItemType);
13                         parentItem.DataSource = parent;
14                         if (m_parentItemStyles != null)
15                             parentItem.SetStyle(m_parentItemStyles);
16                         parentItem.SelectedItem += parentItem_SelectedItem;
17                         Control c = parentItem as Control;
18                         c.Dock = DockStyle.Top;
19                         this.Controls.Add(c);
20                         this.Controls.SetChildIndex(c, 0);
21                         m_lstParentItems.Add(c);
22                     }
23                 }
24                 m_panChildren = new Panel();
25                 if (m_menuStyle == HZH_Controls.Controls.MenuStyle.Fill)
26                 {
27                     m_panChildren.Dock = DockStyle.Fill;
28                     m_panChildren.Height = 0;
29                 }
30                 else
31                 {
32                     m_panChildren.Dock = DockStyle.Top;
33                     m_panChildren.Height = 0;
34                 }
35                 m_panChildren.AutoScroll = true;
36                 this.Controls.Add(m_panChildren);
37             }
38             finally
39             {
40                 ControlHelper.FreezeControl(this, false);
41             }
42 
43             if (m_isShowFirstItem && m_lstParentItems != null && m_lstParentItems.Count > 0)
44             {
45                 parentItem_SelectedItem(m_lstParentItems[0], null);
46             }
47         }

选中父节点时候加载子节点

  1  void parentItem_SelectedItem(object sender, EventArgs e)
  2         {
  3             this.FindForm().ActiveControl = this;
  4             IMenuItem item = sender as IMenuItem;
  5             if (m_lstParentItems.Contains(sender as Control))
  6             {
  7                 if (m_selectParentItem != item)
  8                 {
  9                     if (m_selectParentItem != null)
 10                     {
 11                         m_selectParentItem.SetSelectedStyle(false);
 12                     }
 13                     m_selectParentItem = item;
 14                     m_selectParentItem.SetSelectedStyle(true);
 15                     SetChildrenControl(m_selectParentItem);
 16                 }
 17                 else
 18                 {
 19                     m_selectParentItem.SetSelectedStyle(false);
 20                     m_selectParentItem = null;
 21                     SetChildrenControl(m_selectParentItem, false);
 22                 }
 23             }
 24             else if (m_panChildren.Controls.Contains(sender as Control))
 25             {
 26                 if (m_selectChildrenItem != item)
 27                 {
 28                     if (m_selectChildrenItem != null)
 29                     {
 30                         m_selectChildrenItem.SetSelectedStyle(false);
 31                     }
 32                     m_selectChildrenItem = item;
 33                     m_selectChildrenItem.SetSelectedStyle(true);
 34                 }
 35             }
 36             if (SelectedItem != null)
 37             {
 38                 SelectedItem(sender, e);
 39             }
 40         }
 41 
 42         private void SetChildrenControl(IMenuItem menuitem, bool blnChildren = true)
 43         {
 44             try
 45             {
 46                 ControlHelper.FreezeControl(this, true);
 47                 if (m_menuStyle == HZH_Controls.Controls.MenuStyle.Fill)
 48                 {
 49                     if (blnChildren)
 50                     {
 51                         Control cMenu = menuitem as Control;
 52                         int index = m_lstParentItems.IndexOf(cMenu);
 53                         for (int i = 0; i <= index; i++)
 54                         {
 55                             m_lstParentItems[i].Dock = DockStyle.Top;
 56                             this.Controls.SetChildIndex(m_lstParentItems[i], 1);
 57                         }
 58                         for (int i = index + 1; i < m_lstParentItems.Count; i++)
 59                         {
 60                             m_lstParentItems[i].Dock = DockStyle.Bottom;
 61                             this.Controls.SetChildIndex(m_lstParentItems[i], m_lstParentItems.Count);
 62                         }
 63                         m_panChildren.Controls.Clear();
 64                         int intItemHeigth = 0;
 65                         foreach (var item in menuitem.DataSource.Childrens)
 66                         {
 67                             IMenuItem parentItem = (IMenuItem)Activator.CreateInstance(m_childrenItemType);
 68                             parentItem.DataSource = item;
 69                             if (m_childrenItemStyles != null)
 70                                 parentItem.SetStyle(m_childrenItemStyles);
 71                             parentItem.SelectedItem += parentItem_SelectedItem;
 72                             Control c = parentItem as Control;
 73                             if (intItemHeigth == 0)
 74                                 intItemHeigth = c.Height;
 75                             c.Dock = DockStyle.Top;
 76                             m_panChildren.Controls.Add(c);
 77                             m_panChildren.Controls.SetChildIndex(c, 0);
 78                         }
 79                         //m_panChildren.MinimumSize = new Size(0, menuitem.DataSource.Childrens.Count * intItemHeigth);
 80                     }
 81                     else
 82                     {
 83                         m_panChildren.Controls.Clear();
 84                         foreach (var item in m_lstParentItems)
 85                         {
 86                             item.Dock = DockStyle.Top;
 87                             this.Controls.SetChildIndex(item, 1);
 88                         }
 89                     }
 90                 }
 91                 else
 92                 {
 93                     if (blnChildren)
 94                     {
 95                         Control cMenu = menuitem as Control;
 96                         int index = m_lstParentItems.IndexOf(cMenu);
 97                         this.Controls.SetChildIndex(m_panChildren, m_lstParentItems.Count - index - 1);
 98                         m_panChildren.Controls.Clear();
 99                         int intItemHeigth = 0;
100                         foreach (var item in menuitem.DataSource.Childrens)
101                         {
102                             IMenuItem parentItem = (IMenuItem)Activator.CreateInstance(m_childrenItemType);
103                             parentItem.DataSource = item;
104                             if (m_childrenItemStyles != null)
105                                 parentItem.SetStyle(m_childrenItemStyles);
106                             parentItem.SelectedItem += parentItem_SelectedItem;
107                             Control c = parentItem as Control;
108                             if (intItemHeigth == 0)
109                                 intItemHeigth = c.Height;
110                             c.Dock = DockStyle.Top;
111                             m_panChildren.Controls.Add(c);
112                             m_panChildren.Controls.SetChildIndex(c, 0);
113                         }
114                         m_panChildren.Height = menuitem.DataSource.Childrens.Count * intItemHeigth;
115                     }
116                     else
117                     {
118                         m_panChildren.Controls.Clear();
119                         m_panChildren.Height = 0;
120                     }
121                 }
122             }
123             finally
124             {
125                 ControlHelper.FreezeControl(this, false);
126             }
127         }

代码就这么多

用处及效果

示例代码

List<MenuItemEntity> lstMenu = new List<MenuItemEntity>();
            for (int i = 0; i < 5; i++)
            {
                MenuItemEntity item = new MenuItemEntity()
                {
                    Key = "p" + i.ToString(),
                    Text = "菜单项" + i,
                    DataSource = "这里编写一些自定义的数据源,用于扩展"
                };
                item.Childrens = new List<MenuItemEntity>();
                for (int j = 0; j < 5; j++)
                {
                    MenuItemEntity item2 = new MenuItemEntity()
                    {
                        Key = "c" + i.ToString(),
                        Text = "菜单子项" + i + "-" + j,
                        DataSource = "这里编写一些自定义的数据源,用于扩展"
                    };
                    item.Childrens.Add(item2);
                }
                lstMenu.Add(item);
            }
            this.ucMenu1.MenuStyle = MenuStyle.Top;  
            this.ucMenu1.DataSource = lstMenu;

最后的话

如果你喜欢的话,请到 码云或Github 点个星星吧

相关推荐

最新全国高校名单一键查!报志愿就用它了

7月9日,教育部公布最新全国高等学校名单。截至2020年6月30日,全国高等学校共计3005所,其中:普通高等学校2740所,含本科院校1258所、高职(专科)院校1482所;成人高等学校265所。高...

(十三)C#WinFrom自定义控件系列-导航菜单

前提入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章。本系列文章将讲解各种控件的开发及思路,欢迎各位批评指正。此系列控件开发教程将全部在原生控件基础上进行重绘开发,目标的扁平化...

录取结果早知道!安徽高考录取结果这样查询……

据安徽省教育招生考试院消息,从8月9晚23:00起,将正式开通2020年高考录取结果查询,考生可陆续查询到录取结果。8月8日起,安徽省2020年普通高校招生录取工作已全面展开,每位考生的录取须经过投档...

CS架构开发之-WPF平台权限菜单(关于wps权限的设定有哪些是可行的)

CS架构开发之-WPF平台权限菜单;在之前的基础上,使用EFCode添加本地SQLite数据库;使软件能够保存数据到本地,以及后期其他业务需要和服务器进行数据上传和下载。后期在改造升级为全完仓储...

解锁C#新技能:巧用钩子实现Winform窗体智能关闭

一、引言在Winform应用程序的开发中,我们常常会遇到一些有趣且实用的需求。比如,当用户长时间没有操作键盘和鼠标时,自动关闭Winform窗体,以此来节省系统资源或者实现特定的业务逻辑。实...

DevExpress WinForms——支持HTML &amp; CSS模板全新的DirectX表单

DevExpressWinForms控件附带了许多标准的System.Windows.Forms.Form对象对应的对象,在v22.1版本发布新的表单选项之前,让我们一起看看当前所有可用的表单选项。...

如何打造优质 Web 表单(web表单简单代码)

来人人都是产品经理【起点学院】,BAT实战派产品总监手把手系统带你学产品、学运营。这篇文章算是笔者交的一份读书笔记,与CRM系统打交道了这么久,表单天天见。如果表单有感情的话,我猜它应该都不想再看...

Excel常用技能分享与探讨(5-宏与VBA简介 VBA之用户窗体-避坑指南)

书接上文,之前是VBA用户窗体中常用控件的详细解析,涵盖核心属性、关键事件、典型应用场景及代码示例,下面是窗体使用中遇到的一些问题点汇总。五、避坑指南:五大常见问题5.1、控件操作相关问题1:未初始化...

Excel中窗体的新建与设置;窗体控件新增及代码的录入。

由于前期分享的好多作品中都运用到了窗体。有很多朋友都私信小编,如何更改窗体里面的文字以及设置窗体背景图片。为此小编将根据自己的经验总结,在这里给大家做一个详细的讲解。1、新建窗体:打开Excel表格后...

Excel常用技能分享与探讨(5-宏与VBA简介 VBA之用户窗体-复选框)

书接上文,以下是VBA用户窗体中常用控件的详细解析,涵盖核心属性、关键事件、典型应用场景及代码示例,助您精准掌握每个控件的使用方法。三、核心控件精讲3.6.复选框(CheckBox)3.6.1、复选...

VBA代码对单选框条件控制方法及循环的使用方法

VBA代码对单选框条件的控制方法在上一篇文章的基础上,在创建好选项组之后,我们来看一下在单击事件后,它所返回的值是什么样的,首先右键单击选项组框,在弹出的菜单中选择事件生成器:弹出代码窗口后,在单击事...

实现窗体录入数据的代码(excel窗体录入数据实例)

【分享成果,随喜正能量】学会坚强,这世上,真正在乎你的人并不多,相反,倒是有很多人都在等着看你的笑话。就算遇到天大的麻烦,也不要自暴自弃。你不勇敢,没人替你坚强。《VBA之Excel应用》是非常经典的...

从交互的角度讲讲弹窗(中)(确认弹窗设计)

编辑导读:弹窗是吸引注意力的一种方式,不管是PC端还是移动端都广泛使用。本文作者从交互设计的角度,对弹窗进行分析,与你分享。上期我们小聊了一下弹窗的定义与使用的常见场景,本期我们来聊点实际的:弹窗的内...

Excel批量导入图片,还能一键将图片固定到单元格!这是什么操作

私信回复关键词【福利】,获取丰富办公资源!助你高效办公早下班!大家好,我是懂点Excel的小E~初入「江湖」,还请大家多多关照!今天我们来学学Excel图片的6个小技巧,满满都是干货,记得...

老板让我把图片放到Excel表格中,批量插入效率高

私信回复关键词【福利】,获取丰富办公资源!助你高效办公早下班!大家好,我是懂点Excel的小E~初入「江湖」,还请大家多多关照!今天我们来学学Excel图片的6个小技巧,满满都是干货,记得...