This commit is contained in:
2026-04-10 13:47:53 +08:00
commit 8c78c0f920
61 changed files with 30343 additions and 0 deletions

View File

@@ -0,0 +1,723 @@
进度条是向用户反馈长时间操作进度的关键交互组件,它能让用户清晰了解任务的执行状态,提升用户体验。本指南将系统讲解如何在 SunvStation 脚本中高效使用进度条功能,涵盖基础用法、高级技巧和最佳实践。
在学习本页内容后,建议按照以下路径继续深入:
- [配置日志记录器](37-pei-zhi-ri-zhi-ji-lu-qi) — 了解日志记录的配置方法,与进度条配合使用
- [错误处理与异常管理](39-cuo-wu-chu-li-yu-yi-chang-guan-li) — 掌握进度条操作中的错误处理
- [批量保存到数据库](24-pi-liang-bao-cun-dao-shu-ju-ku) — 查看进度条在实际批量操作中的应用
## 进度条系统架构概览
SunvStation 的进度条功能采用**分层架构设计**,通过 `ProgressMixin` 混入类与 SSProcessManager 集成,结合底层 C++ 封装的 `Progress` 类和进度条控制函数,形成完整的三层架构体系。
```mermaid
classDiagram
class ProgressAware {
<<抽象基类>>
+disable_progress()*
+startProgress(title, total)*
+stepProgress(message)*
+closeProgress()*
}
class ProgressMixin {
+enable_progress: bool
+progress: Progress
+disable_progress()
+startProgress(title, total)
+stepProgress(message)
+closeProgress()
}
class SSProcessManager {
+enable_progress: bool
+progress: Progress
+startProgress(title, total)
+stepProgress(message)
+closeProgress()
}
class Progress {
<<C++封装类>>
+setSize(nSize)
+step() boolean
+getPos() int
+getStep() int
}
class GlobalFunctions {
<<函数模块>>
+startProgress(title, barCount)
+closeProgress()
+setProgressRange(min, max)
+stepProgress(pos, title)
}
ProgressAware <|-- ProgressMixin
ProgressMixin <|-- SSProcessManager
ProgressMixin ..> Progress : 使用
ProgressMixin ..> GlobalFunctions : 调用
```
这种架构设计的核心优势在于**关注点分离**`ProgressMixin` 负责进度条的业务逻辑封装,底层函数处理进度条的创建和更新,而 `Progress` 类则提供了防抖动的智能步进机制,避免频繁更新导致的性能问题。
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L15-L80), [PySSWidget.py](PySSWidget.py#L934-L977)
### 核心组件说明
| 组件 | 类型 | 职责描述 |
|------|------|----------|
| `ProgressMixin` | Mixin 类 | 提供进度条的封装方法,管理 `enable_progress` 开关 |
| `Progress` | C++ 封装类 | 智能步进控制器,将循环总数映射到 0-100 的进度范围 |
| `enable_progress` | bool 属性 | 控制进度条是否显示的全局开关 |
| `progress` | Progress 实例 | 当前进度条控制器实例 |
| `startProgress()` | 全局函数 | 创建并显示进度条对话框 |
| `stepProgress()` | 全局函数 | 更新进度条的当前位置和提示信息 |
| `closeProgress()` | 全局函数 | 关闭进度条对话框 |
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L22-L80), [PySSWidget.py](PySSWidget.py#L500-L560)
## 进度条使用基本流程
进度条操作遵循"启动 → 更新 → 关闭"的标准生命周期管理流程。理解这个流程对于正确使用进度条至关重要,下图展示了完整的操作步骤:
```mermaid
flowchart TD
A[开始使用进度条] --> B[调用 startProgress<br/>启动进度条]
B --> C{enable_progress<br/>是否启用?}
C -->|是| D[创建 Progress 实例<br/>设置进度范围 0-100]
C -->|否| E[跳过进度显示]
D --> F[执行循环任务]
F --> G{progress.step&#40&#41<br/>是否需要更新?}
G -->|是| H[调用 stepProgress<br/>更新进度条显示]
G -->|否| F
H --> F
F --> I{任务是否完成?}
I -->|否| F
I -->|是| J[调用 closeProgress<br/>关闭进度条]
J --> K[进度条使用完成]
E --> K
style D fill:#e1f5ff
style H fill:#fff4e1
style J fill:#ffe1e1
```
理解这个流程后,我们将逐步详细解析每个环节的具体实现方法。
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L32-L80)
## 步骤一:启动进度条
使用 `startProgress()` 方法启动进度条,需要指定标题和总任务数。标题将显示在进度条对话框顶部,总任务数用于计算进度的步进间隔。
### 基本用法
```python
from sunvpy import SSProcess
# 启动进度条,显示标题和总任务数
SSProcess.startProgress("批量导出数据", 1000)
```
### 方法参数说明
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `title` | str | 是 | 进度条对话框标题,描述当前正在执行的任务 |
| `total` | int | 是 | 总任务数,用于计算进度步进间隔 |
### 实现原理
`startProgress()` 内部会执行以下操作:
1. 检查 `enable_progress` 开关是否启用
2. 创建 `Progress(total)` 实例,自动计算步进间隔
3. 调用全局 `startProgress(title, 1)` 创建进度条对话框
4. 调用 `setProgressRange(0, 100)` 设置进度范围固定为 0-100
```mermaid
sequenceDiagram
participant Script as 脚本代码
participant Mixin as ProgressMixin
participant Progress as Progress类
participant UI as 进度条UI
Script->>Mixin: startProgress("导出数据", 1000)
activate Mixin
Mixin->>Mixin: 检查 enable_progress
alt enable_progress == True
Mixin->>Progress: new Progress(1000)
Progress-->>Mixin: 实例创建成功
Mixin->>UI: startProgress("导出数据", 1)
UI-->>Mixin: 对话框已显示
Mixin->>UI: setProgressRange(0, 100)
Mixin->>Mixin: 保存实例到 self.progress
end
Mixin-->>Script: 方法返回
deactivate Mixin
```
这种设计的优势在于:**将循环总数自动映射到 0-100 的标准进度范围**,避免了手动计算百分比的繁琐,同时通过 `Progress` 类的智能步进机制,减少不必要的 UI 更新次数。
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L32-L46), [PySSWidget.py](PySSWidget.py#L495-L500)
## 步骤二:更新进度条
在循环或长时间任务中,使用 `stepProgress()` 方法更新进度条显示。这个方法会自动判断是否真正需要更新 UI避免频繁刷新导致的性能问题。
### 基本用法
```python
# 遍历选择集并更新进度
total_count = SSProcess.getSelGeoCount()
SSProcess.startProgress("处理选择集对象", total_count)
for i in range(total_count):
# 执行业务逻辑
SSProcess.setSelGeoValue(i, "SSObj_Name", f"对象_{i}")
# 更新进度条,显示当前处理的索引
SSProcess.stepProgress(f"正在处理第 {i+1}/{total_count} 个对象")
# 关闭进度条
SSProcess.closeProgress()
```
### 方法参数说明
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `message` | str | 是 | 当前步骤的描述信息,将显示在进度条下方 |
### 智能步进机制
`Progress` 类的核心功能是**智能步进控制**。它会根据总任务数自动计算步进间隔,确保进度条更新不会过于频繁:
```python
# Progress 内部的步进逻辑(简化示例)
class Progress:
def __init__(self, total):
self.total = total
self.current = 0
# 自动计算步进间隔确保在0-100范围内更新不超过100次
self.step_size = max(1, total // 100)
def step(self):
self.current += 1
# 只有当累计步进达到阈值时才返回True触发UI更新
if self.current >= self.step_size:
self.current = 0
return True
return False
```
这种机制的效果是无论循环执行多少次100次还是100000次进度条最多只更新100次大大提高了性能。
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L48-L60), [PySSWidget.py](PySSWidget.py#L946-L972)
### 更新频率对比
以下对比展示了智能步进机制与传统每次循环都更新的区别:
| 场景 | 总任务数 | 传统更新次数 | 智能步进更新次数 | 性能提升 |
|------|----------|-------------|----------------|----------|
| 小型任务 | 50 | 50 | 50 | 无提升 |
| 中型任务 | 1000 | 1000 | 100 | 10倍提升 |
| 大型任务 | 100000 | 100000 | 100 | 1000倍提升 |
Sources: [PySSWidget.py](PySSWidget.py#L950-L972)
## 步骤三:关闭进度条
任务完成后,必须调用 `closeProgress()` 方法关闭进度条对话框。这个方法会清理进度条资源,并确保 UI 正确更新。
### 基本用法
```python
# 任务完成后关闭进度条
SSProcess.closeProgress()
```
### 方法行为
`closeProgress()` 会执行以下操作:
1. 调用全局 `closeProgress()` 函数关闭对话框
2.`self.progress` 设置为 `None`,释放资源
3. 通过异常处理确保即使关闭失败也不会影响程序运行
```python
def closeProgress(self):
"""关闭进度条。"""
if self.progress is not None:
try:
closeProgress()
except Exception:
pass # 忽略关闭异常
self.progress = None
```
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L62-L80), [PySSWidget.py](PySSWidget.py#L505-L510)
### 关闭时机的重要性
确保在以下所有情况下都调用 `closeProgress()`
| 场景 | 说明 | 处理方式 |
|------|------|----------|
| 正常完成 | 循环自然结束 | 在循环结束后调用 |
| 异常中断 | 遇到错误提前退出 | 使用 try-finally 确保调用 |
| 用户取消 | 响应用户中断请求 | 在取消处理中调用 |
| 条件跳过 | 不满足某些条件时退出 | 在所有退出分支中调用 |
### 最佳实践:使用 finally 确保关闭
```python
SSProcess.startProgress("处理数据", total_count)
try:
for i in range(total_count):
# 执行业务逻辑
process_item(i)
# 更新进度
SSProcess.stepProgress(f"处理 {i+1}/{total_count}")
# 检查是否需要中断
if should_cancel():
raise KeyboardInterrupt("用户取消操作")
except Exception as e:
SSProcess.logger.error(f"处理失败: {e}")
raise
finally:
# 确保进度条一定会被关闭
SSProcess.closeProgress()
```
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L62-L80)
## 完整示例:批量导出数据
下面是一个完整的示例,展示在批量导出选择集对象时如何使用进度条。
### 代码实现
```python
from sunvpy import SSProcess
def export_selection_to_csv(file_path):
"""将选择集中的对象导出到CSV文件"""
# 获取选择集数量
geo_count = SSProcess.getSelGeoCount()
if geo_count == 0:
print("选择集为空,无需导出")
return
# 启动进度条
SSProcess.startProgress("导出选择集到CSV", geo_count)
try:
# 准备CSV文件
with open(file_path, 'w', encoding='utf-8') as f:
# 写入CSV表头
f.write("ID,代码,名称,类型,颜色\n")
# 遍历选择集并导出
for i in range(geo_count):
# 获取对象属性
obj_id = SSProcess.getSelGeoValue(i, "SSObj_ID")
obj_code = SSProcess.getSelGeoValue(i, "SSObj_Code")
obj_name = SSProcess.getSelGeoValue(i, "SSObj_Name")
obj_type = SSProcess.getSelGeoValue(i, "SSObj_Type")
obj_color = SSProcess.getSelGeoValue(i, "SSObj_Color")
# 写入CSV行
f.write(f"{obj_id},{obj_code},{obj_name},{obj_type},{obj_color}\n")
# 每处理10个对象更新一次进度
SSProcess.stepProgress(f"正在导出第 {i+1}/{geo_count} 个对象")
print(f"成功导出 {geo_count} 个对象到 {file_path}")
except Exception as e:
print(f"导出失败: {e}")
SSProcess.logger.error(f"导出失败: {e}")
finally:
# 确保关闭进度条
SSProcess.closeProgress()
# 调用示例
export_selection_to_csv("export.csv")
```
### 代码解析
| 代码段 | 说明 |
|--------|------|
| `geo_count = SSProcess.getSelGeoCount()` | 先获取总数量,用于进度条初始化 |
| `SSProcess.startProgress(...)` | 在 try 块之前启动进度条 |
| `SSProcess.stepProgress(...)` | 在循环内部更新进度,显示当前处理位置 |
| `finally: SSProcess.closeProgress()` | 使用 finally 确保进度条一定被关闭 |
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L32-L80)
## 禁用进度条
在某些场景下(如自动化脚本、后台任务),可能需要临时或永久禁用进度条显示。使用 `disable_progress()` 方法可以实现这一需求。
### 基本用法
```python
# 禁用进度条
SSProcess.disable_progress()
# 后续的所有进度条操作都不会显示
SSProcess.startProgress("处理数据", 100) # 不会显示进度条
SSProcess.stepProgress("处理中...") # 不会更新进度条
SSProcess.closeProgress() # 不会执行任何操作
```
### 方法行为
`disable_progress()` 会执行以下操作:
1.`enable_progress` 设置为 `False`
2. 如果当前有正在显示的进度条,立即关闭它
3. 后续所有进度条方法调用都会被跳过
```python
def disable_progress(self):
"""关闭进度条显示并立即关闭正在显示的进度条。"""
self.enable_progress = False
if self.progress is not None:
try:
closeProgress()
except Exception:
pass
self.progress = None
```
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L24-L30)
### 使用场景对比
| 场景 | 是否禁用进度条 | 原因 |
|------|----------------|------|
| 交互式脚本 | 否 | 用户需要看到进度反馈 |
| 批处理自动化任务 | 是 | 无用户交互,节省资源 |
| 后台定时任务 | 是 | 避免弹出窗口干扰 |
| 单元测试 | 是 | 测试环境不需要UI反馈 |
| 调试阶段 | 否 | 需要观察程序执行状态 |
### 条件禁用示例
```python
import sys
def process_data():
"""根据运行环境决定是否显示进度条"""
# 如果是从命令行运行且带有 --quiet 参数,则禁用进度条
if '--quiet' in sys.argv:
SSProcess.disable_progress()
print("静默模式:进度条已禁用")
# 正常使用进度条(会根据 enable_progress 自动判断)
SSProcess.startProgress("处理数据", 1000)
for i in range(1000):
process_item(i)
SSProcess.stepProgress(f"处理 {i+1}")
SSProcess.closeProgress()
# 调用示例
# python script.py --quiet # 禁用进度条
# python script.py # 显示进度条
```
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L24-L30)
## 进度条与日志配合使用
将进度条与日志系统结合使用,可以在提供视觉反馈的同时保留详细的执行记录,特别适合复杂的长时间任务。
### 配合模式
```mermaid
flowchart LR
A[任务开始] --> B[记录日志 INFO]
B --> C[启动进度条]
C --> D{循环执行}
D --> E[执行子任务]
E --> F{子任务成功?}
F -->|是| G[记录日志 DEBUG<br/>详细信息]
F -->|否| H[记录日志 ERROR<br/>错误信息]
G --> I[更新进度条]
H --> I
I --> J{任务完成?}
J -->|否| D
J -->|是| K[关闭进度条]
K --> L[记录日志 INFO<br/>任务完成]
style C fill:#e1f5ff
style I fill:#fff4e1
style K fill:#ffe1e1
```
### 完整示例
```python
import logging
from sunvpy import SSProcess
# 配置日志记录器
logger = logging.getLogger('batch_process')
logger.setLevel(logging.INFO)
file_handler = logging.FileHandler('batch_process.log', encoding='utf-8')
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# 将日志注入到 SSProcess
SSProcess.set_logger(logger)
def batch_process_objects():
"""批量处理对象,配合进度条和日志"""
logger.info("开始批量处理任务")
# 获取选择集数量
total_count = SSProcess.getSelGeoCount()
logger.info(f"选择集对象数量: {total_count}")
if total_count == 0:
logger.warning("选择集为空,跳过处理")
return
# 启动进度条
SSProcess.startProgress("批量处理对象", total_count)
success_count = 0
error_count = 0
try:
for i in range(total_count):
try:
# 执行业务逻辑
process_single_object(i)
# 记录详细日志
logger.debug(f"成功处理对象 {i+1}/{total_count}")
success_count += 1
except Exception as e:
# 记录错误日志
logger.error(f"处理对象 {i+1} 失败: {str(e)}")
error_count += 1
# 更新进度条
SSProcess.stepProgress(f"处理进度: {i+1}/{total_count}")
finally:
# 关闭进度条
SSProcess.closeProgress()
# 记录汇总日志
logger.info(f"处理完成: 成功 {success_count} 个, 失败 {error_count}")
def process_single_object(index):
"""处理单个对象的业务逻辑"""
# 这里实现具体的处理逻辑
obj_name = SSProcess.getSelGeoValue(index, "SSObj_Name")
SSProcess.setSelGeoValue(index, "SSObj_Name", f"{obj_name}_已处理")
# 调用示例
batch_process_objects()
# 输出示例:
# 2024-01-15 10:30:00 - INFO - 开始批量处理任务
# 2024-01-15 10:30:00 - INFO - 选择集对象数量: 100
# 2024-01-15 10:30:05 - INFO - 处理完成: 成功 98 个, 失败 2 个
# 2024-01-15 10:30:05 - ERROR - 处理对象 15 失败: 无法获取属性值
```
### 日志级别与进度条的配合策略
| 操作阶段 | 进度条操作 | 日志级别 | 说明 |
|----------|-----------|---------|------|
| 任务开始 | startProgress | INFO | 记录任务启动,包含总数量 |
| 每个循环 | stepProgress | DEBUG | 记录详细信息(可关闭) |
| 子任务成功 | - | DEBUG | 记录成功的详细步骤 |
| 子任务失败 | - | ERROR | 记录失败的原因和堆栈 |
| 任务结束 | closeProgress | INFO | 记录任务完成,包含统计 |
这种配合方式的优势在于:**进度条提供实时视觉反馈,日志提供可追溯的详细记录**,两者互为补充,满足不同场景的需求。
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L32-L80), [log_mixin.py](ssprocess_mixins/log_mixin.py#L26-L42)
## 常见问题与解决方案
### 问题一:进度条不显示
**现象**:调用 `startProgress()` 后进度条没有显示。
**可能原因及解决方案**
| 原因 | 检查方法 | 解决方案 |
|------|---------|----------|
| 已禁用进度条 | 检查 `SSProcess.enable_progress` | 调用 `SSProcess.enable_progress = True` 重新启用 |
| 总任务数为0 | 检查传入的 `total` 参数 | 确保传入有效的正整数 |
| 主线程阻塞 | 检查是否有耗时操作在主线程 | 将耗时操作放到子线程中执行 |
```python
# 检查进度条状态
print(f"进度条启用状态: {SSProcess.enable_progress}")
# 重新启用进度条
SSProcess.enable_progress = True
```
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L24-L30)
### 问题二:进度条卡住不动
**现象**:进度条显示后长时间没有更新。
**可能原因及解决方案**
```python
# 错误示例:步进过大导致进度条长时间不更新
SSProcess.startProgress("处理", 100000) # 任务数过大
for i in range(100000):
process_item(i)
# 每1000次才会更新一次进度看起来像卡住
SSProcess.stepProgress(f"处理 {i}")
# 正确示例:提供更有意义的进度提示
SSProcess.startProgress("处理", 100000)
for i in range(100000):
process_item(i)
# 进度条内部会智能控制更新频率,但提示信息会实时显示
SSProcess.stepProgress(f"处理 {i+1}/{100000}")
```
Sources: [PySSWidget.py](PySSWidget.py#L950-L972)
### 问题三:进度条关闭后程序无响应
**现象**:调用 `closeProgress()` 后程序似乎卡住。
**解决方案**
```python
# 原因分析进度条关闭操作在UI线程执行可能被其他操作阻塞
# 解决方案1确保在主线程中关闭
def close_progress_safely():
if SSProcess.progress is not None:
try:
closeProgress()
except Exception as e:
print(f"关闭进度条时出错: {e}")
finally:
SSProcess.progress = None
close_progress_safely()
# 解决方案2使用超时机制
import threading
import time
def close_with_timeout(timeout=5):
def _close():
try:
closeProgress()
except:
pass
thread = threading.Thread(target=_close)
thread.start()
thread.join(timeout=timeout)
if thread.is_alive():
print("警告:进度条关闭超时")
SSProcess.progress = None
close_with_timeout()
```
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L62-L80)
## 最佳实践总结
### 1. 进度条使用检查清单
在使用进度条前,请确认以下要点:
| 检查项 | 说明 |
|--------|------|
| ✓ 确认总任务数 | 使用准确的数字初始化进度条避免0或负数 |
| ✓ 在try前启动 | 确保进度条启动在try块之前 |
| ✓ 在finally中关闭 | 使用try-finally确保进度条一定会被关闭 |
| ✓ 提供有意义的提示 | `stepProgress` 的message应反映当前操作状态 |
| ✓ 考虑禁用场景 | 自动化任务中考虑使用 `disable_progress()` |
### 2. 性能优化建议
| 优化点 | 说明 | 示例 |
|--------|------|------|
| 减少UI更新 | 利用 `Progress.step()` 的自动步进机制 | 不需要手动计算更新频率 |
| 异步关闭 | 大任务完成后考虑异步关闭进度条 | 使用子线程关闭避免阻塞 |
| 条件显示 | 根据任务时长决定是否显示进度条 | 短任务(<1秒跳过进度条 |
### 3. 代码模板
```python
# 标准进度条使用模板
from sunvpy import SSProcess
def task_with_progress():
"""带进度条的标准任务模板"""
# 1. 获取总任务数
total = get_task_count()
if total == 0:
return
# 2. 启动进度条
SSProcess.startProgress("任务描述", total)
try:
# 3. 执行循环任务
for i in range(total):
# 执行业务逻辑
do_work(i)
# 4. 更新进度
SSProcess.stepProgress(f"处理 {i+1}/{total}")
except Exception as e:
# 5. 错误处理
SSProcess.logger.error(f"任务失败: {e}")
raise
finally:
# 6. 确保关闭
SSProcess.closeProgress()
```
Sources: [progress_mixin.py](ssprocess_mixins/progress_mixin.py#L15-L80)
## 下一步学习
掌握了进度条的基本使用后,您可以继续学习以下相关主题:
- [配置日志记录器](37-pei-zhi-ri-zhi-ji-lu-qi) — 了解如何将日志与进度条配合,构建完整的反馈机制
- [错误处理与异常管理](39-cuo-wu-chu-li-yu-yi-chang-guan-li) — 学习在进度条操作中正确处理异常
- [批量保存到数据库](24-pi-liang-bao-cun-dao-shu-ju-ku) — 查看进度条在实际批量操作中的应用示例
通过这些内容的学习,您将能够构建出用户体验良好、健壮性强的 SunvStation 脚本应用。