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

目录

简介
基础类定义
颜色区间类 (GaugeColorRange)
颜色区间集合类 (GaugeColorRangeCollection)
颜色区间集合编辑器 (GaugeColorRangeCollectionEditor)
主控件类 (GaugeMeter)
字段和属性
使用示例
基本使用
总结

简介

本教程将详细介绍如何在 Windows Forms 中创建一个自定义的仪表盘控件。这个控件具有以下特性:

  • 可配置的颜色区间
  • 平滑的动画效果
  • 可自定义的外观
  • 刻度和数值显示
  • 设计时支持,这个以前没咋研究过,有点尴尬了。。

先看一下效果

image.png

以前一直没有认真的实现过控件集合编辑,发现这块还是挺麻烦的。

image.png

基础类定义

颜色区间类 (GaugeColorRange)

C#
// 颜色区间类 [Serializable] [TypeConverter(typeof(ExpandableObjectConverter))] public class GaugeColorRange : ICloneable { private float _startValue; private float _endValue; private Color _color; public GaugeColorRange() { _startValue = 0; _endValue = 0; _color = Color.Black; } public GaugeColorRange(float start, float end, Color color) { _startValue = start; _endValue = end; _color = color; } [Description("区间起始值")] [Browsable(true)] [DefaultValue(0f)] public float StartValue { get => _startValue; set => _startValue = value; } [Description("区间结束值")] [Browsable(true)] [DefaultValue(0f)] public float EndValue { get => _endValue; set => _endValue = value; } [Description("区间颜色")] [Browsable(true)] public Color Color { get => _color; set => _color = value; } public object Clone() { return new GaugeColorRange(StartValue, EndValue, Color); } public override string ToString() { return $"{StartValue} - {EndValue}: {Color.Name}"; } }

颜色区间集合类 (GaugeColorRangeCollection)

C#
// 颜色区间集合类 [Serializable] public class GaugeColorRangeCollection : BindingList<GaugeColorRange> { public GaugeColorRangeCollection() { AllowNew = true; AllowEdit = true; AllowRemove = true; } public GaugeColorRangeCollection Clone() { var newCollection = new GaugeColorRangeCollection(); foreach (var range in this) { newCollection.Add((GaugeColorRange)range.Clone()); } return newCollection; } }

颜色区间集合编辑器 (GaugeColorRangeCollectionEditor)

C#
// 颜色区间集合编辑器 public class GaugeColorRangeCollectionEditor : CollectionEditor { public GaugeColorRangeCollectionEditor(Type type) : base(type) { } protected override Type CreateCollectionItemType() { return typeof(GaugeColorRange); } protected override object CreateInstance(Type itemType) { return new GaugeColorRange(0, 100, Color.Black); } }

主控件类 (GaugeMeter)

字段和属性

