• 【原创】自己动手写控件----XSmartNote控件


    一、前面的话

    在上一篇博文自己动手写工具----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 }
    View Code

    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 }
    View Code

    根据状态进行绘制:

     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 }
    View Code

    鼠标事件控制,进入或离开的时候,强制区域无效并更新状态,这样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 }
    View Code

     为控件绑定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 }
    View Code

    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 }
    View Code

    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 }
    View Code

    代码中的属性上方包含一些控件特性,用于在设计时进行浏览或操作:

    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 }
    View Code

    默认情况下,单击控件的非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 }
    View Code

    四、总结

    自定义控件的开发,其实是有规律可循的,无非就是绘制、状态、逻辑的有序组合,无论是简单的控件还是精美复杂的控件,开发的思路都是一样的,掌握了方法,万变不离其宗。这里列出的控件都是入门级的,是适合初学者的,如果文中有表述失误的地方,请提出来,不仅提高自己,更能避免引人误入歧途。具体的代码还是参考我的GitHub上的小工具。注:本文同步发布到我的简书

     作者:悠扬的牧笛

     博客地址:http://www.cnblogs.com/xhb-bky-blog/p/5523611.html

     声明:本博客原创文字只代表本人工作中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未授权贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文连接。

  • 相关阅读:
    MFC对话框控件数据提取之DoDataExchange()
    慎用USES_CONVERSION
    Oracle OCP 11G 051(61题版本)答案解析目录
    OCP-1Z0-新051-61题版本-61
    OCP-1Z0-新051-61题版本-60
    OCP-1Z0-新051-61题版本-59
    OCP-1Z0-新051-61题版本-58
    OCP-1Z0-新051-61题版本-57
    OCP-1Z0-新051-61题版本-55
    OCP-1Z0-新051-61题版本-56
  • 原文地址:https://www.cnblogs.com/xhb-bky-blog/p/5523611.html
Copyright © 2020-2023  润新知