当前位置:首页 > 人生 > 作品墙 > 正文内容

ProgressBarD 圆形水波进度条调用文档

作品墙3天前


命名空间: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
CategoryCCon
DefaultValueLeftOrRight.Right
作用波浪水平流动的方向。
Right → 波浪视觉上向右流动;
Left → 波浪视觉上向左流动
副作用赋值后立即 Invalidate()
备注其效果在下一帧 Tick 时生效,不会立刻改变当前相位
bar.WaveDirection = LeftOrRight.Right;  // 向右
bar.WaveDirection = LeftOrRight.Left;   // 向左

public float WaveSpeed

项目说明
类型float
默认0.10f
CategoryCCon
DefaultValue0.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
CategoryCCon
DefaultValue0.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 的成员(由本类绘制逻辑推断)

成员类型(推断)在本类中的作用用法示例
Minint进度下限;Value 低于它时按 Min 处理bar.Min = 0;
Maxint进度上限;计算水位比例的分母。Max ≤ Min 时水位不绘制bar.Max = 100;
Valueint当前进度;决定水位高度bar.Value = 45;
ColorBackColor背景圆颜色bar.ColorBack = Color.Gray;
ColorCoreColor水位/内层波浪颜色。
外层波浪会自动使用 Color.FromArgb(100, ColorCore)(约 39% 不透明度)
bar.ColorCore = Color.DeepSkyBlue;
BorderColorColor圆环边框颜色bar.BorderColor = Color.Black;
BorderSizeint圆环边框粗细(像素);实际绘制时会自动 Clamp 到圆半径以内bar.BorderSize = 4;

事件(如 Change / ValueChanged)与其它方法请参考 ProgressBarBase 源码。

4.3 绘制逻辑所使用到的 Control 继承成员

成员来源说明
TextControl控件文本;本类在圆形中央居中绘制
FontControl文本字体
ForeColorControl文本颜色
ClientSizeControl用于计算圆的直径和位置
Invalidate()Control属性变化或 Tick 时触发重绘
Visible / IsHandleCreated / DesignModeControl用于控制 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 / ValueProgressBarBase属性(int)进度范围与当前值
ColorBack / ColorCore / BorderColor / BorderSizeProgressBarBase属性背景圆 / 水位 / 边框 外观
Text / Font / ForeColorControl属性中央文字
Size / ClientSizeControl属性(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 - 1fGDI+ 抗锯齿下,直径恰好等于 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; 直接返回,不绘制。

分支三:中间水位(核心)

步骤代码作用与“为什么”
① 计算水面基准线 YbaseY = rect.Bottom - ratio * rect.Height从底部向上按 ratio 比例抬升;ratio=0 时 baseY = Bottom,ratio=1 时 baseY = Top
② 计算振幅自适应上限maxUp = baseY - rect.Top - 1
maxDown = rect.Bottom - baseY - 1
ampCap = 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 - cy
dx = 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 个点
⑧ 计算每个点的波浪 Yenvelope = 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 = +1phase 增大时波峰 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
    };
}



转载请注明出处。

本文链接:http://www.pythonopen.com/?id=7977

相关文章

已经实现的一些算法

编码CConA1是一种可复原的编码算法,用于将文本或字节转换为纯字母。可用于文件名混淆,避免因包含违...

横向滚动条HScrollBar 控件调用文档

命名空间:CConUI继承:System.Windows.Forms.Control版本:基于所提供...

纵向滚动条VScrollBar 控件调用文档

命名空间:CConUI继承:System.Windows.Forms.Control版本:基于所提供...

菜单MenuItem 与 ContextMenu 控件调用文档

命名空间:CConUI依赖:System.Windows.Forms、System.Drawing、...

图片框PictureBox 控件调用文档

命名空间:CConUI继承:System.Windows.Forms.Control定位:自绘图片框...

蚂蚁线AntLine 控件调用文档

命名空间:CConUI继承:System.Windows.Forms.Control定位:虚线蚂蚁线...