ProgressBarE 雷达加载进度条调用文档
命名空间:CConUI
继承:ProgressBarBase(自定义基类) → Control
可见性:public class ProgressBarE(可外部使用)
定位:雷达加载动画——把圆环切成若干扇形“扇块”,用 Value 轮流点亮(或累积点亮),配合 Timer 循环推进,形成经典的“雷达扫描 / Loading”视觉。
一、作用
在控件区域内绘制一个居中的环形进度条,环被均分为
Max个扇形块(块与块之间留PieSpace度空隙),用两种模式点亮:
模式 One(单个闪烁):任一时刻只点亮
Value - 1索引那一块(即Value = 1时点亮第 1 块,Value = 8时点亮第 8 块),视觉像雷达指针在逐格扫描;模式 Two(累积点亮):从第 1 块点亮到第
Value块,视觉像进度从 0 走到 100%;配合内部
Timer(默认 100ms/次),Value在0 ~ Max之间循环递增(到顶后回到 0),实现“永不停止的加载动画”。
视觉示意
| 模式 | Value 序列(Max=8) | 视觉 |
|---|---|---|
ModeTwo.One | 0 → 1 → 2 → … → 8 → 0 → … | Value=0 时全部块都是背景色(无点亮);Value=1~8 时第 Value-1 块点亮。视觉上是一个亮点绕圈,每轮有一次“全灭”瞬间 |
ModeTwo.Two | 0 → 1 → 2 → … → 8 → 0 → … | Value=0 时全部灭;Value=N 时第 1~N 块点亮。满环瞬间后立刻重置为全灭 |
与
ProgressBarB / C / D的区别:
ProgressBarB:扇形扫过(静态);
ProgressBarC:从底部展开(静态);
ProgressBarD:水位 + 波浪(动画);
ProgressBarE:环被切块、逐块点亮(动画),通常表示“无限循环加载中”,而不是“业务进度”。
二、快速上手
using System.Drawing;
using System.Windows.Forms;
using CConUI;
using CConPlus.Enum;
// 1. 创建控件
var bar = new ProgressBarE();
// 2. 放入容器(建议尺寸接近正方形)
bar.Parent = panel1;
bar.Size = new Size(120, 120);
bar.Location = new Point(20, 20);
// 3. 外观
bar.ColorBack = Color.FromArgb(60, 60, 64); // 未点亮块的颜色
bar.ColorCore = Color.FromArgb(0, 200, 255); // 点亮块的颜色
bar.BorderColor = Color.FromArgb(30, 30, 30); // 外圆环
bar.BorderSize = 2;
bar.ForeColor = Color.White;
bar.Font = new Font("Microsoft YaHei", 10F);
// 4. 环形分块参数
bar.Max = 8; // ⭐ 用 Max 控制扇形数量(默认构造已是 8)
bar.PieSpace = 14; // 块与块之间隔 14 度
bar.PieWidth = 34; // 环形厚度
bar.Interval = 100; // 100ms 前进一步(最小 50)
// 5. 模式
bar.ModeTow = ModeTwo.One; // 单个闪烁
// 6. 【关键】手动启动动画
// ⚠ 本控件 OnHandleCreated 里的自动启动条件实际不会成立(见“注意事项”),
// 必须显式调用 Start() 才会开始转动
bar.Start();三、构造函数与生命周期
public ProgressBarE()
| 项目 | 说明 |
|---|---|
| 参数 | 无 |
| 实际行为 | ① 调用 ProgressBarBase 无参构造;② 设置 Min = 0、Max = 8;③ 创建内部 Timer(Interval = 100),并挂接 Tick 处理 |
| Timer 启动 | 构造时不会启动;OnHandleCreated 的启动条件实际不成立(见下);必须显式 Start() |
| 默认尺寸 | 以 ProgressBarBase 为准;强烈建议手动设为接近正方形 |
生命周期方法(protected override,仅内部调用)
| 方法 | 触发时机 | 行为 |
|---|---|---|
OnHandleCreated(EventArgs e) | 控件句柄创建后 | 若 this.Enabled() && !DesignMode 则 _timer.Start()。⚠ 实际不会启动: this.Enabled() 检查的是“timer 当前是否 Enabled”,而构造时 timer 是 Enabled = false,所以在 OnHandleCreated 时刻条件恒为 false |
OnHandleDestroyed(EventArgs e) | 控件句柄销毁后 | 停止 _timer |
Dispose(bool disposing) | 控件释放时 | 若 disposing,_timer?.Dispose() |
结论:
- 控件不会自动开始动画(与ProgressBarD不同),需要显式调用bar.Start();
- 控件销毁时自动停止Timer,无需手动Stop();
- 隐藏控件(Visible = false)不会停止Timer——本类未重写OnVisibleChanged,请自行Stop()。
四、公开属性
4.1 本类新增的属性
public CConPlus.Enum.ModeTwo ModeTow
| 项目 | 说明 |
|---|---|
| 类型 | CConPlus.Enum.ModeTwo(值:One / Two) |
| 默认 | ModeTwo.One |
| Category | CCon |
| DefaultValue | ModeTwo.One |
| 作用 | 绘制模式:One = 只点亮 Value - 1 索引对应的那一块(单个闪烁);Two = 点亮第 1 ~ Value 的所有块(累积点亮) |
| 副作用 | 赋值后 Invalidate() |
| 备注 | 属性名拼写为 ModeTow(少了一个 o),与枚举名 ModeTwo 不一致,是源码拼写问题;请使用 ModeTow 访问 |
bar.ModeTow = ModeTwo.One; // 单块闪烁 bar.ModeTow = ModeTwo.Two; // 累积点亮
public int PieWidth
| 项目 | 说明 |
|---|---|
| 类型 | int(像素) |
| 默认 | 34 |
| Category | CCon |
| DefaultValue | 34 |
| 作用 | 环形厚度(每块扇形的“径向宽度”) |
| 取值范围 | Math.Max(1, value)——自动 clamp 到 ≥1 |
| 额外保护 | 绘制时 penWidth = min(PieWidth, d/2 - 1),保证环厚不超过半径,避免内圆消失 |
| 副作用 | 赋值后 Invalidate() |
bar.PieWidth = 20; // 细环 bar.PieWidth = 40; // 粗环
public int PieSpace
| 项目 | 说明 |
|---|---|
| 类型 | int(角度,单位:度) |
| 默认 | 14 |
| Category | CCon |
| DefaultValue | 14 |
| 作用 | 相邻两块扇形之间的空隙角度 |
| 取值范围 | Math.Max(0, value)——允许 0(无缝) |
| 副作用 | 赋值后 Invalidate() |
| 额外逻辑 | 每块扇形实际角度 pieAngle = (360 - PieSpace × Max) / Max。若 PieSpace × Max ≥ 360,则 pieAngle ≤ 0,代码里强制为 1,此时扇形会严重重叠或叠在起点处,视觉异常 |
bar.PieSpace = 0; // 无缝圆环 bar.PieSpace = 14; // 默认 bar.PieSpace = 20; // 更宽的块间空隙 // ⚠ 请保证 PieSpace × Max < 360 // 如 Max = 8 时,PieSpace < 45
public int Interval
| 项目 | 说明 |
|---|---|
| 类型 | int(毫秒) |
| 默认 | 100 |
| Category | CCon |
| DefaultValue | 100 |
| 作用 | Timer 每 Interval 毫秒 Tick 一次,Value 前进一格 |
| 取值范围 | Math.Max(50, value)——至少 50ms(旧版是 10ms,新版提高到 50ms) |
| 副作用 | 若 _timer != null,立即更新 Timer.Interval;不触发重绘(速度变化不影响当前帧) |
bar.Interval = 60; // 更快(下限 50) bar.Interval = 200; // 更慢 bar.Interval = 20; // 会被 clamp 到 50
4.2 继承自 ProgressBarBase 的成员(由本类绘制逻辑推断)
| 成员 | 类型(推断) | 在本类中的作用 | 用法示例 |
|---|---|---|---|
Min | int | 进度下限;构造时设为 0,绘制时不参与计算(只用于基类语义) | bar.Min = 0; |
Max | int | 既是进度上限,也是“扇形数量”。构造时设为 8。 本类不再提供 PieNum 属性,改块数就改 Max | bar.Max = 12;(改为 12 块) |
Value | int | 当前激活块索引(0 ~ Max);决定点亮哪一块 | bar.Value = 3; |
ColorBack | Color | 未点亮块的环形底色 | bar.ColorBack = Color.Gray; |
ColorCore | Color | 点亮块的颜色 | bar.ColorCore = Color.Lime; |
BorderColor | Color | 外圆环边框颜色 | bar.BorderColor = Color.Black; |
BorderSize | int | 外圆环边框粗细(像素) | bar.BorderSize = 2; |
事件(如
Change/ValueChanged)请参考ProgressBarBase源码。
注意:本类_timer_Tick里直接调用this.Value++/this.Value = 0,因此如果基类Valuesetter 会触发事件,则每Interval毫秒都会触发一次——订阅时注意性能。
4.3 绘制逻辑所使用到的 Control 继承成员
| 成员 | 来源 | 说明 |
|---|---|---|
Text | Control | 圆中央文字 |
Font | Control | 文字字体 |
ForeColor | Control | 文字颜色 |
ClientSize | Control | 用于计算圆的直径与居中 |
Invalidate() | Control | 属性变化后触发重绘 |
DesignMode | Control | 设计器模式下不启动 Timer |
五、公开方法
public void Start()
| 项目 | 说明 |
|---|---|
| 参数 | 无 |
| 返回 | void |
| 作用 | 启动动画:设置 _timer.Interval = _interval 后 _timer.Start() |
| 幂等 | 若 _timer.Enabled == true,直接返回(不会重复设置 Interval) |
| 典型场景 | 必须显式调用(因为构造和 OnHandleCreated 都不会自动启动) |
bar.Start(); // 开始转动
public void Stop()
| 项目 | 说明 |
|---|---|
| 参数 | 无 |
| 返回 | void |
| 作用 | 停止动画:_timer.Stop() |
| 备注 | 不会重置 Value;再次 Start() 会从当前位置继续 |
bar.Stop(); // 停止转动(Value 保留)
public new bool Enabled()
| 项目 | 说明 |
|---|---|
| 参数 | 无 |
| 返回 | bool:true 表示动画正在运行(Timer 已启动);false 表示已停止 |
| 作用 | 查询动画是否运行中 |
| ⚠ 重要 | 这是 new 隐藏了 Control.Enabled 属性的方法——两者名字相同,但语义完全不同:bar.Enabled(属性)→ 控件是否可用(可用于禁用整个控件);bar.Enabled()(方法)→ 动画是否正在运行。这种写法极易混淆,编写代码时务必留意有没有括号 |
if (bar.Enabled()) // 调用方法 → 动画是否运行中
Console.WriteLine("动画运行中");
if (bar.Enabled) // 访问属性 → 控件是否启用
bar.Start();六、公开成员速查表
| 成员 | 来源 | 类型 | 说明 |
|---|---|---|---|
ProgressBarE() | 本类 | 构造函数 | 初始化 Min=0、Max=8、创建 Timer(100ms) |
ModeTow | 本类 | 属性(ModeTwo) | 单块闪烁 / 累积点亮(默认 One) |
PieWidth | 本类 | 属性(int) | 环形厚度(默认 34,≥1) |
PieSpace | 本类 | 属性(int) | 块间角度(默认 14,≥0) |
Interval | 本类 | 属性(int) | 帧间隔毫秒(默认 100,≥50) |
Min | ProgressBarBase | 属性(int) | 范围下限(构造时设 0) |
Max | ProgressBarBase | 属性(int) | 范围上限 + 扇形数量(构造时设 8) |
Value | ProgressBarBase | 属性(int) | 当前激活块索引(0 ~ Max) |
ColorBack / ColorCore / BorderColor / BorderSize | ProgressBarBase | 属性 | 环形外观 |
Text / Font / ForeColor | Control | 属性 | 中央文字 |
Size / ClientSize | Control | 属性(Size) | 建议设为正方形 |
Start() | 本类 | 方法 | 启动动画 |
Stop() | 本类 | 方法 | 停止动画 |
Enabled() | 本类(new) | 方法 | 查询动画是否运行中 |
Invalidate() | Control | 方法 | 触发重绘 |
OnPaint(e) | 本类(override) | 方法(protected) | 环形绘制,外部不可直接调用 |
OnHandleCreated / OnHandleDestroyed / Dispose | 本类(override) | 方法(protected) | 生命周期管理 Timer |
事件(如
Change/ValueChanged)请参考ProgressBarBase源码。
七、绘制原理(深入理解)
以下分析帮你在调整块数、间隔、环厚、尺寸时理解
ProgressBarE的具体表现。
7.1 外圆与环参数
float side = Math.Min(ClientSize.Width, ClientSize.Height); if (side <= 4) 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); float penWidth = Math.Min(_pieWidth, d / 2f - 1f); // 环厚上限 = 半径 - 1 if (penWidth <= 0) return; var arcRect = RectangleF.Inflate(outer, -penWidth / 2f, -penWidth / 2f);
| 要点 | 说明 |
|---|---|
d = side - 1f | GDI+ 抗锯齿下直径等于 side 时最右/下边缘会被裁半像素,减 1 保证完整 |
penWidth 上限 d/2 - 1 | Pen 是居中描边,半径最多为 d/2;若 penWidth = d/2,内圆半径为 0(整块实心),不美观。限制到 d/2 - 1 保留内圆 |
arcRect = outer 内缩 penWidth/2 | 因 Pen 居中描边,其外边缘会超出路径 penWidth/2。内缩 penWidth/2 后,外边缘正好贴到 outer 上,不会超出控件 |
7.2 扇形数量与角度
int pieNum = this.Max; if (pieNum <= 0) return; float pieAngle = (360f - _pieSpace * pieNum) / pieNum; if (pieAngle <= 0) pieAngle = 1f;
| 要点 | 说明 |
|---|---|
pieNum = Max | 新版的块数直接取自 Max;不再有独立的 PieNum 属性。改块数 = 改 Max |
Max ≤ 0 保护 | 若用户误将 Max 设为 0 或负数,直接返回(不绘制),避免除零 |
360 - PieSpace × Max | 整个环共 360°。总空隙 = 每块空隙 × 块数;剩余角度平分给每块扇形 |
| 示例(默认) | Max=8,PieSpace=14 ⇒ pieAngle = (360 - 14×8) / 8 = (360-112)/8 = 31°。每块 31°,中间隔 14°,8 块共 (31+14)×8 = 360°,完美铺满 |
pieAngle ≤ 0 强制 1 | 若 PieSpace × Max ≥ 360(空隙太多或块数太多),理论 pieAngle 会 ≤0。代码兜底为 1°,但此时所有扇形会重叠在同一个起点附近,视觉混乱。请保证 PieSpace × Max < 360 |
7.3 绘制背景块
using (var p = new Pen(this.ColorBack, penWidth) { StartCap = LineCap.Flat, EndCap = LineCap.Flat })
{
for (int i = 0; i < pieNum; i++)
{
float startAngle = -90f + _pieSpace / 2f + i * (pieAngle + _pieSpace);
g.DrawArc(p, arcRect, startAngle, pieAngle);
}
}| 要点 | 说明 |
|---|---|
StartCap / EndCap = Flat | 平头端点,让扇形两端是直线切口,不会画成圆头/方头。视觉上每块是一个“梯形环块” |
-90f | 12 点方向为起点(GDI+ 0° 在 3 点,顺时针正) |
+ _pieSpace / 2f | 第一块与 12 点之间留半个空隙,使整圈视觉对称 |
i * (pieAngle + _pieSpace) | 第 i 块的起始角 = 起点 + i×(块角度 + 空隙角度) |
循环 i < pieNum | 先画全部 Max 个未激活块(用 ColorBack) |
7.4 绘制激活块
if (_modeTow == ModeTwo.One)
{
int index = this.Value - 1;
if (index >= 0 && index < pieNum)
{
float startAngle = -90f + _pieSpace / 2f + index * (pieAngle + _pieSpace);
g.DrawArc(p, arcRect, startAngle, pieAngle);
}
}
else if (_modeTow == ModeTwo.Two)
{
int count = Math.Min(this.Value, pieNum);
for (int i = 0; i < count; i++)
{
float startAngle = -90f + _pieSpace / 2f + i * (pieAngle + _pieSpace);
g.DrawArc(p, arcRect, startAngle, pieAngle);
}
}| 模式 | 行为 | 说明 |
|---|---|---|
ModeTwo.One | 只画 Value-1 索引的那一块 | 当 Value = 0 时,index = -1,不点亮任何块;当 Value = Max 时,index = Max-1,点亮最后一块;因此一轮循环 0 → Max → 0 里,有一帧是“全灭” |
ModeTwo.Two | 画索引 0 ~ Value-1 的所有块 | 当 Value = 0 时,count = 0,全灭;当 Value = Max 时,count = Max,满环;到顶后回到 0,视觉上会从“满环”跳变到“全灭” |
| 覆盖顺序 | 先画 ColorBack 全块 → 再画 ColorCore 激活块 | 激活块覆盖在背景块上,颜色完全替换(无渐变/混合) |
7.5 绘制文字与边框
if (!string.IsNullOrEmpty(this.Text))
{
using (var b = new SolidBrush(this.ForeColor))
using (var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center })
g.DrawString(this.Text, this.Font, b, outer, sf);
}
int border = Math.Min(this.BorderSize, (int)(d / 2) - 1);
if (border > 0)
{
float half = border / 2f;
var penRect = RectangleF.Inflate(outer, -half, -half);
using (var p = new Pen(this.BorderColor, border))
g.DrawEllipse(p, penRect);
}| 要点 | 说明 |
|---|---|
文字绘制在 outer 内 | 文字在圆的正中心居中 |
border = min(BorderSize, d/2 - 1) | 边框半径不超过圆半径 |
penRect = outer 内缩 half | 同 arcRect 逻辑:因 Pen 居中描边,内缩一半线宽让边框外边缘贴合 outer |
| 边框最后画 | 覆盖在环形之上,视觉上把环“包”在圆内 |
7.6 Value 循环逻辑(与旧版的关键差异)
private void _timer_Tick(object sender, EventArgs e)
{
if (this.Value + 1 > this.Max)
this.Value = 0; // ⚠ 新版:到顶回到 0(旧版是回到 1)
else
this.Value++; // 否则 +1
}| 要点 | 说明 |
|---|---|
| 循环范围 | Value 取值 0 ~ Max,共 Max + 1 个状态 |
| 到顶回到 0 | Value = Max 时下一 Tick 直接回 0,循环 0, 1, 2, …, Max, 0, 1, … |
| 初始 Value | 构造时未显式设置 Value(基类默认值,通常为 0);第一次 Tick 会把 Value 从 0 推到 1(走 else 分支) |
| Value = 0 的视觉 | 两种模式都不点亮任何块(One 模式 index=-1 越界;Two 模式 count=0)因此每轮循环里有一帧“全灭”,视觉上是“点亮绕圈一圈后短暂熄灭再重来” |
| 触发事件 | 每次 Value = 赋值都会走基类 setter;若基类触发事件(如 Change),则每 Interval 毫秒触发一次,订阅时注意性能 |
八、完整使用示例
示例 1:基础雷达加载
using System.Drawing;
using System.Windows.Forms;
using CConUI;
using CConPlus.Enum;
var bar = new ProgressBarE
{
Parent = panel1,
Size = new Size(120, 120),
Location = new Point(20, 20),
Max = 8, // 8 块
PieSpace = 14,
PieWidth = 34,
Interval = 100,
ModeTow = ModeTwo.One,
ColorBack = Color.FromArgb(60, 60, 64),
ColorCore = Color.FromArgb(0, 200, 255),
BorderColor = Color.FromArgb(30, 30, 30),
BorderSize = 2,
ForeColor = Color.White,
Font = new Font("Microsoft YaHei", 10F)
};
bar.Start(); // ⚠ 必须显式启动示例 2:累积点亮(进度型)
var bar = new ProgressBarE
{
Parent = this,
Size = new Size(140, 140),
Max = 20, // 20 块
PieSpace = 4,
PieWidth = 20,
Interval = 80,
ModeTow = ModeTwo.Two, // 累积点亮
ColorBack = Color.FromArgb(240, 240, 240),
ColorCore = Color.DodgerBlue,
BorderColor = Color.LightGray,
BorderSize = 2,
Text = "加载中"
};
bar.Start();示例 3:可暂停 / 恢复的加载指示
var bar = new ProgressBarE { Parent = this, Size = new Size(100, 100), Max = 12, PieSpace = 10 };
bar.Start();
btnPause.Click += (s, e) =>
{
if (bar.Enabled()) // 调用方法:动画是否运行
{
bar.Stop();
btnPause.Text = "继续";
}
else
{
bar.Start();
btnPause.Text = "暂停";
}
};示例 4:根据业务进度提前停止动画
var bar = new ProgressBarE
{
Parent = this,
Size = new Size(120, 120),
Max = 8,
PieSpace = 14,
ModeTow = ModeTwo.Two
};
bar.Start();
// 模拟业务完成后停止
var stopTimer = new Timer { Interval = 5000 };
stopTimer.Tick += (s, e) =>
{
stopTimer.Stop();
bar.Stop();
// 设置为满进度(可选)
bar.Value = bar.Max;
// 显示完成文字
bar.Text = "完成";
bar.Invalidate();
};
stopTimer.Start();示例 5:无缝圆环(PieSpace = 0)
// PieSpace=0 时,块与块之间无空隙,视觉上是一条完整的环
// 适合想做“跑马灯”效果但不需要可见间隔的场景
var bar = new ProgressBarE
{
Parent = this,
Size = new Size(100, 100),
Max = 60, // 60 块,每块 6°
PieSpace = 0,
PieWidth = 12,
Interval = 50,
ModeTow = ModeTwo.One, // 单块扫描
ColorBack = Color.FromArgb(50, 50, 50),
ColorCore = Color.Lime
};
bar.Start();示例 6:中英文百分比显示(外部计算 Value)
var bar = new ProgressBarE
{
Parent = this,
Size = new Size(120, 120),
Max = 100, // 100 块,Value 恰好等价于百分比
PieSpace = 1, // 每块 1°,间隔 1°,共 200°?注意!
// ⚠ PieSpace * Max = 1 * 100 = 100 < 360,OK
PieWidth = 8,
Interval = 100,
ModeTow = ModeTwo.Two,
ColorCore = Color.Orange
};
// 外部监听 Value 更新文字(假设基类触发 Change 事件)
bar.Change += (s, e) =>
{
bar.Text = bar.Value + "%";
};
bar.Start();转载请注明出处。