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

目录

简介
控件特性
完整代码实现
使用方法
主要功能说明
1. 基础属性
2. 刻度系统
3. 值指示器
4. 性能优化
总结

简介

本文将详细介绍如何使用C#和GDI+技术实现一个自定义的线性表控件(Linear Gauge)。这个控件具有清晰的刻度显示、数值指示和专业的外观,适用于各种仪表盘和监控界面。

控件特性

  • 垂直方向显示
  • 可自定义最大值和最小值
  • 大小刻度线显示
  • 刻度值标签
  • 当前值指示器
  • 可自定义颜色和样式

完整代码实现

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 { public class LinearGauge : Control { // 私有字段 private float _minimum = 0; private float _maximum = 100; private float _value = 0; private Color _scaleColor = Color.FromArgb(40, 40, 40); private Color _valueColor = Color.FromArgb(220, 50, 50); private Color _glassColor = Color.FromArgb(200, 210, 230); private Color _borderColor = Color.FromArgb(120, 120, 120); private int _majorTickCount = 5; private int _minorTickCount = 5; private int _cornerRadius = 10; private float _borderWidth = 1.5f; private bool _enableRoundedCorners = true; private Timer animationTimer; private float currentAnimatedValue; private float targetValue; private float animationStep; private int animationDuration = 500; // 默认动画持续时间(毫秒) private int animationInterval = 16; // 约60fps private bool isAnimating = false; private AnimationMode animationMode = AnimationMode.EaseInOut; // 动画模式枚举 public enum AnimationMode { Linear, EaseIn, EaseOut, EaseInOut } // 动画完成事件 public event EventHandler AnimationCompleted; [Description("动画持续时间(毫秒)")] public int AnimationDuration { get => animationDuration; set { animationDuration = Math.Max(0, value); CalculateAnimationStep(_value); } } [Description("动画模式")] public AnimationMode AnimationType { get => animationMode; set { animationMode = value; Invalidate(); } } [Description("是否正在动画")] public bool IsAnimating => isAnimating; // 属性 [Description("最小值")] public float Minimum { get => _minimum; set { _minimum = value; Invalidate(); } } [Description("最大值")] public float Maximum { get => _maximum; set { _maximum = value; Invalidate(); } } [Description("当前值")] public float Value { get => _value; set { if (_value != value) { if (animationDuration > 0 && IsHandleCreated) { StartAnimation(value); } else { _value = Math.Max(Minimum, Math.Min(Maximum, value)); currentAnimatedValue = _value; Invalidate(); } } } } [Description("玻璃效果颜色")] public Color GlassColor { get => _glassColor; set { _glassColor = value; Invalidate(); } } [Description("边框颜色")] public Color BorderColor { get => _borderColor; set { _borderColor = value; Invalidate(); } } [Description("边框宽度")] public float BorderWidth { get => _borderWidth; set { _borderWidth = value; Invalidate(); } } [Description("是否启用圆角")] public bool EnableRoundedCorners { get => _enableRoundedCorners; set { _enableRoundedCorners = value; Invalidate(); } } [Description("圆角半径")] public int CornerRadius { get => _cornerRadius; set { _cornerRadius = Math.Max(0, Math.Min(value, Math.Min(Width, Height) / 2)); Invalidate(); } } // 构造函数 public LinearGauge() { SetStyle(ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true); Size = new Size(80, 300); BackColor = Color.FromArgb(240, 240, 240); // 初始化动画计时器 animationTimer = new Timer(); animationTimer.Interval = animationInterval; animationTimer.Tick += AnimationTimer_Tick; // 初始化动画值 currentAnimatedValue = _value; targetValue = _value; } // 启动动画 private void StartAnimation(float newValue) { if (IsDisposed || !IsHandleCreated) return; targetValue = Math.Max(Minimum, Math.Min(Maximum, newValue)); if (Math.Abs(targetValue - currentAnimatedValue) < float.Epsilon) { return; } // 计算动画步长 float totalDistance = targetValue - currentAnimatedValue; float totalSteps = animationDuration / animationInterval; animationStep = totalDistance / totalSteps; isAnimating = true; animationTimer.Start(); } // 动画计时器事件处理 private void AnimationTimer_Tick(object sender, EventArgs e) { if (!isAnimating) return; float previousValue = currentAnimatedValue; float progress = Math.Abs((currentAnimatedValue - _value) / (targetValue - _value)); // 根据动画模式计算新值 float step = CalculateAnimationStep(progress); currentAnimatedValue += step; // 检查是否到达目标值 if ((step > 0 && currentAnimatedValue >= targetValue) || (step < 0 && currentAnimatedValue <= targetValue)) { currentAnimatedValue = targetValue; _value = targetValue; isAnimating = false; animationTimer.Stop(); AnimationCompleted?.Invoke(this, EventArgs.Empty); } // 当值改变时才重绘 if (Math.Abs(previousValue - currentAnimatedValue) > float.Epsilon) { Invalidate(); } } // 计算动画步长 private float CalculateAnimationStep(float progress) { float step = animationStep; switch (animationMode) { case AnimationMode.Linear: // 线性动画,步长保持不变 break; case AnimationMode.EaseIn: // 缓入动画,开始慢后来快 step *= (progress + 0.5f); break; case AnimationMode.EaseOut: // 缓出动画,开始快后来慢 step *= (1.5f - progress); break; case AnimationMode.EaseInOut: // 缓入缓出动画,开始和结束慢,中间快 if (progress < 0.5f) step *= (progress + 0.5f); else step *= (1.5f - progress); break; } return step; } // 创建圆角路径 private GraphicsPath CreateRoundedRectangle(Rectangle bounds, int radius) { if (!EnableRoundedCorners || radius <= 0) { GraphicsPath path = new GraphicsPath(); path.AddRectangle(bounds); return path; } radius = Math.Min(radius, Math.Min(bounds.Width, bounds.Height) / 2); GraphicsPath roundedPath = new GraphicsPath(); int diameter = radius * 2; Rectangle arc = new Rectangle(bounds.Location, new Size(diameter, diameter)); // 左上角 roundedPath.AddArc(arc, 180, 90); // 右上角 arc.X = bounds.Right - diameter; roundedPath.AddArc(arc, 270, 90); // 右下角 arc.Y = bounds.Bottom - diameter; roundedPath.AddArc(arc, 0, 90); // 左下角 arc.X = bounds.Left; roundedPath.AddArc(arc, 90, 90); roundedPath.CloseFigure(); return roundedPath; } // 重写OnPaint方法 protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Graphics g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; // 计算绘制区域,考虑边框宽度 Rectangle gaugeRect = new Rectangle( Padding.Left + (int)Math.Ceiling(_borderWidth / 2), Padding.Top + (int)Math.Ceiling(_borderWidth / 2), Width - Padding.Horizontal - (int)Math.Ceiling(_borderWidth), Height - Padding.Vertical - (int)Math.Ceiling(_borderWidth) ); // 创建路径 using (GraphicsPath path = CreateRoundedRectangle(gaugeRect, _enableRoundedCorners ? _cornerRadius : 0)) { // 绘制玻璃效果背景 DrawGlassEffect(g, path, gaugeRect); // 计算刻度区域 int scaleMargin = 25; Rectangle scaleRect = new Rectangle( gaugeRect.Left + scaleMargin, gaugeRect.Top + 10, 10, gaugeRect.Height - 20 ); // 绘制刻度线和刻度值 DrawScale(g, scaleRect); // 绘制当前值指示器 DrawValueIndicator(g, scaleRect); // 绘制边框 using (Pen borderPen = new Pen(_borderColor, _borderWidth)) { // 设置边框连接和端点样式 borderPen.LineJoin = LineJoin.Round; borderPen.StartCap = LineCap.Round; borderPen.EndCap = LineCap.Round; g.DrawPath(borderPen, path); } } } // 绘制玻璃效果 private void DrawGlassEffect(Graphics g, GraphicsPath path, Rectangle bounds) { // 主背景 - 使用更细腻的渐变 using (LinearGradientBrush backgroundGradient = new LinearGradientBrush( bounds, Color.FromArgb(240, _glassColor), Color.FromArgb(180, _glassColor), LinearGradientMode.Vertical)) { ColorBlend blend = new ColorBlend(3); blend.Colors = new Color[] { Color.FromArgb(240, _glassColor), Color.FromArgb(210, _glassColor), Color.FromArgb(180, _glassColor) }; blend.Positions = new float[] { 0f, 0.5f, 1f }; backgroundGradient.InterpolationColors = blend; g.FillPath(backgroundGradient, path); } // 上部光泽效果 Rectangle upperGloss = new Rectangle( bounds.X, bounds.Y, bounds.Width, bounds.Height / 2); using (GraphicsPath glossPath = CreateRoundedRectangle(upperGloss, _enableRoundedCorners ? _cornerRadius : 0)) using (LinearGradientBrush glossGradient = new LinearGradientBrush( upperGloss, Color.FromArgb(180, Color.White), Color.FromArgb(0, Color.White), LinearGradientMode.Vertical)) { g.FillPath(glossGradient, glossPath); } // 顶部反光效果 int reflectionHeight = bounds.Height / 15; Rectangle reflectionRect = new Rectangle( bounds.X + bounds.Width / 4, bounds.Y + 5, bounds.Width / 2, reflectionHeight); using (LinearGradientBrush reflectionBrush = new LinearGradientBrush( reflectionRect, Color.FromArgb(150, Color.White), Color.FromArgb(0, Color.White), LinearGradientMode.Vertical)) { if (_enableRoundedCorners) { using (GraphicsPath reflectionPath = CreateRoundedRectangle(reflectionRect, reflectionHeight / 2)) { g.FillPath(reflectionBrush, reflectionPath); } } else { g.FillRectangle(reflectionBrush, reflectionRect); } } } // 绘制刻度 private void DrawScale(Graphics g, Rectangle scaleRect) { float stepValue = (_maximum - _minimum) / (_majorTickCount - 1); float stepPixels = scaleRect.Height / (_majorTickCount - 1); using (Pen scalePen = new Pen(_scaleColor, 1)) using (Font font = new Font("Segoe UI", 8, FontStyle.Regular)) using (StringFormat sf = new StringFormat()) { sf.Alignment = StringAlignment.Far; sf.LineAlignment = StringAlignment.Center; // 绘制主刻度线和刻度值 for (int i = 0; i < _majorTickCount; i++) { float y = scaleRect.Bottom - (i * stepPixels); float value = _minimum + (i * stepValue); // 绘制主刻度线 using (Pen majorTickPen = new Pen(Color.FromArgb(180, _scaleColor), 1.5f)) { g.DrawLine(majorTickPen, scaleRect.Left - 15, y, scaleRect.Right, y); } // 绘制刻度值 using (SolidBrush textBrush = new SolidBrush(_scaleColor)) { g.DrawString( value.ToString("0"), font, textBrush, new RectangleF(0, y - 10, scaleRect.Left - 5, 20), sf); } // 绘制小刻度线 if (i < _majorTickCount - 1) { float minorStep = stepPixels / (_minorTickCount + 1); using (Pen minorTickPen = new Pen(Color.FromArgb(120, _scaleColor), 1)) { for (int j = 1; j <= _minorTickCount; j++) { float minorY = y - (j * minorStep); g.DrawLine(minorTickPen, scaleRect.Left - 5, minorY, scaleRect.Right, minorY); } } } } } } // 绘制值指示器 private void DrawValueIndicator(Graphics g, Rectangle scaleRect) { float valuePosition = scaleRect.Bottom - ((currentAnimatedValue - Minimum) / (Maximum - Minimum) * scaleRect.Height); // 创建指示器渐变 using (LinearGradientBrush indicatorBrush = new LinearGradientBrush( new Point(scaleRect.Left - 15, (int)valuePosition - 2), new Point(scaleRect.Right + 5, (int)valuePosition + 2), Color.FromArgb(220, _valueColor), Color.FromArgb(180, _valueColor))) { // 绘制带渐变的指示器 g.FillRectangle(indicatorBrush, scaleRect.Left - 15, valuePosition - 1.5f, scaleRect.Right - scaleRect.Left + 20, 3); // 添加高光效果 using (Pen highlightPen = new Pen(Color.FromArgb(80, Color.White), 1)) { g.DrawLine(highlightPen, scaleRect.Left - 15, valuePosition - 1, scaleRect.Right + 5, valuePosition - 1); } } } // 释放资源 protected override void Dispose(bool disposing) { if (disposing) { animationTimer?.Dispose(); } base.Dispose(disposing); } } }

使用方法

C#
public partial class Form1 : Form { private LinearGauge linearGauge; public Form1() { InitializeComponent(); // 创建并配置控件 linearGauge = new LinearGauge { Location = new Point(50, 50), Minimum = 0, Maximum = 100, Value = 75 }; // 添加到窗体 this.Controls.Add(linearGauge); } }

主要功能说明

1. 基础属性

控件提供了三个主要属性:

  • Minimum:最小值
  • Maximum:最大值
  • Value:当前值

2. 刻度系统

  • 主刻度线:均匀分布的长刻度线,附带数值标签
  • 次刻度线:主刻度线之间的短刻度线
  • 刻度值:在主刻度线旁显示对应的数值

3. 值指示器

使用红色线条标示当前值的位置。

4. 性能优化

  • 使用双缓冲绘制,避免闪烁
  • 启用抗锯齿,提供平滑的显示效果
  • 仅在必要时重绘控件

总结

这个自定义线性表控件实现了专业的刻度显示和值指示功能,可以方便地集成到Windows Forms应用程序中。通过调整属性和样式,可以满足不同的显示需求。代码结构清晰,易于维护和扩展。

希望这个实现对您有所帮助!如果需要更多功能,可以基于此代码进行扩展开发。

本文作者:rick

本文链接:

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