一、重写 OnPaint 事件给仪表盘画「颜值」核心绘制逻辑都在这仪表盘的所有外观效果比如底圆、渐变扇形、环形、中心文字都靠OnPaint 事件实现这是自定义控件绘制的核心入口。新手刚开始看这段代码会觉得懵其实只要搞懂核心关键词和基础逻辑就能轻松上手。1. OnPaint 核心骨架代码所有绘制操作都基于这个骨架展开先把基础结构写对再逐步添加绘图逻辑避免一步到位出错。// 重写父类UserControl的OnPaint事件实现自定义绘制 protected override void OnPaint(PaintEventArgs e) { // 1. 执行父类默认绘制逻辑必写新手最容易漏 // 作用绘制控件的基础背景、边框删掉会导致控件背景变黑/透明 base.OnPaint(e); // 2. 获取画布对象——所有绘制操作的核心工具 // Graphics就像画画的画笔画布能画线条、圆形、扇形、文字、渐变等 Graphics graphics e.Graphics; // 3. 画布基础设置仪表盘必备抗锯齿让图形/文字边缘更平滑 graphics.SmoothingMode SmoothingMode.AntiAlias; // 图形抗锯齿 graphics.TextRenderingHint TextRenderingHint.AntiAliasGridFit; // 文字抗锯齿 graphics.PixelOffsetMode PixelOffsetMode.HighQuality; // 提升绘制质量 // 后续绘制底圆、扇形、内圆、文字的逻辑都写在这里 }2. 核心关键词拆解新手秒懂不用死记硬背理解每个关键词的作用就能看懂绘制逻辑的底层原理关键词通俗解释新手必知要点protected访问修饰符只有当前控件和它的子类能访问外部代码无法修改避免绘图逻辑被误改比如子类速度仪表盘能复用父类仪表盘的绘制方法override重写父类 UserControl 自带的 OnPaint 只会画一个空白框我们用 override「替换」成自己的仪表盘样式PaintEventArgs e绘制工具箱里面的 Graphics 对象是核心没有它就无法执行任何绘制操作base.OnPaint(e)执行父类默认绘制必写删掉会导致控件背景变黑、透明或出现各种样式异常Graphics画布 / 画笔所有绘图操作的入口画圆、扇形、文字都要通过它的方法实现3. 分步实现仪表盘绘制从底到顶避免图层覆盖仪表盘的绘制遵循由底到顶的逻辑先画底层的底圆再画中间的渐变扇形接着画内圆形成环形最后画顶层的中心文字避免上层图形被底层覆盖。每一步都讲清代码 逻辑 避坑点新手能直接跟着写。步骤 1绘制底圆 —— 仪表盘的基础背景底圆是仪表盘的底层背景核心用到SolidBrush纯色画刷和FillEllipse绘制椭圆 / 圆方法WinForm 中没有直接画圆的方法通过正方形的内切椭圆实现圆的效果。// 1. 实例化纯色画刷设置底圆颜色gapColor是后续封装的可配置属性 SolidBrush solidBrush new SolidBrush(gapColor); // 2. 定义外接矩形x2,y2 避免控件边缘超出宽高为控件宽高-4保证是正方形 Rectangle rectangle new Rectangle(2, 2, this.Width - 4, this.Height - 4); // 3. 绘制椭圆正方形内切就是圆形 graphics.FillEllipse(solidBrush, rectangle);新手避坑定义矩形时要保证宽高相等否则画出来的是椭圆不是正圆坐标不要设为 0否则图形会贴紧控件边缘显示不美观。步骤 2绘制渐变扇形 —— 仪表盘的核心数据展示区扇形用于展示当前数值的占比核心用到LinearGradientBrush线性渐变画刷和FillPie绘制扇形方法关键是实现数值与角度的换算让扇形随数值动态变化。// 1. 实例化线性渐变画刷设置渐变起始色、结束色均为可配置属性 LinearGradientBrush linearGradientBrush new LinearGradientBrush(rectangle, startColor, endColor, LinearGradientMode.BackwardDiagonal); // 2. 数值转角度将[minValue,maxValue]映射为[0°,360°] float sweepangle ((currentValue - this.minValue) / (this.maxValue - this.minValue) * 360.0f); // 3. 绘制扇形起始角度-90°垂直向上仪表盘常规起始位置扫过角度为计算后的角度 graphics.FillPie(linearGradientBrush, rectangle, -90.0f, sweepangle);新手必懂知识点FillPie的起始角度WinForm 中以水平向右为 0°顺时针为正方向-90°刚好是垂直向上符合仪表盘的使用习惯数值角度换算公式核心是计算当前值在「最小值 - 最大值」中的占比再乘以 360°实现数值到图形的动态映射。步骤 3绘制内圆 —— 将实心圆转为环形当前绘制的是实心圆 扇形需要绘制一个与底圆同心的内圆覆盖中间区域形成环形效果核心是缩小外接矩形尺寸保证同心。// 1. 重新设置画刷颜色为控件背景色覆盖中间区域 solidBrush new SolidBrush(BackColor); // 2. 定义内圆外接矩形边距为gapWidth宽高为控件宽高-2*gapWidth保证与底圆同心 rectangle new Rectangle(gapWidth, gapWidth, this.Width - 2 * gapWidth, this.Height - 2 * gapWidth); // 3. 绘制内圆形成环形 graphics.FillEllipse(solidBrush, rectangle);步骤 4绘制中心文字 —— 直观显示数值支持双模式最后在环形中心绘制数值文字支持百分比显示和原始数值 单位显示两种模式同时实现文字的水平 垂直居中让显示更美观。// 1. 根据是否显示百分比拼接文字内容 string content string.Empty; if (isPercent) { // 百分比模式保留2位小数拼接%符号 content ((currentValue - this.minValue) / (this.maxValue - this.minValue) * 100.0f).ToString(f2) %; } else { // 原始数值模式保留2位小数拼接自定义单位如℃、km/h content currentValue.ToString(f2) unit; } // 2. 设置文字对齐方式水平垂直居中 StringFormat stringFormat new StringFormat(); stringFormat.LineAlignment StringAlignment.Center; // 垂直居中 stringFormat.Alignment StringAlignment.Center; // 水平居中 // 3. 绘制文字参数为内容、字体、画刷、绘制区域、对齐方式 graphics.DrawString(content, this.valueFont, Brushes.Coral, rectangle, stringFormat);新手必懂知识点ToString(f2)将浮点数保留 2 位小数避免数值过长影响显示效果文字绘制区域用内圆的外接矩形作为绘制区域配合居中设置文字会自动显示在环形中心。4. 画刷的父子关系新手别再混淆 Brush 和 SolidBrush开发时我曾疑惑为什么一会用 Brush一会用 SolidBrush实现的效果好像都是填充颜色两者有什么区别后来才明白Brush 是抽象基类SolidBrush 是它的子类所有具体的画刷都继承自 Brush核心区别在「通用性」和「具体用途」。画刷的继承关系一张图看懂生活类比新手秒懂把 Brush 比作「笔」这个通用概念定义了「笔能用来涂色」这个核心功能但没有具体说明是什么笔而 SolidBrush、LinearGradientBrush 是具体的笔SolidBrush 马克笔只能涂单一纯色LinearGradientBrush 渐变色水彩笔能涂从蓝到红、从绿到黄的渐变颜色HatchBrush 花纹笔能涂格子、条纹等纹理。开发时根据需求选择具体的子类画刷即可比如画纯色底圆用 SolidBrush画渐变扇形用 LinearGradientBrush。二、封装可配置属性让仪表盘「灵活可调」告别硬编码新手开发控件最容易犯的错把数值写死比如固定最大值 100、底圆颜色固定为灰色后期想修改数值或样式要改代码、重新编译非常麻烦。正确的做法是把仪表盘的关键参数封装成可配置属性在 VS 的属性窗口中直接修改实时预览效果不用动代码。1. 新手快捷技巧propfull 快捷键生成属性代码不用手动敲完整的属性代码在 VS 中输入propfull按两次 Tab 键就能自动生成属性的完整模板只需要修改字段类型、字段名、属性名效率提升一倍。2. 核心可配置属性封装完整代码新手可直接复用封装的核心是私有字段 公共属性 特性标签 Invalidate () 刷新同时要引用System.ComponentModel和System.Drawing命名空间否则特性和颜色 / 字体类型会报错。using System; using System.ComponentModel; // 特性标签必备命名空间 using System.Drawing; // 颜色、字体必备命名空间 using System.Windows.Forms; namespace xbd.ControlLib { public partial class Gauge : UserControl { public Gauge() { InitializeComponent(); } #region 仪表盘核心可配置属性 // 1. 最大值 private float maxValue 100.0F; [Browsable(true)] // 让属性显示在VS属性窗口 [Category(自定义属性)] // 属性分组方便查找 [Description(设置或获取仪表盘的最大值)] // 鼠标悬停提示 public float MaxValue { get { return maxValue; } set { maxValue value; this.Invalidate(); // 关键修改属性后刷新控件实时显示效果 } } // 2. 最小值 private float minValue 0.0F; [Browsable(true)] [Category(自定义属性)] [Description(设置或获取仪表盘的最小值)] public float MinValue { get { return minValue; } set { minValue value; this.Invalidate(); } } // 3. 当前值 private float currentValue 70.0F; [Browsable(true)] [Category(自定义属性)] [Description(设置或获取仪表盘的当前值)] public float CurrentValue { get { return currentValue; } set { currentValue value; this.Invalidate(); } } // 4. 环形边距宽度内圆与底圆的间距 private int gapWidth 22; [Browsable(true)] [Category(自定义属性)] [Description(设置或获取仪表盘的环形边距宽度)] public int GapWidth { get { return gapWidth; } set { gapWidth value; this.Invalidate(); } } // 5. 底圆背景色 private Color gapColor Color.FromArgb(93, 107, 153); [Browsable(true)] [Category(自定义属性)] [Description(设置或获取仪表盘底圆的背景色)] public Color GapColor { get { return gapColor; } set { gapColor value; this.Invalidate(); } } // 6. 中心文字字体 private Font valueFont new Font(华为宋体, 14.0f); [Browsable(true)] [Category(自定义属性)] [Description(设置或获取仪表盘中心文字的字体)] public Font ValueFont { get { return valueFont; } set { valueFont value ?? new Font(微软雅黑, 12f); // 容错避免字体为null报错 this.Invalidate(); } } // 7. 扇形渐变起始色 private Color startColor Color.Blue; [Browsable(true)] [Category(自定义属性)] [Description(设置或获取仪表盘扇形的渐变起始颜色)] public Color StartColor { get { return startColor; } set { startColor value; this.Invalidate(); } } // 8. 扇形渐变结束色 private Color endColor Color.Red; [Browsable(true)] [Category(自定义属性)] [Description(设置或获取仪表盘扇形的渐变结束颜色)] public Color EndColor { get { return endColor; } set { endColor value; this.Invalidate(); } } // 9. 是否以百分比显示数值 private bool isPercent true; [Browsable(true)] [Category(自定义属性)] [Description(设置或获取是否以百分比形式显示数值)] public bool IsPercent { get { return isPercent; } set { isPercent value; this.Invalidate(); } } // 10. 数值显示单位如℃、km/h、% private string unit ℃; [Browsable(true)] [Category(自定义属性)] [Description(设置或获取仪表盘数值的显示单位)] public string Unit { get { return unit; } set { unit value; this.Invalidate(); } } #endregion // 重写OnPaint事件的绘制逻辑写在这里上文已讲 protected override void OnPaint(PaintEventArgs e) { // 此处放上文的核心绘制代码 } } }上一章小白入门WinForm 自定义仪表盘控件开发一