C#
public class GaugeMeter : Control { private float _currentValue; private float _minValue = 0; private float _maxValue = 100; private Color _dialColor = Color.DarkBlue; private Color _pointerColor = Color.Red; private Timer _animationTimer; private float _targetValue; private float _dialThickness = 3f; private GaugeColorRangeCollection _colorRanges; public GaugeMeter() { SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true); Size = new Size(200, 200); BackColor = Color.White; _colorRanges = new GaugeColorRangeCollection(); } [Category("Appearance")] [Description("颜色区间集合")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [Editor(typeof(GaugeColorRangeCollectionEditor), typeof(UITypeEditor))] public GaugeColorRangeCollection ColorRanges { get => _colorRanges; set { if (_colorRanges != value) { _colorRanges = value; Invalidate(); } } } public float DialThickness { get => _dialThickness; set { if (value >= 1 && value <= 20) // 限制合理范围 { _dialThickness = value; Invalidate(); } } } public float MinValue { get => _minValue; set { _minValue = value; Invalidate(); } } public float MaxValue { get => _maxValue; set { _maxValue = value; Invalidate(); } } public float Value { get => _currentValue; set { if (value < _minValue) value = _minValue; if (value > _maxValue) value = _maxValue; _targetValue = value; if (_animationTimer == null) { _animationTimer = new Timer(); _animationTimer.Interval = 16; _animationTimer.Tick += AnimationTimer_Tick; } _animationTimer.Start(); } } public Color DialColor { get => _dialColor; set { _dialColor = value; Invalidate(); } } public Color PointerColor { get => _pointerColor; set { _pointerColor = value; Invalidate(); } } private void AnimationTimer_Tick(object sender, EventArgs e) { float diff = _targetValue - _currentValue; if (Math.Abs(diff) < 0.1f) { _currentValue = _targetValue; _animationTimer.Stop(); } else { _currentValue += diff * 0.1f; } Invalidate(); } protected override void OnPaint(PaintEventArgs e) { e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; float centerX = Width / 2f; float centerY = Height / 2f; float radius = Math.Min(Width, Height) * 0.4f; using (var path = new GraphicsPath()) { // 绘制外圈 float adjustedRadius = radius - _dialThickness / 2; if (_colorRanges != null && _colorRanges.Count > 0) { foreach (var range in _colorRanges) { if (range.StartValue >= range.EndValue) continue; // 计算起始和结束角度 float startAngle = 135 + (range.StartValue - _minValue) / (_maxValue - _minValue) * 270; float endAngle = 135 + (range.EndValue - _minValue) / (_maxValue - _minValue) * 270; float sweepAngle = endAngle - startAngle; using (var pen = new Pen(range.Color, _dialThickness)) { e.Graphics.DrawArc(pen, centerX - adjustedRadius, centerY - adjustedRadius, adjustedRadius * 2, adjustedRadius * 2, startAngle, sweepAngle); } } } else { // 如果没有设置颜色区间,使用默认颜色 using (var pen = new Pen(_dialColor, _dialThickness)) { e.Graphics.DrawArc(pen, centerX - adjustedRadius, centerY - adjustedRadius, adjustedRadius * 2, adjustedRadius * 2, 135, 270); } } // 获取当前值对应的颜色 Color currentColor = GetColorForValue(_currentValue); // 绘制刻度 for (int i = 0; i <= 10; i++) { float angle = 135 + i * 27; float radian = angle * (float)Math.PI / 180f; float scaleLength = _dialThickness + 7; float startX = centerX + (radius - scaleLength) * (float)Math.Cos(radian); float startY = centerY + (radius - scaleLength) * (float)Math.Sin(radian); float endX = centerX + radius * (float)Math.Cos(radian); float endY = centerY + radius * (float)Math.Sin(radian); // 获取刻度值对应的颜色 float value = _minValue + (_maxValue - _minValue) * i / 10f; Color scaleColor = GetColorForValue(value); using (var pen = new Pen(scaleColor, Math.Max(1, _dialThickness / 2))) { e.Graphics.DrawLine(pen, startX, startY, endX, endY); } // 绘制刻度值 string text = value.ToString("F0"); using (var font = new Font("Arial", 8)) { float textX = centerX + (radius - scaleLength - 15) * (float)Math.Cos(radian); float textY = centerY + (radius - scaleLength - 15) * (float)Math.Sin(radian); using (var brush = new SolidBrush(scaleColor)) { e.Graphics.DrawString(text, font, brush, textX - 10, textY - 5); } } } // 绘制指针 float valueAngle = 135 + (_currentValue - _minValue) / (_maxValue - _minValue) * 270; float valueRadian = valueAngle * (float)Math.PI / 180f; float pointerLength = radius * 0.8f; float pointerWidth = Math.Max(2, _dialThickness * 0.8f); PointF[] pointer = new PointF[] { new PointF(centerX + pointerLength * (float)Math.Cos(valueRadian), centerY + pointerLength * (float)Math.Sin(valueRadian)), new PointF(centerX + pointerWidth * (float)Math.Cos(valueRadian + Math.PI / 2), centerY + pointerWidth * (float)Math.Sin(valueRadian + Math.PI / 2)), new PointF(centerX - 20 * (float)Math.Cos(valueRadian), centerY - 20 * (float)Math.Sin(valueRadian)), new PointF(centerX + pointerWidth * (float)Math.Cos(valueRadian - Math.PI / 2), centerY + pointerWidth * (float)Math.Sin(valueRadian - Math.PI / 2)), }; using (var brush = new SolidBrush(_pointerColor)) { e.Graphics.FillPolygon(brush, pointer); } // 中心圆 float centerCircleSize = Math.Max(10, _dialThickness * 3); using (var brush = new SolidBrush(_pointerColor)) { e.Graphics.FillEllipse(brush, centerX - centerCircleSize / 2, centerY - centerCircleSize / 2, centerCircleSize, centerCircleSize); } // 绘制当前值 using (var font = new Font("Arial", Math.Max(12, _dialThickness + 3), FontStyle.Bold)) { string valueText = _currentValue.ToString("F1"); SizeF size = e.Graphics.MeasureString(valueText, font); using (var brush = new SolidBrush(currentColor)) { e.Graphics.DrawString(valueText, font, brush, centerX - size.Width / 2, centerY + radius * 0.3f); } } } base.OnPaint(e); } // 根据数值获取对应的颜色 private Color GetColorForValue(float value) { if (_colorRanges != null) { foreach (var range in _colorRanges) { if (value >= range.StartValue && value <= range.EndValue) { return range.Color; } } } return _dialColor; } protected override void Dispose(bool disposing) { if (disposing) { if (_animationTimer != null) { _animationTimer.Dispose(); _animationTimer = null; } } base.Dispose(disposing); } }

使用示例

基本使用

C#
public partial class Form1 : Form { private GaugeMeter gaugeMeter; public Form1() { InitializeComponent(); gaugeMeter = new GaugeMeter { Location = new Point(50, 50), Size = new Size(200, 200), MinValue = 0, MaxValue = 100, Value = 0, DialColor = Color.Gray, PointerColor = Color.Red, DialThickness = 5f }; // 添加颜色区间 gaugeMeter.ColorRanges.Add(new GaugeColorRange(0, 30, Color.Green)); gaugeMeter.ColorRanges.Add(new GaugeColorRange(30, 70, Color.Yellow)); gaugeMeter.ColorRanges.Add(new GaugeColorRange(70, 100, Color.Red)); this.Controls.Add(gaugeMeter); } }

总结

本教程展示了如何创建一个功能完整的仪表盘控件,包括:

  • 自定义控件的基本结构
  • 属性和事件的处理
  • 绘图技术的应用
  • 动画效果的实现
  • 设计时支持的添加

通过这个示例,您可以了解到 Windows Forms 自定义控件开发的主要方面,并能够根据需求进行扩展和修改。

这个控件可以作为基础吧,根据具体需求进行进一步的开发和定制,有时间估计事件这块我会先增加,这个简单,想法也比较明确。

本文作者:rick

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!