本教程将详细介绍如何在 Windows Forms 中创建一个自定义的仪表盘控件。
这个控件具有以下特性:
可配置的颜色区间
平滑的动画效果
可自定义的外观
刻度和数值显示
设计时支持,这个以前没咋研究过,有点尴尬了。。
先看一下效果
以前一直没有认真的实现过控件集合编辑,发现这块还是挺麻烦的。
2. 基础类定义
2.1 颜色区间类 (GaugeColorRange)
// 颜色区间类
[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}";
}
}
2.2 颜色区间集合类 (GaugeColorRangeCollection)
// 颜色区间集合类
[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;
}
}
2.3 颜色区间集合编辑器 (GaugeColorRangeCollectionEditor)
// 颜色区间集合编辑器
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);
}
}
3. 主控件类 (GaugeMeter)
3.1 字段和属性
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);
}
}
4. 使用示例
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);
}
}
5. 总结
本教程展示了如何创建一个功能完整的仪表盘控件,包括:
自定义控件的基本结构
属性和事件的处理
绘图技术的应用
动画效果的实现
设计时支持的添加
通过这个示例,您可以了解到 Windows Forms 自定义控件开发的主要方面,并能够根据需求进行扩展和修改。
这个控件可以作为基础吧,根据具体需求进行进一步的开发和定制。
标签:控件,自定义,C#,float,value,Color,private,new,public From: https://www.cnblogs.com/o-O-oO/p/18561600原创 iamrick 技术老小子