Files
sunvpy-docs/docs/content/38-jin-du-tiao-shi-yong-zhi-nan.md
2026-04-10 13:47:53 +08:00

723 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
进度条是向用户反馈长时间操作进度的关键交互组件,它能让用户清晰了解任务的执行状态,提升用户体验。本指南将系统讲解如何在 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 脚本应用。