winform实现自定义折叠面板控件
程序员文章站
2023-11-03 17:44:22
代码文件:https://github.com/Caijt/CollapsePanel 最近在学习做winform,想实现一个系统导航菜单,系统菜单以模块进行分组,菜单是树型结构。 效果类似旧版QQ的那种折叠面板,就是垂直并排很多个模块按钮,按其中一个模块就展开哪一个模块里面树型菜单,如下图所示,我 ......
代码文件:https://github.com/caijt/collapsepanel
最近在学习做winform,想实现一个系统导航菜单,系统菜单以模块进行分组,菜单是树型结构。
效果类似旧版qq的那种折叠面板,就是垂直并排很多个模块按钮,按其中一个模块就展开哪一个模块里面树型菜单,如下图所示,我先把我实现后的效果展示出来
一开始我以为这么常见的控件,winform里面肯定有,结果大失所望,居然没有,我刚学习winform,就遇到难题,好吧,那就学下怎么自定义控件,反正早晚要学的。
其实这个控件实现起来还是满简单的,没有太复杂的知识,就是把button控件跟treeview组合起来,主要调整它们的dock值,配合控件的bringtofront方法跟sendtoback方法
首先先定义我的菜单数据结构,其实就是一个很简单的树型结构,主要有个parentid来表明各节点的父子关系,根节点就是模块,根节点下面的子节点,就是模块的菜单。
namespace collapsepanelform { public class menudata { public int id { get; set; } //可为空,当为空时,说明当前节点是根节点 public int? parentid { get; set; } //模块或菜单的名称 public string name { get; set; } //这个是用于构建菜单对应form控件的路径的,可以利用反射实现打开匹配路径的form控件 public string path { get; set; } } }
下面是控件的完整代码,已进行注释,我相信你们看得明白的。
using system; using system.collections.generic; using system.componentmodel; using system.data; using system.linq; using system.windows.forms; namespace collapsepanelform { public partial class collapsepanel : usercontrol { /// <summary> /// 这是菜单列表数据,控件公开的属性必须定义以下这些特性,不然会出错,提示未标记为可序列化 /// </summary> [designerserializationvisibility(designerserializationvisibility.content)] [localizable(true)] [mergableproperty(false)] public list<menudata> menus { get; set; } /// <summary> /// 菜单双击事件 /// </summary> public event eventhandler menudoubleclick; /// <summary> /// 模块的按钮列表 /// </summary> private list<button> headerbuttons; /// <summary> /// 模块下的菜单,每一个模块下面的菜单对应一个treeview控件 /// </summary> private list<treeview> treeviews; /// <summary> /// 当前控件打开的模块索引值 /// </summary> private int? openmenuindex = null; /// <summary> /// 当模块处理打开状态时,模块名称后带的符号 /// </summary> private string openarrow = " <<"; /// <summary> /// 当模块处理关闭状态时,模块名称后带的符号 /// </summary> private string hidearrow = " >>"; public collapsepanel() { initializecomponent(); headerbuttons = new list<button>(); treeviews = new list<treeview>(); menus = new list<menudata>(); this.initmenus(); } /// <summary> /// 根据menus的数据初始化控件,就是动态增加button跟treeview控件 /// </summary> public void initmenus() { this.controls.clear(); //过滤出所有parentid为null的根节点,就是模块列表 foreach (var menu in menus.where(a => a.parentid == null)) { button headerbutton = new button(); headerbutton.dock = dockstyle.top; headerbutton.tag = menu.name; headerbutton.text = menu.name + hidearrow; headerbutton.tabstop = false; headerbutton.click += headerbutton_click; headerbuttons.add(headerbutton); this.controls.add(headerbutton); //这个bringtofront置于顶层方法对于布局很重要 headerbutton.bringtofront(); treeview tree = new treeview(); //用一个递归方法构建出nodes节点 tree.nodes.addrange(buildtreenode(menu.id, menu.path.substring(0, 1).toupper() + menu.path.substring(1))); tree.visible = false; tree.dock = dockstyle.fill; tree.nodemousedoubleclick += tree_doubleclick; treeviews.add(tree); this.controls.add(tree); } } private void tree_doubleclick(object sender, eventargs e) { if (menudoubleclick != null) { menudoubleclick(sender, e); } } /// <summary> /// 模块按钮单击事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void headerbutton_click(object sender, eventargs e) { var clickbutton = sender as button; //得出当前单击的模块按钮索引值 var clickmenuindex = headerbuttons.indexof(clickbutton); //如果当前单击的模块按钮索引值等于已经打开的模块索引值的话,那么当前模块要关闭,否则则打开 if (openmenuindex == clickmenuindex) { clickbutton.text = clickbutton.tag.tostring() + hidearrow; this.treeviews[clickmenuindex].hide(); openmenuindex = null; } else { //关闭之前打开的模块按钮 if (openmenuindex.hasvalue) { this.treeviews[openmenuindex.value].hide(); headerbuttons[openmenuindex.value].text = headerbuttons[openmenuindex.value].tag.tostring() + hidearrow; } clickbutton.text = clickbutton.tag.tostring() + openarrow; openmenuindex = clickmenuindex; this.treeviews[clickmenuindex].show(); } //以下的操作也很重要,根据当前单击的模块按钮索引值,小于这个值的模块按钮移到上面,大于的移到下面 int i = 0; foreach (var b in headerbuttons) { if (i <= clickmenuindex || openmenuindex == null) { b.dock = dockstyle.top; b.bringtofront(); } else { b.dock = dockstyle.bottom; b.sendtoback(); } i++; } //最后对应的treeview控件得置于顶层,这样布局就完美了 this.treeviews[clickmenuindex].bringtofront(); } /// <summary> /// 递归根据节点的id,构建出treenode数组,这个prefixpath是用来构建完美的path路径的 /// </summary> /// <param name="parentid"></param> /// <param name="prefixpath"></param> /// <returns></returns> private treenode[] buildtreenode(int parentid, string prefixpath) { list<treenode> nodelist = new list<treenode>(); menus.foreach(m => { if (m.parentid == parentid) { //拼接当前节点完整路径,然后再传给递归方法 string path = prefixpath + "." + m.path.substring(0, 1).toupper() + m.path.substring(1); treenode node = new treenode(); node.text = m.name; node.tag = path; node.nodes.addrange(buildtreenode(m.id, path)); nodelist.add(node); } }); return nodelist.toarray(); } } }
上一篇: 毛笋炒肉片怎么做好吃?这些步骤缺一不可!