一、前面的话
在上一篇博文自己动手写工具----XSmartNote [Beta 3.0]中,用到了若干个自定义控件,其中包含用于显示Note内容的简单的Label扩展控件,用于展示标签内容的label扩展控件,还有包含自定义事件的含checkbox的控件。自定义控件的好处就是其灵活程度很高,不但可以扩展控件的外观,还可以扩展控件的事件,甚至从底层拦截Windows消息进行处理,这也是我喜欢自己写控件的原因。至于自定义控件的几种形式在这里就不说了,有兴趣的小伙伴可以百度一下,下面来看看这些控件的实现过程。
二、控件效果
这部分控件除了上面的TextBoxEx,其他主要是基于Label进行扩展的,有的包含事件,有的包含动态效果,还有的控件只是用于简单地显示信息。
三、控件实现
1、 LabelEx控件
LabelEx控件既包含简单的动态效果,也有自己的事件,首先新建用户控件,继承需要扩展的控件,这里继承的是Label控件,这样就可以使用Label的所有的成员。完善构造函数,并绑定label的LabelEx_TextChanged事件,绑定事件是为了当文字长度发生变化时,LabelEx能够自动适应。然后重写OnPaint方法,绘制自己需要的外观,注意base.OnPaint(e)这句,如果不加上这句代码,控件上的文本是不会显示的。具体看代码:
1 public partial class LabelEx : Label 2 { 3 4 #region PARAMS 5 private const int WIDTH = 0x0226;//550 6 private const int HEIGHT = 0x002F;//47 7 #endregion 8 9 #region CONSTRUCTOR 10 public LabelEx() 11 : base() 12 { 13 this.Font = new Font(new FontFamily("微软雅黑"), 10.0f); 14 InitializeComponent(); 15 this.AutoSize = false; 16 //设置内边距 17 this.Padding = new Padding(5); 18 //设置外边距 19 this.Margin = new Padding(5); 20 this.Width = WIDTH; 21 this.Height = HEIGHT; 22 this.TextChanged += LabelEx_TextChanged; 23 } 24 #endregion 25 26 27 #region Property 28 public Color NormalBorderColor { get; set; } 29 public Color HighLightBorderColor { get; set; } 30 #endregion 31 32 #region EVENTS 33 private void LabelEx_TextChanged(object sender, EventArgs e) 34 { 35 //文字变化后改变一下当前的大小 36 System.Drawing.Size ps = GetPreferredSize(this.Size); 37 if (ps.Height < HEIGHT) 38 { 39 ps.Height = HEIGHT; 40 } 41 this.Size = new System.Drawing.Size(this.Width, ps.Height); 42 } 43 #endregion 44 45 #region OVERRIDE 46 protected override void OnPaint(PaintEventArgs e) 47 { 48 base.OnPaint(e);//解决了文字不显示的问题 49 Graphics g = e.Graphics; 50 int x = this.Width; 51 int y = this.Height; 52 Point leftTop = new Point(0, 0); 53 Point rightTop = new Point(x - 1, 0); 54 Point leftBottom = new Point(0, y - 1); 55 Point rightBottom = new Point(x - 1, y - 1); 56 //绘制四个边缘,避免被背景色填充 57 g.DrawLine(new Pen(Color.White), leftTop, rightTop); 58 g.DrawLine(new Pen(Color.White), leftBottom, rightBottom); 59 g.DrawLine(new Pen(Color.White), leftTop, leftBottom); 60 g.DrawLine(new Pen(Color.White), rightTop, rightBottom); 61 //画上边缘 62 for (int i = 0; i < x - 1; i += 3) 63 { 64 g.FillRectangle(new SolidBrush(Color.Black), new Rectangle(i, 0, 2, 1)); 65 } 66 67 //画下边缘 68 for (int m = 0; m < x - 1; m += 3) 69 { 70 g.FillRectangle(new SolidBrush(Color.Black), new Rectangle(m, y - 1, 2, 1)); 71 } 72 73 //画左边缘 74 for (int i = 0; i < y - 1; i += 3) 75 { 76 g.FillRectangle(new SolidBrush(Color.Black), new Rectangle(0, i, 1, 2)); 77 } 78 79 //画右边缘 80 for (int i = 0; i < y - 1; i += 3) 81 { 82 g.FillRectangle(new SolidBrush(Color.Black), new Rectangle(x - 1, i, 1, 2)); 83 } 84 } 85 86 public override bool AutoSize 87 { 88 get 89 { 90 return false; 91 } 92 } 93 #endregion 94 }
2、LabelExS控件
上面的LabelEx控件只是简单地处理了一下Label控件,而LabelExS控件继承自LabelEx控件,并添加了一些简单的状态控制。这里的重绘方法,和上面的控件大概是一致的,只是稍微做出了修改。不同状态下的绘制代码:
1 /// <summary> 2 /// 激活态 3 /// </summary> 4 /// <param name="g"></param> 5 private void DrawHighLightBorder(Graphics g) 6 { 7 int x = this.Width; 8 int y = this.Height; 9 //画上边缘 10 for (int i = 0; i < x - 1; i++) 11 { 12 g.FillRectangle(new SolidBrush(base.HighLightBorderColor), new Rectangle(i, 0, 1, 1)); 13 } 14 15 //画下边缘 16 for (int m = 0; m < x - 1; m++) 17 { 18 g.FillRectangle(new SolidBrush(base.HighLightBorderColor), new Rectangle(m, y - 1, 1, 1)); 19 } 20 21 //画左边缘 22 for (int i = 0; i < y - 1; i++) 23 { 24 g.FillRectangle(new SolidBrush(base.HighLightBorderColor), new Rectangle(0, i, 1, 1)); 25 } 26 27 //画右边缘 28 for (int i = 0; i < y - 1; i++) 29 { 30 g.FillRectangle(new SolidBrush(base.HighLightBorderColor), new Rectangle(x - 1, i, 1, 1)); 31 } 32 } 33 34 /// <summary> 35 /// 常规态 36 /// </summary> 37 /// <param name="g"></param> 38 private void DrawNormalBorder(Graphics g) 39 { 40 int x = this.Width; 41 int y = this.Height; 42 //画上边缘 43 for (int i = 0; i < x - 1; i++) 44 { 45 g.FillRectangle(new SolidBrush(base.NormalBorderColor), new Rectangle(i, 0, 1, 1)); 46 } 47 48 //画下边缘 49 for (int m = 0; m < x - 1; m++) 50 { 51 g.FillRectangle(new SolidBrush(base.NormalBorderColor), new Rectangle(m, y - 1, 1, 1)); 52 } 53 54 //画左边缘 55 for (int i = 0; i < y - 1; i++) 56 { 57 g.FillRectangle(new SolidBrush(base.NormalBorderColor), new Rectangle(0, i, 1, 1)); 58 } 59 60 //画右边缘 61 for (int i = 0; i < y - 1; i++) 62 { 63 g.FillRectangle(new SolidBrush(base.NormalBorderColor), new Rectangle(x - 1, i, 1, 1)); 64 } 65 }
根据状态进行绘制:
1 protected override void OnPaint(PaintEventArgs e) 2 { 3 base.OnPaint(e); 4 switch (State) 5 { 6 case 0: 7 DrawNormalBorder(g); 8 break; 9 case 1: 10 DrawHighLightBorder(g); 11 break; 12 default: 13 DrawNormalBorder(g); 14 break; 15 } 16 }
鼠标事件控制,进入或离开的时候,强制区域无效并更新状态,这样OnPaint函数会根据状态进行重新绘制:
1 /// <summary> 2 /// 鼠标进入事件 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void LabelExSolidBorder_MouseEnter(object sender, EventArgs e) 7 { 8 State = 1; 9 base.ForeColor = Color.Orange; 10 base.HighLightBorderColor = Color.Orange;// ★可配置 11 this.Invalidate(); 12 } 13 14 /// <summary> 15 /// 鼠标离开事件 16 /// </summary> 17 /// <param name="sender"></param> 18 /// <param name="e"></param> 19 private void LabelExSolidBorder_MouseLeave(object sender, EventArgs e) 20 { 21 State = 0; 22 base.ForeColor = Color.Brown; 23 base.NormalBorderColor = Color.White;// ★可配置 24 this.Invalidate(); 25 }
为控件绑定Click事件,执行自定义操作:
1 /// <summary> 2 /// 观察者模式,鼠标单击后,执行自定义操作 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void LabelExSolidBorder_Click(object sender, EventArgs e) 7 { 8 MainForm mf = (MainForm)this.TopLevelControl; 9 ShowNote += mf.SetText;//绑定事件 10 SetSelectedNode += mf.SetSelectedNode; 11 12 if (ShowNote != null) 13 { 14 ShowNote();//自定义操作 15 } 16 }
3、 RectangleLabel控件
这个控件和第一个控件差不太多,主要的区别就是外围路径的绘制,由矩形变成了圆角矩形,做成了类似标签的效果,在这里仅仅贴出绘制的代码,大概思路就是绘制两条线段,并在合适的位置再绘制两个半圆形并组合在一起就可以了:
1 protected override void OnPaint(PaintEventArgs e) 2 { 3 base.OnPaint(e); 4 Graphics g = e.Graphics; 5 Rectangle rect = new Rectangle(new Point(0, 0), new Size(20, 20)); 6 Rectangle rect2 = new Rectangle(new Point(20, 10), new Size(40, 20)); 7 Rectangle rect3 = new Rectangle(new Point(60, 0), new Size(20, 20)); 8 Point[] ps = { new Point(11, 0), new Point(69, 0) }; 9 Point[] pq = { new Point(11, 20), new Point(69, 20) }; 10 float start = 90f; 11 float end = 180f; 12 float start2 = 270f; 13 float end2 = 180f; 14 Pen p = new Pen(this.BorderColor, this.BorderWidth); 15 SolidBrush sl = new SolidBrush(this.InnerColor); 16 { 17 g.SmoothingMode = SmoothingMode.HighQuality; 18 g.DrawArc(p, rect, start, end); 19 g.DrawArc(p, rect3, start2, end2); 20 g.DrawLine(p, ps[0], ps[1]); 21 g.DrawLine(p, pq[0], pq[1]); 22 } 23 }
4、LabelWithCheck控件
相对来说,这个控件由Label和ChecBox组成,是这些控件中是最复杂的,因为它本身包含了事件信息和对应的逻辑,就不仅仅是绘制那么简单了。该控件直接继承自UserControl,灵活性也比较高。
添加了若干属性:
1 [Category("XHB.Controls")] 2 [Browsable(true)] 3 [Description("设置或获取LabelText宽度")] 4 public int LabelWidth 5 { 6 get 7 { 8 return labelWidth; 9 } 10 set 11 { 12 labelWidth = value + ck.Width + 13; 13 this.Refresh(); 14 } 15 } 16 [Category("XHB.Controls")] 17 [Browsable(true)] 18 [Description("指示LabelText是否被选中")] 19 [DefaultValue(false)] 20 public bool LabelChecked 21 { 22 get 23 { 24 return ck.Checked; 25 } 26 set 27 { 28 ck.Checked = value; 29 } 30 }
代码中的属性上方包含一些控件特性,用于在设计时进行浏览或操作:
Category : 指示控件的分组信息;
Browsable : 指示控件是否可浏览;
Description : 控件的描述信息;
DefaultValue : 控件的默认值;
自定义事件,LabelWithCheck控件的自定义事件继承自EventArgs类:
1 /// <summary> 2 /// 事件参数包含的信息 3 /// </summary> 4 public class LabelWithCheckEventArgs :EventArgs 5 { 6 private string _LabelText; 7 private Guid _Id; 8 public LabelWithCheckEventArgs(Guid id,string labelText) 9 { 10 this._LabelText = labelText; 11 this._Id = id; 12 } 13 public string LabelText 14 { 15 get 16 { 17 return _LabelText; 18 } 19 set 20 { 21 _LabelText = value; 22 } 23 } 24 public Guid Id 25 { 26 get 27 { 28 return _Id; 29 } 30 set 31 { 32 _Id = value; 33 } 34 } 35 }
默认情况下,单击控件的非CheckBox所在区域,CheckBox不会变化,下面实现单击整个控件都会改变CheckBox的选中状态,只要加一丢丢代码就好:
1 /// <summary> 2 /// 单击Label也可以触发CheckBox的选中事件 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void lb_Click(object sender, EventArgs e) 7 { 8 ck.Checked = ck.Checked ? false : true; 9 }
四、总结
自定义控件的开发,其实是有规律可循的,无非就是绘制、状态、逻辑的有序组合,无论是简单的控件还是精美复杂的控件,开发的思路都是一样的,掌握了方法,万变不离其宗。这里列出的控件都是入门级的,是适合初学者的,如果文中有表述失误的地方,请提出来,不仅提高自己,更能避免引人误入歧途。具体的代码还是参考我的GitHub上的小工具。注:本文同步发布到我的简书。
作者:悠扬的牧笛
博客地址:http://www.cnblogs.com/xhb-bky-blog/p/5523611.html
声明:本博客原创文字只代表本人工作中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未授权贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文连接。