在实际应用开发中,可能UI框架的默认控件,已经不能满足实际的应用需求,或者某些功能被大量的重复应用,如果每一个使用的地方都书写大量的重复代码,则不仅会浪费精力,且让维护变得复杂,此时就需要将包含特定功能的部分封装成控件,便于重复使用。自定义控件就像造轮子,轮子造好以后,别的地方可以一键复用。今天我们以一些简单的小例子,简述基于.NET的Windows窗体编程中自定义控件的相关应用,仅供学习分享使用,如有不足之处,还请指正。

概述
一般而言,自定义控件,是在Visual Studio提供的默认控件不能满足实际的工作需要,才需要在现有控件的基础之上进行新的扩展,来实现自己的功能。自定义控件有自己特有的属性,事件,方法来支撑特定的功能。自定义控件封装实现的细节,可以进行方便的复用。自定义控件主要有以下三种分类,本例采用的是第三种。
- 用户控件(User Control),又称之为复合控件(Composite Controls),将现有的各种控件组合起来,形成一个新的控件,来满足用户的需求。复合控件的设计方式与窗体类似,具有可视化设计器。 通过视觉设计器创建、排列和修改构成控件。 控件事件和逻辑的编写方式与设计窗体时完全相同。 用户控件与其他任何控件一样放置在表单上。
- 扩展控件(Extended Controls):就是在现有的控件基础上,派生出一个新的控件,增加新的功能,或者修改原有功能,来满足用户需求。
- 自定义控件(Custom Controls):就是直接从Control类派生,也就是说完全由自己来设计、实现一个全新的控件,这是最灵活、最强大的方法。
今天主要讲解自定义控件,关于用户控件,扩展控件,可以参考前两篇文章。
自定义控件
自定义控件中主要涉及以下知识点:
- Control : 它是所有控件的基类,创建一个自定义控件,会默认继承这个类。
- OnPaint :控件重绘方法,是protected修饰符,本例中需要重写此方法。
- Graphics : 密封类,不可被继承,用于绘制图形(包括矩形,扇形,直线等)。
- ToolTip : 表示一个长方形的小弹出窗口,该窗口在用户将指针悬停在一个控件上时显示有关该控件用途的简短说明。
- 鼠标事件函数:OnMouseHover 鼠标悬停时触发函数,OnMouseLeave鼠标离开时触发函数。
创建自定义控件库
由于自定义控件可以在创建它的项目中使用,也可也可以在引用这些用户控件库的其他项目中使用,所以为了将自定义控件单独存放,我们创建一个独立的自定义控件库"Okcoder.Winform.CustomControls"。
1. 创建自定义控件
选择控件库,然后点击右键,在弹出的快捷菜单中,选择"添加",然后选择"新建项(W)...",在弹出的"添加新项"对话框中,选择"自定义控件(Windows窗体)",然后输入名称,点击"添加"按钮即可,如下图所示:

