ProgressBarD 圆形水波进度条调用文档
命名空间:CConUI
继承:ProgressBarBase(自定义基类) → Control
可见性:public class ProgressBarD(可外部使用)
定位:水波(水位)圆形进度条——在圆形区域内以“液面上升 + 波浪流动”的方式表现进度,附带持续动画(Timer 驱动),视觉上像一杯被逐渐注满的水。
一、作用
在控件区域内绘制一个居中的正圆,使用“液位 + 波浪”的方式表现进度:
底层:填满的背景圆(
ColorBack);中层:从底部向上填充的水位区(
ColorCore),水面是一条正弦波浪线;外层:叠加一层半透明(Alpha≈100)的“外层波浪”,位置比内层略高
0.005倍高度、相位差1.3弧度,形成立体波纹感;波浪持续流动(由内部
Timer每 30ms 驱动相位),可设置方向/速度/振幅;最外层:圆环边框(
BorderColor/BorderSize);中央可显示文本
Text(ForeColor/Font)。
视觉进度示意
| 进度 | 视觉效果 |
|---|---|
Value <= Min | 圆内只有背景 + 边框 + 文字,无水位 |
| 0 < 进度 < 100% | 圆底部出现“液面”,波浪线在液面处持续波动,随进度上升 |
| 进度 = 100% | 整圆被 ColorCore 填满,无波浪(走“满水”分支,直接 360° 填充) |
与
ProgressBarB/ProgressBarC的区别:
-ProgressBarB:从 12 点顺时针扇形扫过,静态;
-ProgressBarC:从底部向两侧对称展开,静态;
-ProgressBarD:水位 + 波浪,持续动画,Visual 更有活力,适合“加载中”“上传中”等需要动感提示的场景。
二、快速上手
using System.Drawing;
using System.Windows.Forms;
using CConUI;
using CConPlus.Enum;
// 1. 创建控件
var bar = new ProgressBarD();
// 2. 放入容器(建议尺寸接近正方形)
bar.Parent = panel1;
bar.Size = new Size(140, 140);
bar.Location = new Point(20, 20);
// 3. 设置范围与当前值
bar.Min = 0;
bar.Max = 100;
bar.Value = 45;
bar.Text = "45%";
// 4. 外观
bar.ColorBack = Color.FromArgb(45, 45, 48);
bar.ColorCore = Color.FromArgb(0, 180, 220);
bar.BorderColor = Color.FromArgb(30, 30, 30);
bar.BorderSize = 4;
bar.ForeColor = Color.White;
bar.Font = new Font("Microsoft YaHei", 10F, FontStyle.Bold);
// 5. 波浪效果调节
bar.WaveDirection = LeftOrRight.Right; // 波浪向右流动
bar.WaveSpeed = 0.10f; // 0.01 ~ 0.5
bar.WaveAmplitude = 0.04f; // 0.01 ~ 0.15
// 无需手动调用,Timer 会自动启动(Handle 创建后且 Visible=true)三、构造函数与生命周期
public ProgressBarD()
| 项目 | 说明 |
|---|---|
| 参数 | 无 |
| 作用 | 构造水波进度条 |
| 实际行为 | ① 调用 ProgressBarBase 的无参构造函数;② 创建内部 Timer(Interval = 30,即约 33fps),并挂接 Tick 处理;③ Tick 处理:非设计模式下按 WaveDirection 与 WaveSpeed 更新 _wavePhase,再触发 Invalidate() 重绘 |
| Timer 启动时机 | 见下方 OnHandleCreated / OnVisibleChanged |
| 默认尺寸 | 以 ProgressBarBase 为准;使用本控件时强烈建议手动设置接近正方形的 Size |
生命周期方法(protected override,仅内部调用)
| 方法 | 触发时机 | 行为 |
|---|---|---|
OnHandleCreated(EventArgs e) | 控件句柄创建后 | 若 !DesignMode,启动 _waveTimer |
OnHandleDestroyed(EventArgs e) | 控件句柄销毁后 | 停止 _waveTimer |
OnVisibleChanged(EventArgs e) | Visible 属性变化时 | Visible && IsHandleCreated → 启动;否则停止。此机制保证隐藏时不耗 CPU |
Dispose(bool disposing) | 控件释放时 | 若 disposing,则 _waveTimer?.Dispose() |
结论:只要控件可见且句柄已创建,波浪就会持续自动流动,无需手动启动/停止。
设计模式下(VS 设计器)Timer 不会启动,不会占用设计器 CPU。
四、公开属性
4.1 本类新增的属性(波浪控制)
public CConPlus.Enum.LeftOrRight WaveDirection
| 项目 | 说明 |
|---|---|
| 类型 | CConPlus.Enum.LeftOrRight |
| 默认 | CConPlus.Enum.LeftOrRight.Right |
| Category | CCon |
| DefaultValue | LeftOrRight.Right |
| 作用 | 波浪水平流动的方向。Right → 波浪视觉上向右流动;Left → 波浪视觉上向左流动 |
| 副作用 | 赋值后立即 Invalidate() |
| 备注 | 其效果在下一帧 Tick 时生效,不会立刻改变当前相位 |
bar.WaveDirection = LeftOrRight.Right; // 向右 bar.WaveDirection = LeftOrRight.Left; // 向左
public float WaveSpeed
| 项目 | 说明 |
|---|---|
| 类型 | float |
| 默认 | 0.10f |
| Category | CCon |
| DefaultValue | 0.10f |
| 作用 | 每次 Tick 时相位 _wavePhase 的增量幅度。数值越大,波浪流动越快 |
| 取值范围 | 内部 Math.Max(0.01f, Math.Min(0.5f, value))——自动 Clamp 到 [0.01, 0.5] |
| 副作用 | 无 Invalidate()——因为只影响速度,不影响当前画面;下一帧 Tick 会自然应用 |
bar.WaveSpeed = 0.05f; // 慢一点 bar.WaveSpeed = 0.30f; // 快一点
public float WaveAmplitude
| 项目 | 说明 |
|---|---|
| 类型 | float |
| 默认 | 0.04f |
| Category | CCon |
| DefaultValue | 0.04f |
| 作用 | 波浪振幅(正弦波的峰谷高度),以圆直径的百分比表示。0.04 ≈ 峰谷 4% 直径,0.15 则波动明显得多 |
| 取值范围 | 内部 Math.Max(0.01f, Math.Min(0.15f, value))——自动 Clamp 到 [0.01, 0.15] |
| 副作用 | 赋值后立即 Invalidate() |
| 额外保护 | 在 FillWater 内部还会根据“水位离圆上下边缘的距离”再压一次振幅——水位很低/很高时振幅会被自动削减,避免波浪穿出圆外 |
bar.WaveAmplitude = 0.02f; // 波平缓 bar.WaveAmplitude = 0.10f; // 波明显
4.2 继承自 ProgressBarBase 的成员(由本类绘制逻辑推断)
| 成员 | 类型(推断) | 在本类中的作用 | 用法示例 |
|---|---|---|---|
Min | int | 进度下限;Value 低于它时按 Min 处理 | bar.Min = 0; |
Max | int | 进度上限;计算水位比例的分母。Max ≤ Min 时水位不绘制 | bar.Max = 100; |
Value | int | 当前进度;决定水位高度 | bar.Value = 45; |
ColorBack | Color | 背景圆颜色 | bar.ColorBack = Color.Gray; |
ColorCore | Color | 水位/内层波浪颜色。 外层波浪会自动使用 Color.FromArgb(100, ColorCore)(约 39% 不透明度) | bar.ColorCore = Color.DeepSkyBlue; |
BorderColor | Color | 圆环边框颜色 | bar.BorderColor = Color.Black; |
BorderSize | int | 圆环边框粗细(像素);实际绘制时会自动 Clamp 到圆半径以内 | bar.BorderSize = 4; |
事件(如
Change/ValueChanged)与其它方法请参考ProgressBarBase源码。
4.3 绘制逻辑所使用到的 Control 继承成员
| 成员 | 来源 | 说明 |
|---|---|---|
Text | Control | 控件文本;本类在圆形中央居中绘制 |
Font | Control | 文本字体 |
ForeColor | Control | 文本颜色 |
ClientSize | Control | 用于计算圆的直径和位置 |
Invalidate() | Control | 属性变化或 Tick 时触发重绘 |
Visible / IsHandleCreated / DesignMode | Control | 用于控制 Timer 是否启动 |
五、公开事件
ProgressBarD没有定义任何新的事件,全部事件继承自ProgressBarBase。
若基类提供Change或ValueChanged事件,订阅方式与其它ProgressBar*一致:
bar.Change += (s, e) =>
{
var b = (ProgressBarD)s;
Console.WriteLine("当前进度:" + b.Value);
};六、公开方法
ProgressBarD没有新增 public 方法。
所有方法(如Invalidate()、Focus()、BringToFront()等)均继承自Control;ProgressBarBase若定义了公开方法也照常继承可用。
内部方法(不可外部调用)
| 方法 | 可见性 | 作用 |
|---|---|---|
OnPaint(PaintEventArgs e) | protected override | 整体绘制:背景 → 水位+波浪 → 文字 → 边框 |
FillWater(Graphics g, RectangleF rect, float ratio, float phase, Color color) | private | 核心算法:按比例与相位绘制水位 + 波浪,并精细处理与圆边界的交点、底部圆弧、抗锯齿填缝 |
七、公开成员速查表
| 成员 | 来源 | 类型 | 说明 |
|---|---|---|---|
ProgressBarD() | 本类 | 构造函数 | 创建 Timer(30ms),自动启停与生命周期联动 |
WaveDirection | 本类 | 属性(LeftOrRight) | 波浪方向(默认 Right) |
WaveSpeed | 本类 | 属性(float) | 波浪速度(0.01~0.5,默认 0.10) |
WaveAmplitude | 本类 | 属性(float) | 波浪振幅(0.01~0.15,默认 0.04) |
Min / Max / Value | ProgressBarBase | 属性(int) | 进度范围与当前值 |
ColorBack / ColorCore / BorderColor / BorderSize | ProgressBarBase | 属性 | 背景圆 / 水位 / 边框 外观 |
Text / Font / ForeColor | Control | 属性 | 中央文字 |
Size / ClientSize | Control | 属性(Size) | 建议设为正方形 |
Invalidate() | Control | 方法 | 触发重绘 |
OnPaint(e) | 本类(override) | 方法(protected) | 圆形绘制逻辑,外部不可直接调用 |
FillWater(...) | 本类 | 方法(private) | 水位 + 波浪绘制,外部不可直接调用 |
事件(如
Change/ValueChanged)请参考ProgressBarBase源码。
八、绘制与动画原理(深入理解)
以下分析帮你在调参、调色、排查“波浪出现缺口/穿出圆外/闪烁”等问题时能快速定位。
8.1 外圆与内圆计算
float side = Math.Min(ClientSize.Width, ClientSize.Height); if (side <= 4f) return; float d = side - 1f; // 直径,减 1 避免抗锯齿裁边 float ox = (ClientSize.Width - d) / 2f; // 水平居中偏移 float oy = (ClientSize.Height - d) / 2f; // 垂直居中偏移 var outer = new RectangleF(ox, oy, d, d); int border = Math.Max(0, Math.Min(this.BorderSize, (int)(d / 2f) - 1)); var inner = new RectangleF(outer.X + border, outer.Y + border, outer.Width - border * 2f, outer.Height - border * 2f);
| 要点 | 说明 |
|---|---|
d = side - 1f | GDI+ 抗锯齿下,直径恰好等于 side 时最右/最下会被裁半像素,所以减 1 |
min(宽, 高) | 无论控件是否为正方形,都绘制正圆;多出的空间留白 |
border 上限 d/2 - 1 | 避免边框把内圆压缩到 0 或负数;有 Math.Max(0, ...) 兜底防止负数 |
inner | 内圆 = 外圆向内缩 border 像素;所有水位/波浪/文字都画在 inner 或 outer 上 |
8.2 绘制背景圆
using (var brush = new SolidBrush(this.ColorBack)) g.FillPie(brush, inner.X, inner.Y, inner.Width, inner.Height, -90f, 360f);
用 FillPie(360°)而不是 FillEllipse,是为了和后面的波浪图层走同一段 GDI+ 弧线路径,抗锯齿边缘像素级重合,避免出现 1px 的背景缝。
8.3 计算进度比例
float ratio = 0f;
if (this.Max > this.Min)
{
int v = Math.Max(this.Min, Math.Min(this.Max, this.Value));
ratio = (float)(v - this.Min) / (this.Max - this.Min);
}Max ≤ Min时不计算,ratio = 0(不画水位);Value被 clamp 到[Min, Max],避免越界导致ratio<0 或 >1。
8.4 双层波浪绘制
if (ratio > 0f)
{
// 外层波浪:ratio + 0.005,相位 + 1.3,半透明
FillWater(g, inner, Math.Min(1f, ratio + 0.005f), _wavePhase + 1.3f,
Color.FromArgb(100, this.ColorCore));
// 内层波浪:真实 ratio,真实相位,实色
FillWater(g, inner, ratio, _wavePhase, this.ColorCore);
}| 要点 | 说明 |
|---|---|
外层比内层高 0.005 | 高度多约 0.5% 直径,让外层波浪略微“冒出”内层顶部 |
外层相位差 1.3 | 让两层正弦波的波峰错开,形成“波纹叠加”的立体效果 |
外层透明度 100 | 约 39% 不透明度,透出内层颜色,形成深度层次 |
| 先画外层再画内层 | 内层(实色)覆盖外层大部分,只留下高出的部分可见 |
Math.Min(1f, ratio + 0.005f) | 防止 ratio = 1 时外层超出 1 |
8.5 FillWater 核心算法
分支一:满水位(ratio ≥ 1)
if (ratio >= 1f)
{
using (var b = new SolidBrush(color))
g.FillPie(b, rect.X, rect.Y, rect.Width, rect.Height, -90f, 360f);
return;
}直接画整圆,不画波浪(否则满水时波浪会溢出圆外)。
分支二:空水位(ratio ≤ 0)
入口处 if (ratio <= 0f) return; 直接返回,不绘制。
分支三:中间水位(核心)
| 步骤 | 代码 | 作用与“为什么” |
|---|---|---|
| ① 计算水面基准线 Y | baseY = rect.Bottom - ratio * rect.Height | 从底部向上按 ratio 比例抬升;ratio=0 时 baseY = Bottom,ratio=1 时 baseY = Top |
| ② 计算振幅自适应上限 | maxUp = baseY - rect.Top - 1maxDown = rect.Bottom - baseY - 1ampCap = max(0, min(maxUp, maxDown)) | 水位离上/下边界的最近距离。若振幅超过这个距离,波浪就会穿出圆,所以提前 clamp。-1 是安全余量 |
| ③ 最终振幅 | amplitude = min(rect.Height * _waveAmplitude, ampCap)if (amplitude < 0.5f) amplitude = 0 | 以直径百分比配置波浪大小;但不超过上/下余量。振幅过小(<0.5px)时直接归零,避免“看起来像噪声” |
| ④ 波长 | wavelength = rect.Width * 0.55f | 大约“整个圆宽能装 1.8 个波”。视觉上既能看清波动,又不至于过密 |
| ⑤ 求水面与圆的左右交点 | dy = baseY - cydx = sqrt(max(0, r*r - dy*dy))xStart = cx - dx; xEnd = cx + dx | 圆方程 (x-cx)² + (y-cy)² = r²;给定 y = baseY,解出 x 区间。关键:保证水面两端正好与圆的弧相切,不会强制拉宽到整个直径 |
| ⑥ 求底部圆弧的角度区间 | theta1 = atan2(dy, dx) * 180/πtheta2 = atan2(dy, -dx) * 180/πsweep = theta2 - theta1; if (sweep <= 0) sweep += 360 | 求右交点/左交点相对圆心在屏幕坐标系(Y 向下)下的角度;sweep 是从左交点到右交点沿下半圆顺时针扫过的角度。当 theta2 - theta1 为负时 +360 保证顺时针走下半部 |
| ⑦ 采样波浪上边界 | N = 120,i = 0..N,x = xStart + stepX*i | 把 [xStart, xEnd] 均匀采样 121 个点 |
| ⑧ 计算每个点的波浪 Y | envelope = sin(π * i/N)waveY = baseY + amplitude * envelope * sin(2π*(x-xStart)/wavelength + phase) | 核心点: · sin(2π*(x-xStart)/λ + phase) 是标准正弦波;· envelope = sin(π*i/N) 是从 0→1→0 的包络线,让波浪两端振幅衰减到 0,与圆弧交点平滑汇合,避免出现断口 |
| ⑨ 限制波浪不穿出圆 | yTop = cy - sqrt(r² - dxX²)yBottom = cy + sqrt(r² - dxX²)yUpper = max(yTop, waveY); if (yUpper > yBottom) yUpper = yBottom | 针对每个采样点,先求该 X 处圆的上/下边界;再把 waveY clamp 到 [yTop, yBottom],保证波浪即便被异常放大也不会穿出圆 |
| ⑩ 组合路径 | path.AddLines(topPoints)path.AddArc(rect, theta1, sweep)path.CloseFigure() | ① 沿波浪上边界从左到右画折线;② 再从右交点顺时针扫底部圆弧到左交点;③ 闭合。AddLines 与 AddArc 会自动用直线连接首尾,形成封闭“水面 + 圆弧”的区域 |
| ⑪ 填充 + 描边 | g.FillPath(b, path)g.DrawPath(new Pen(color, 1f), path) | 用同色 Pen 描 1px 边,用来填补抗锯齿造成的半透明缝,让边缘看起来实心无缝 |
8.6 相位更新逻辑
_waveTimer.Tick += (s, e) =>
{
if (this.DesignMode) return;
// Right → dir = -1;Left → dir = +1
float dir = _waveDirection == CConPlus.Enum.LeftOrRight.Right ? -1f : 1f;
_wavePhase += _waveSpeed * dir;
// 归一到 [0, 2π),避免数值无限增大导致精度问题
float twoPi = (float)(Math.PI * 2);
_wavePhase %= twoPi;
Invalidate();
};| 要点 | 说明 |
|---|---|
Right → dir = -1 | 波浪函数是 sin(kx + phase),phase 减小时,同一波峰对应的 x 增大 → 视觉向右移动 |
Left → dir = +1 | phase 增大时波峰 x 减小 → 视觉向左移动 |
% twoPi | 正弦函数周期 2π,相位只在 [0, 2π) 内变化即可;否则连续累积会导致 float 精度衰减 |
if (DesignMode) return; | VS 设计器里不更新相位,避免占 CPU / 抖动 |
九、完整使用示例
示例 1:基础水波进度条
using System.Drawing;
using System.Windows.Forms;
using CConUI;
using CConPlus.Enum;
var bar = new ProgressBarD
{
Parent = panel1,
Size = new Size(140, 140),
Location = new Point(20, 20),
Min = 0,
Max = 100,
Value = 45,
Text = "45%",
ColorBack = Color.FromArgb(45, 45, 48),
ColorCore = Color.FromArgb(0, 180, 220),
BorderColor = Color.FromArgb(30, 30, 30),
BorderSize = 4,
ForeColor = Color.White,
Font = new Font("Microsoft YaHei", 10F, FontStyle.Bold),
WaveDirection = LeftOrRight.Right,
WaveSpeed = 0.10f,
WaveAmplitude = 0.04f
};示例 2:上传进度动画
var bar = new ProgressBarD
{
Parent = this,
Size = new Size(150, 150),
Min = 0,
Max = 100,
Value = 0,
Text = "上传中 0%",
ColorBack = Color.FromArgb(240, 240, 240),
ColorCore = Color.DodgerBlue,
BorderColor = Color.LightGray,
BorderSize = 3,
ForeColor = Color.FromArgb(40, 40, 40),
Font = new Font("Segoe UI", 10F),
WaveDirection = LeftOrRight.Right,
WaveSpeed = 0.15f,
WaveAmplitude = 0.06f
};
var timer = new Timer { Interval = 100 };
timer.Tick += (s, e) =>
{
if (bar.Value >= bar.Max)
{
bar.Text = "上传完成";
timer.Stop();
return;
}
bar.Value += 1;
bar.Text = "上传中 " + bar.Value + "%";
};
timer.Start();示例 3:静态截图 / 打印场景(停止动画)
// 截图或打印时,希望波浪静止在某一个相位 // 直接 Hide 一下再 Show,即可暂停/恢复动画(内部 OnVisibleChanged 会启停 Timer) bar.Hide(); // 停止 Tick // ... 截图 ... bar.Show(); // 恢复 Tick // 或者临时设置设计模式不可行,最简单的写法: bar.SendToBack(); // 不推荐——这不会停 Timer // 正确做法:控制 Visible
若你需要完全关闭动画,最简单的办法:
- 将WaveSpeed设为0.01f并手动Invalidate()一次,视觉上变化极慢;
- 或把Visible设为false(但控件也不可见了)。
本类未提供WaveEnabled开关,若业务上需要频繁关闭动画,建议提 PR 在基类或本类加一个bool AnimateWave { get; set; }。
示例 4:深色主题 + 半透明效果
var bar = new ProgressBarD
{
Parent = this,
Size = new Size(120, 120),
Min = 0,
Max = 100,
Value = 65,
Text = "65%",
ColorBack = Color.FromArgb(30, 30, 34),
ColorCore = Color.FromArgb(120, 200, 255), // 亮蓝
BorderColor = Color.FromArgb(60, 60, 66),
BorderSize = 3,
ForeColor = Color.White,
Font = new Font("Consolas", 11F, FontStyle.Bold),
WaveDirection = LeftOrRight.Left, // 向左流动
WaveSpeed = 0.08f,
WaveAmplitude = 0.05f
};示例 5:多个水波进度条网格
for (int i = 0; i < 4; i++)
{
var bar = new ProgressBarD
{
Parent = panel1,
Size = new Size(90, 90),
Location = new Point(10 + i * 100, 10),
Min = 0,
Max = 100,
Value = 25 * (i + 1),
Text = (25 * (i + 1)) + "%",
ColorCore = Color.FromArgb(0, 150 + i * 30, 200 - i * 30),
BorderSize = 3,
WaveSpeed = 0.10f + i * 0.05f, // 每个快一点,错开视觉
WaveDirection = i % 2 == 0 ? LeftOrRight.Right : LeftOrRight.Left
};
}转载请注明出处。