(十三)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 & 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个小技巧,满满都是干货,记得...
- 一周热门
- 最近发表
-
- 最新全国高校名单一键查!报志愿就用它了
- (十三)C#WinFrom自定义控件系列-导航菜单
- 录取结果早知道!安徽高考录取结果这样查询……
- CS架构开发之-WPF平台权限菜单(关于wps权限的设定有哪些是可行的)
- 解锁C#新技能:巧用钩子实现Winform窗体智能关闭
- DevExpress WinForms——支持HTML & CSS模板全新的DirectX表单
- 如何打造优质 Web 表单(web表单简单代码)
- Excel常用技能分享与探讨(5-宏与VBA简介 VBA之用户窗体-避坑指南)
- Excel中窗体的新建与设置;窗体控件新增及代码的录入。
- Excel常用技能分享与探讨(5-宏与VBA简介 VBA之用户窗体-复选框)
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- HTML 参考手册 (28)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML常用标签 (29)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- HTML button formtarget 属性 (30)