编辑
2025-02-20
C# 应用
00
请注意,本文编写于 75 天前,最后修改于 75 天前,其中某些信息可能已经过时。

目录

概述
控件特性
完整代码实现
总结

概述

本教程将详细介绍如何使用C#和GDI+开发一个数字表样式的自定义控件。这个控件可以用来显示数值,并具有可自定义的外观和动画效果。

控件特性

  • 可自定义表盘背景颜色
  • 可自定义刻度颜色和样式
  • 可自定义指针颜色和样式
  • 支持数值范围设置
  • 支持动画效果
  • 支持鼠标交互

完整代码实现

image.png

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 许可协议。转载请注明出处!