前提
入行已经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 /// 5 /// 键 6 /// 7 public string Key { get; set; } 8 /// 9 /// 文字10 /// 11 public string Text { get; set; }12 /// 13 /// 子节点14 /// 15 public List Childrens { get; set; }16 /// 17 /// 自定义数据源,一般用于扩展展示,比如定义节点图片等18 /// 19 public object DataSource { get; set; }20 21 }
再定义一个接口来约束
1 public interface IMenuItem 2 { 3 event EventHandler SelectedItem; 4 MenuItemEntity DataSource { get; set; } 5 /// 6 /// 设置样式 7 /// 8 /// key:属性名称,value:属性值 9 void SetStyle(Dictionary styles);10 /// 11 /// 设置选中样式12 /// 13 /// 是否选中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 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 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 { /// /// 平铺 /// Fill = 1, /// /// 顶部对齐 /// Top = 2, }
这个枚举的作用就是改变样式,默认是Fill,也就是子节点面板填充铺满,选中父节点上面的兄弟节点顶端对齐,下面的兄弟节点低端对齐,当父节点较多时候就会出现子节点无法显示的问题,这个时候使用Top就可以了,所有父节点顶端对齐
先看下有哪些属性
1 /// 2 /// 选中项事件 3 /// 4 public event EventHandler SelectedItem; 5 private Type m_parentItemType = typeof(UCMenuParentItem); 6 /// 7 /// 父类节点类型 8 /// 9 public Type ParentItemType10 {11 get { return m_parentItemType; }12 set13 {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 /// 25 /// 子类节点类型26 /// 27 public Type ChildrenItemType28 {29 get { return m_childrenItemType; }30 set31 {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 m_parentItemStyles;41 /// 42 /// 父类节点样式设置,key:属性名称,value:属性值43 /// 44 public Dictionary ParentItemStyles45 {46 get { return m_parentItemStyles; }47 set { m_parentItemStyles = value; }48 }49 50 private Dictionary m_childrenItemStyles;51 /// 52 /// 子类节点样式设置,key:属性名称,value:属性值53 /// 54 public Dictionary ChildrenItemStyles55 {56 get { return m_childrenItemStyles; }57 set { m_childrenItemStyles = value; }58 }59 60 private List m_dataSource;61 /// 62 /// 数据源63 /// 64 public List DataSource65 {66 get { return m_dataSource; }67 set68 {69 m_dataSource = value;70 71 ReloadItems();72 }73 }74 private bool m_isShowFirstItem = true;75 /// 76 /// 是否自动展开第一个节点77 /// 78 public bool IsShowFirstItem79 {80 get { return m_isShowFirstItem; }81 set { m_isShowFirstItem = value; }82 }83 84 private MenuStyle m_menuStyle = MenuStyle.Fill;85 /// 86 /// 菜单样式87 /// 88 public MenuStyle MenuStyle89 {90 get { return m_menuStyle; }91 set { m_menuStyle = value; }92 }93 94 private List m_lstParentItems = new List();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 else31 {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 finally39 {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 else117 {118 m_panChildren.Controls.Clear();119 m_panChildren.Height = 0;120 }121 }122 }123 finally124 {125 ControlHelper.FreezeControl(this, false);126 }127 }
代码就这么多
用处及效果
示例代码
List lstMenu = new List(); for (int i = 0; i < 5; i++) { MenuItemEntity item = new MenuItemEntity() { Key = "p" + i.ToString(), Text = "菜单项" + i, DataSource = "这里编写一些自定义的数据源,用于扩展" }; item.Childrens = new List(); 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 点个星星吧