在本例中,创建温度计自定义控件,命名为"Thermometer",打开创建的扩展控件的代码视图,然后修改控件的继承类,默认为Control。
2. 创建自定义控件属性
为温度计控件添加自定义属性,包括最小温度,最大温度,当前温度,颜色,字体,等内容,如下所示:
cs
private int interval = 10;
/// <summary>
/// 刻度间隔
/// </summary>
public int Interval
{
get { return interval; }
set { interval = value; }
}
private int minValue = -50;
/// <summary>
/// 最低温度
/// </summary>
public int MinValue
{
get { return minValue; }
set { minValue = value; }
}
private int maxValue = 50;
/// <summary>
/// 最高温度
/// </summary>
public int MaxValue
{
get { return maxValue; }
set { maxValue = value; }
}
private float curValue = 0;
/// <summary>
/// 当前温度
/// </summary>
public float CurValue
{
get { return curValue; }
set { curValue = value; }
}
private Color thermoColor = Color.Red;
/// <summary>
/// 温度条颜色
/// </summary>
public Color ThermoColor
{
get { return thermoColor; }
set { thermoColor = value; }
}
private Color backGroundColor = Color.SkyBlue;
/// <summary>
/// 温度计背景色
/// </summary>
public Color BackGroundColor
{
get { return backGroundColor; }
set { backGroundColor = value; }
}
private Font thermoFont = new Font("宋体", 10, FontStyle.Regular);
/// <summary>
/// 温度计上字体
/// </summary>
public Font ThermoFont
{
get { return thermoFont; }
set { thermoFont = value; }
}
private string thermoTitle = "温度计";
/// <summary>
/// 标题
/// </summary>
public string ThermoTitle
{
get { return this.thermoTitle; }
set { this.thermoTitle = value; }
}
private bool showTip = false;
/// <summary>
/// 是否显示提示
/// </summary>
public bool ShowTip
{
get { return showTip; }
set { showTip = value; }
}
private ToolTip tip = new ToolTip();
3. 重绘控件
重写OnPaint方法,在OnPaint方法中,通过PaintEventArgs属性获取Graphics属性,用于绘制图像,通过Graphics对象可以绘制直线,矩形,椭圆等常见对象,如下所示:
cs
protected override void OnPaint(PaintEventArgs e)
{
//base.OnPaint(pe);
int width = this.Width;
int height = this.Height - 50;
Graphics g = e.Graphics;
int c_x = width / 2;
int c_y = height / 2;
int padding = this.Padding.All;//空白
int r = (width - 2 * padding) / 2;//半径
int d = 2 * r;//直径
int dis = 2;//两个半圆之间的间隔
int dis2 = 2 * dis;//填充与边框之间的距离
int startAngle1 = -180;
int startAngle2 = 0;
int sweepAngle1 = 180;
//首先画顶端一个半圆
g.DrawPie(Pens.Black, new Rectangle(padding, padding, d, d), startAngle1, sweepAngle1);
g.DrawPie(Pens.Black, new Rectangle(padding + dis, padding + dis, d - 2 * dis, d - 2 * dis), startAngle1, sweepAngle1);
//填充背景色
g.FillPie(new SolidBrush(this.backGroundColor), new Rectangle(padding + dis2, padding + dis2, d - 2 * dis2, d - 2 * dis2), startAngle1, sweepAngle1);
//画底端一个矩形
g.DrawPie(Pens.Black, new Rectangle(padding, height - d - padding, d, d), startAngle2, sweepAngle1);
g.DrawPie(Pens.Black, new Rectangle(padding + dis, height - d - padding + dis, d - 2 * dis, d - 2 * dis), startAngle2, sweepAngle1);
g.FillPie(new SolidBrush(this.backGroundColor), new Rectangle(padding + dis2, height - d - padding + dis2, d - 2 * dis2, d - 2 * dis2), startAngle2, sweepAngle1);
//画一个矩形
g.DrawRectangle(Pens.Black, new RectangleF(padding-0.4F, padding + r, d+0.8F, height - d - 2 * padding));
g.DrawRectangle(Pens.Black, new Rectangle(padding + dis, padding + r + dis, d - 2 * dis, height - d - 2 * padding - 2 * dis));
//背景色填充,去掉边界线
g.FillRectangle(new SolidBrush(this.backGroundColor), new Rectangle(padding + 3, padding + r - 2, 2 * r - 6, 6));
g.FillRectangle(new SolidBrush(this.backGroundColor), new Rectangle(padding + 3, height - r - padding - 4, 2 * r - 6, 8));
//背景色填充中间部分
g.FillRectangle(new SolidBrush(this.backGroundColor), new Rectangle(padding + dis2, padding + r + dis2, d - 2 * dis2, height - d - 2 * padding - 2 * dis2));
//画刻度
int s_s_x_1 = padding + r - 20;
int s_s_x_2 = width - padding - r + 20;
int s_s_y = padding + r + 4;
int total = this.maxValue - this.minValue;
int scale_width = 5;//刻度宽度
int scale = total / this.interval;
int pscale = (height - 2 * r - 2 * padding) / this.interval;//像素间隔
//竖线
g.DrawLine(Pens.Black, new Point(s_s_x_1, s_s_y), new Point(s_s_x_1, s_s_y + this.interval * pscale));
g.DrawLine(Pens.Black, new Point(s_s_x_2, s_s_y), new Point(s_s_x_2, s_s_y + this.interval * pscale));
for (int i = 0; i <= this.interval; i++)
{
//横线刻度
g.DrawLine(Pens.Black, new Point(s_s_x_1 - scale_width, s_s_y + i * pscale), new Point(s_s_x_1, s_s_y + i * pscale));
g.DrawLine(Pens.Black, new Point(s_s_x_2, s_s_y + i * pscale), new Point(s_s_x_2 + scale_width, s_s_y + i * pscale));
//画刻度数字
g.DrawString((this.maxValue - (scale * i)).ToString(), this.thermoFont, new SolidBrush(this.ForeColor), new Point(s_s_x_1 - 35, s_s_y + i * pscale - 10));
g.DrawString((this.maxValue - (scale * i)).ToString(), this.thermoFont, new SolidBrush(this.ForeColor), new Point(s_s_x_2 + 10, s_s_y + i * pscale - 10));
}
int white_width = 3;//中间白色线宽度
//画条白色细线
g.FillRectangle(Brushes.White, new Rectangle(c_x - white_width, r / 2, white_width * 2, height - r));
//在底部画一个圆球
g.FillPie(new SolidBrush(this.thermoColor), new Rectangle(c_x - r / 2 + 5, height - r - padding, r - 10, r - 10), 0, 360);
//根据当前温度画红色线
int red_width = 5;//红色温度线宽度
float ii = (this.curValue - this.minValue) / this.interval;
g.FillRectangle(new SolidBrush(this.thermoColor), new RectangleF(c_x - red_width, height - r - padding - (ii * pscale) - 4, 2 * red_width, ii * pscale + 5));//此处有一像素的误差
//画标志字符单℃位
g.DrawString("℃", this.thermoFont, new SolidBrush(this.ForeColor), new Point(c_x - 30, r / 2 - 10));
Font titleFont = new Font("宋体", 13, FontStyle.Bold);
//绘制标题
SizeF tsize = g.MeasureString(this.thermoTitle, titleFont);
g.DrawString(this.thermoTitle, titleFont, new SolidBrush(this.ForeColor), new PointF(c_x - (tsize.Width / 2), height + 5));
string cur = string.Format("当前温度:{0}℃", this.curValue);
SizeF tsize2 = g.MeasureString(cur, this.thermoFont);
g.DrawString(cur, this.thermoFont, new SolidBrush(this.thermoColor), new PointF(c_x - (tsize2.Width / 2), height + 10 + tsize.Height));
}
4. 自定义控件提示
当鼠标放在控件上时,显示提示信息,主要显示当前温度,如下所示:
cs
/// <summary>
/// 当鼠标覆盖进去时
/// </summary>
/// <param name="e"></param>
protected override void OnMouseHover(EventArgs e)
{
this.showTip = true;
//需要显示的内容
int x = this.Width / 2;
int y = (this.Height - 50) / 2;
StringBuilder sbTips = new StringBuilder();
//sbTips.AppendLine(this.ThermoTitle);
sbTips.AppendLine(string.Format("当前温度:{0}", this.curValue));
sbTips.AppendLine("单位:℃");
tip.ToolTipTitle = this.ThermoTitle;
tip.IsBalloon = true;
tip.UseFading = true;
//t.SetToolTip(this, sbTips.ToString());
tip.Show(sbTips.ToString(), this, x, y);
}
protected override void OnMouseLeave(EventArgs e)
{
this.showTip = false;
tip.Hide(this);
}
应用自定义控件
自定义控件创建完成,且项目编译成功后,在需要应用自定义控件的项目引用控件库"Okcoder.WinForm.CustomControls",就可以在工具箱中,看到创建的自定义控件,然后拖动控件到Form表单中,如下所示:

为控件设置属性,如下所示:

运行实例,效果如下所示:

鼠标放在控件上时,会显示ToolTip信息,如下所示:

以上就是《基于.NET的Windows窗体编程之WinForms自定义控件(3)》的全部内容,旨在抛砖引玉,一起学习,共同进步!!!