首页 > 系统相关 >【C#应用】Windows Forms 自定义仪表盘控件开发

【C#应用】Windows Forms 自定义仪表盘控件开发

时间:2024-11-21 21:42:33浏览次数:1  
标签:控件 自定义 C# float value Color private new public

本教程将详细介绍如何在 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 自定义控件开发的主要方面,并能够根据需求进行扩展和修改。

这个控件可以作为基础吧,根据具体需求进行进一步的开发和定制。

原创 iamrick 技术老小子

标签:控件,自定义,C#,float,value,Color,private,new,public
From: https://www.cnblogs.com/o-O-oO/p/18561600

相关文章

  • 欧皇限定のColorCoding
    ColorCoding与最小权重k-path问题省流一款令欧皇喜笑颜开,令非酋愁眉苦脸的算法。(但是概率远远大于抽卡)参考资料:https://xuewen.cnki.net/CJFD-JSJA200801005.htmlhttps://blog.csdn.net/u010352695/article/details/40924019以及来自同学的题解用途&适用作为一种近似......
  • 【C#应用】C# 对 Windows API 内存操作
    在C#中,我们可以通过调用WindowsAPI来进行内存操作,这在一些特定的场景下非常有用。比如在需要与底层系统进行交互、进行内存分配和释放、修改其他进程的内存等情况下,使用WindowsAPI可以帮助我们实现这些功能。应用场景内存分配和释放通过WindowsAPI可以实现内存的动态分配和......
  • Java性能为什么比c#高很多?
    实际上,Java和C#的性能并不是一成不变的,它们在不同的场景和条件下可能会有不同的表现。以下是一些可能导致Java在某些情况下性能比C#高的原因:1.**JIT编译**: -Java使用即时编译(JIT)技术,这种技术允许JVM在运行时优化字节码。随着时间的推移,JVM可以对经常执行的代码......
  • 全网首发!红帽 RHEL 10.0 一键安装 Oracle 19C,硬核!
    大家好,这里是Lucifer三思而后行,专注于提升数据库运维效率。目录社群交流前言安装准备环境信息安装命令安装过程连接测试写在最后往期精彩文章社群交流为了给大家提供一些技术交流的平台,目前已成立的技术交流群:Oracle数据库交流群国产数据库交流群Linux技术交......
  • Cppcheck 静态代码分析
    Cppcheck工具主要用于静态代码分析,帮助开发者在不运行代码的情况下发现潜在的错误、代码质量问题或性能隐患。Cppcheck安装sudoaptinstallcppcheckCppcheck使用基本用法cppcheck[选项][文件或目录]常用命令和选项检查单个文件cppcheckexample.cpp检......
  • 深入计算机语言之C++:STL之vector的模拟实现
    ......
  • YOLOv8-ultralytics-8.2.103部分代码阅读笔记-block.py
    block.pyultralytics\nn\modules\block.py目录block.py1.所需的库和模块2.classDFL(nn.Module):3.classProto(nn.Module):4.classHGStem(nn.Module): 5.classHGBlock(nn.Module): 6.classSPP(nn.Module): 7.classSPPF(nn.Module): 8.classC1(nn.Module):......
  • vxe-grid 自定义插槽模板
    在vxe-table中使用vxe-grid渲染表格,当配置式不能满足需求时,。需要使用自定义插槽模板来自定义业务需求,实现更灵活的功能。vxe-grid支持多种自定义方式,可以使用插槽模板,也可以使用插槽来自定义模板。自定义单元格模板<template><div><vxe-gridv-bind="gridOptions......
  • 神经网络(系统性学习四):深度学习——卷积神经网络(CNN)
    相关文章:神经网络中常用的激活函数神经网络(系统性学习一):入门篇神经网络(系统性学习二):单层神经网络(感知机)神经网络(系统性学习三):多层感知机(MLP)        卷积神经网络(ConvolutionalNeuralNetworks,CNN)是一种深度学习模型,专为处理具有网格拓扑结构的数据而设计,最常见的应......
  • Cocos Creator引擎开发:物理引擎使用_物理材质的应用
    物理材质的应用在CocosCreator中,物理材质(PhysicsMaterial)是用于定义物理对象表面特性的资源。通过物理材质,可以控制物体之间的摩擦力、弹性等属性,从而实现更加真实和丰富的物理效果。本节将详细介绍物理材质的原理和应用方法,并通过具体的代码示例来演示如何在项目中使用......