本教程将详细介绍如何使用C#和GDI+开发一个数字表样式的自定义控件。这个控件可以用来显示数值,并具有可自定义的外观和动画效果。
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Timer = System.Windows.Forms.Timer;
namespace AppControls
{
/// <summary>
/// 数字表自定义控件
/// </summary>
public class DigitalGauge : Control
{
#region 私有字段
private float _minimum = 0;
private float _maximum = 100;
private float _value = 0;
private float _targetValue = 0;
private Color _dialColor = Color.DarkGray;
private Color _pointerColor = Color.Red;
private Timer _animationTimer;
private bool _enableAnimation = true;
private bool _enableGlassEffect = true;
private float _glassOpacity = 0.3f;
private Color _glassHighlightColor = Color.White;
private Color _glassShadowColor = Color.FromArgb(30, 0, 0, 0);
private bool _fullCircleScale = false;
private float _startAngle = 225; // 默认起始角度
private float _endAngle = 495; // 默认结束角度 (等于135度)
private float _scaleArcLength = 270; // 刻度弧长
private int _majorTickCount = 10; // 主刻度数量
private int _minorTickCount = 5; // 每个主刻度之间的小刻度数量
#endregion
#region 公共属性
/// <summary>
/// 获取或设置是否绘制满圈刻度
/// </summary>
[Description("获取或设置是否绘制满圈刻度")]
public bool FullCircleScale
{
get { return _fullCircleScale; }
set
{
_fullCircleScale = value;
UpdateScaleAngles();
Invalidate();
}
}
/// <summary>
/// 获取或设置起始角度(度)
/// </summary>
[Description("获取或设置起始角度(度)")]
public float StartAngle
{
get { return _startAngle; }
set
{
_startAngle = value;
UpdateScaleAngles();
Invalidate();
}
}
/// <summary>
/// 获取或设置结束角度(度)
/// </summary>
[Description("获取或设置结束角度(度)")]
public float EndAngle
{
get { return _endAngle; }
set
{
_endAngle = value;
UpdateScaleAngles();
Invalidate();
}
}
/// <summary>
/// 获取或设置主刻度数量
/// </summary>
[Description("获取或设置主刻度数量")]
public int MajorTickCount
{
get { return _majorTickCount; }
set
{
if (value > 0)
{
_majorTickCount = value;
Invalidate();
}
}
}
/// <summary>
/// 获取或设置每个主刻度之间的小刻度数量
/// </summary>
[Description("获取或设置每个主刻度之间的小刻度数量")]
public int MinorTickCount
{
get { return _minorTickCount; }
set
{
if (value >= 0)
{
_minorTickCount = value;
Invalidate();
}
}
}
/// <summary>
/// 获取或设置是否启用玻璃效果
/// </summary>
[Description("获取或设置是否启用玻璃效果")]
public bool EnableGlassEffect
{
get { return _enableGlassEffect; }
set
{
_enableGlassEffect = value;
Invalidate();
}
}
/// <summary>
/// 获取或设置玻璃效果的透明度
/// </summary>
[Description("获取或设置玻璃效果的透明度")]
public float GlassOpacity
{
get { return _glassOpacity; }
set
{
_glassOpacity = Math.Max(0, Math.Min(1, value));
Invalidate();
}
}
/// <summary>
/// 获取或设置最小值
/// </summary>
[Description("获取或设置最小值")]
public float Minimum
{
get { return _minimum; }
set
{
if (value < _maximum)
{
_minimum = value;
Invalidate();
}
}
}
/// <summary>
/// 获取或设置最大值
/// </summary>
[Description("获取或设置最大值")]
public float Maximum
{
get { return _maximum; }
set
{
if (value > _minimum)
{
_maximum = value;
Invalidate();
}
}
}
/// <summary>
/// 获取或设置当前值
/// </summary>
[Description("获取或设置当前值")]
public float Value
{
get { return _value; }
set
{
if (value >= _minimum && value <= _maximum)
{
_targetValue = value;
if (_enableAnimation)
{
if (!_animationTimer.Enabled)
_animationTimer.Start();
}
else
{
_value = value;
Invalidate();
}
}
}
}
/// <summary>
/// 获取或设置表盘颜色
/// </summary>
[Description("获取或设置表盘颜色")]
public Color DialColor
{
get { return _dialColor; }
set
{
_dialColor = value;
Invalidate();
}
}
/// <summary>
/// 获取或设置指针颜色
/// </summary>
[Description("获取或设置指针颜色")]
public Color PointerColor
{
get { return _pointerColor; }
set
{
_pointerColor = value;
Invalidate();
}
}
#endregion
#region 构造函数
/// <summary>
/// 初始化数字表控件
/// </summary>
public DigitalGauge()
{
// 设置控件样式
SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.ResizeRedraw, true);
// 初始化动画计时器
_animationTimer = new Timer();
_animationTimer.Interval = 10;
_animationTimer.Tick += AnimationTimer_Tick;
}
#endregion
/// <summary>
/// 更新刻度角度
/// </summary>
private void UpdateScaleAngles()
{
if (_fullCircleScale)
{
_startAngle = 0;
_endAngle = 360;
_scaleArcLength = 360;
}
else
{
_scaleArcLength = _endAngle - _startAngle;
}
}
/// <summary>
/// 将值转换为角度
/// </summary>
private float ValueToAngle(float value)
{
return _startAngle + ((value - _minimum) / (_maximum - _minimum) * _scaleArcLength);
}
/// <summary>
/// 将角度转换为值
/// </summary>
private float AngleToValue(float angle)
{
// 将角度标准化到起始角度范围内
while (angle < _startAngle)
angle += 360;
while (angle > _startAngle + _scaleArcLength)
angle -= 360;
return _minimum + (angle - _startAngle) * (_maximum - _minimum) / _scaleArcLength;
}
#region 绘制方法
/// <summary>
/// 重写绘制方法
/// </summary>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
float centerX = Width / 2f;
float centerY = Height / 2f;
float radius = Math.Min(Width, Height) / 2f - 10;
// 绘制表盘背景
DrawDial(g, centerX, centerY, radius);
// 绘制刻度
DrawScale(g, centerX, centerY, radius);
// 绘制指针
DrawPointer(g, centerX, centerY, radius);
// 绘制数值
DrawValue(g, centerX, centerY);
// 绘制玻璃效果
if (_enableGlassEffect)
{
DrawGlassEffect(g, centerX, centerY, radius);
}
}
/// <summary>
/// 绘制玻璃效果
/// </summary>
private void DrawGlassEffect(Graphics g, float centerX, float centerY, float radius)
{
// 创建玻璃效果区域路径
using (GraphicsPath glassPath = new GraphicsPath())
{
glassPath.AddEllipse(centerX - radius, centerY - radius, radius * 2, radius * 2);
// 创建高光渐变
using (PathGradientBrush highlightBrush = new PathGradientBrush(glassPath))
{
// 设置中心点颜色和边缘颜色
highlightBrush.CenterColor = Color.FromArgb(
(int)(255 * _glassOpacity),
_glassHighlightColor);
highlightBrush.SurroundColors = new Color[] {
Color.FromArgb(0, _glassHighlightColor)
};
// 设置渐变焦点
highlightBrush.CenterPoint = new PointF(
centerX - radius * 0.3f,
centerY - radius * 0.3f
);
// 绘制高光效果
g.FillEllipse(highlightBrush,
centerX - radius,
centerY - radius,
radius * 2,
radius * 2);
}
// 创建阴影效果
RectangleF shadowRect = new RectangleF(
centerX - radius,
centerY - radius * 0.2f,
radius * 2,
radius * 1.2f
);
using (LinearGradientBrush shadowBrush = new LinearGradientBrush(
shadowRect,
Color.FromArgb(0, _glassShadowColor),
_glassShadowColor,
LinearGradientMode.Vertical))
{
// 创建阴影区域
using (GraphicsPath shadowPath = new GraphicsPath())
{
shadowPath.AddEllipse(centerX - radius, centerY - radius,
radius * 2, radius * 2);
// 使用裁剪区域确保阴影只在表盘内部
using (Region clipRegion = new Region(shadowPath))
{
g.Clip = clipRegion;
g.FillRectangle(shadowBrush, shadowRect);
g.ResetClip();
}
}
}
// 添加边缘高光
using (Pen highlightPen = new Pen(Color.FromArgb(100, Color.White), 1))
{
g.DrawEllipse(highlightPen,
centerX - radius,
centerY - radius,
radius * 2,
radius * 2);
}
}
}
/// <summary>
/// 绘制表盘背景
/// </summary>
private void DrawDial(Graphics g, float centerX, float centerY, float radius)
{
using (SolidBrush brush = new SolidBrush(_dialColor))
{
g.FillEllipse(brush, centerX - radius, centerY - radius,
radius * 2, radius * 2);
}
// 绘制边框
using (Pen pen = new Pen(Color.Black, 2))
{
g.DrawEllipse(pen, centerX - radius, centerY - radius,
radius * 2, radius * 2);
}
}
/// <summary>
/// 绘制刻度
/// </summary>
private void DrawScale(Graphics g, float centerX, float centerY, float radius)
{
using (Pen majorPen = new Pen(Color.White, 2))
using (Pen minorPen = new Pen(Color.White, 1))
using (Font font = new Font("Arial", 8))
using (StringFormat sf = new StringFormat())
{
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
// 绘制主刻度和刻度值
for (int i = 0; i <= _majorTickCount; i++)
{
float angle = _startAngle + (i * _scaleArcLength / _majorTickCount);
double rad = angle * Math.PI / 180;
// 计算刻度线的起点和终点
float innerX = (float)(centerX + (radius - 15) * Math.Cos(rad));
float innerY = (float)(centerY + (radius - 15) * Math.Sin(rad));
float outerX = (float)(centerX + radius * Math.Cos(rad));
float outerY = (float)(centerY + radius * Math.Sin(rad));
// 绘制主刻度线
g.DrawLine(majorPen, innerX, innerY, outerX, outerY);
// 绘制刻度值
float value = _minimum + ((_maximum - _minimum) * i / _majorTickCount);
string text = value.ToString("F0");
// 计算文本位置
float textRadius = radius - 30;
float textX = (float)(centerX + textRadius * Math.Cos(rad));
float textY = (float)(centerY + textRadius * Math.Sin(rad));
// 创建文本绘制区域
RectangleF textRect = new RectangleF(
textX - 15, textY - 15, 30, 30);
g.DrawString(text, font, Brushes.White, textRect, sf);
// 绘制小刻度
if (i < _majorTickCount && _minorTickCount > 0)
{
float minorStep = _scaleArcLength / _majorTickCount / (_minorTickCount + 1);
for (int j = 1; j <= _minorTickCount; j++)
{
float minorAngle = angle + (j * minorStep);
double minorRad = minorAngle * Math.PI / 180;
float minorInnerX = (float)(centerX + (radius - 8) * Math.Cos(minorRad));
float minorInnerY = (float)(centerY + (radius - 8) * Math.Sin(minorRad));
float minorOuterX = (float)(centerX + radius * Math.Cos(minorRad));
float minorOuterY = (float)(centerY + radius * Math.Sin(minorRad));
g.DrawLine(minorPen, minorInnerX, minorInnerY,
minorOuterX, minorOuterY);
}
}
}
}
}
/// <summary>
/// 绘制指针
/// </summary>
private void DrawPointer(Graphics g, float centerX, float centerY, float radius)
{
float angle = ValueToAngle(_value);
double rad = angle * Math.PI / 180;
PointF[] points = new PointF[3];
points[0] = new PointF(
(float)(centerX + (radius - 10) * Math.Cos(rad)),
(float)(centerY + (radius - 10) * Math.Sin(rad)));
points[1] = new PointF(
(float)(centerX + 5 * Math.Cos(rad - Math.PI / 2)),
(float)(centerY + 5 * Math.Sin(rad - Math.PI / 2)));
points[2] = new PointF(
(float)(centerX + 5 * Math.Cos(rad + Math.PI / 2)),
(float)(centerY + 5 * Math.Sin(rad + Math.PI / 2)));
using (SolidBrush brush = new SolidBrush(_pointerColor))
{
g.FillPolygon(brush, points);
}
// 绘制中心点
using (SolidBrush brush = new SolidBrush(Color.White))
{
g.FillEllipse(brush, centerX - 5, centerY - 5, 10, 10);
}
}
/// <summary>
/// 绘制数值
/// </summary>
private void DrawValue(Graphics g, float centerX, float centerY)
{
string text = _value.ToString("F1");
using (Font font = new Font("Arial", 12, FontStyle.Bold))
{
SizeF size = g.MeasureString(text, font);
g.DrawString(text, font, Brushes.White,
centerX - size.Width / 2,
centerY + 20);
}
}
#endregion
#region 动画相关
/// <summary>
/// 动画计时器事件处理
/// </summary>
private void AnimationTimer_Tick(object sender, EventArgs e)
{
float delta = (_targetValue - _value) / 10;
if (Math.Abs(delta) < 0.1)
{
_value = _targetValue;
_animationTimer.Stop();
}
else
{
_value += delta;
}
Invalidate();
}
#endregion
#region 资源释放
/// <summary>
/// 释放资源
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_animationTimer != null)
{
_animationTimer.Dispose();
}
}
base.Dispose(disposing);
}
#endregion
}
}
本教程详细介绍了如何使用C#和GDI+开发一个数字表样式的自定义控件。通过合理的代码组织和注释,使得代码易于理解和维护。控件具有良好的可扩展性,可以根据实际需求进行功能扩展。
本文作者:rick
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!