在 Qt + Creo 二次开发中实现流畅的进度条显示
本文分享在 Creo 二次开发中,如何结合 Qt 框架实现不卡UI的进度条展示。包含任务调度、嵌套进度等实战经验。
做过 Creo 二次开发的朋友都知道,所有对模型的操作都必须在主线程执行。这个限制加上 Qt 的 UI 也只能在主线程更新,很容易让程序在处理大型装配体时界面卡死,进度条也成了摆设。
这篇文章分享几种我在实际项目中用过的解决方案,从简单到复杂,总有一款适合你的场景。
为什么会卡?
先说说为什么会出现这个问题。Creo 的 API 设计决定了所有模型操作必须在创建 Session 的线程(通常是主线程)中执行。而 Qt 的事件循环也在主线程运行,负责处理用户交互和界面刷新。
当你在主线程执行一个耗时的 Creo 操作时,事件循环被阻塞了,导致:
- 界面无法响应鼠标键盘
- 进度条不更新
- 窗口拖动都会卡住
方案一:processEvents() - 简单粗暴
最简单的办法是在循环中手动让 Qt 处理一下事件:
void processModels(const QList<ProMdl>& models) {
int total = models.size();
ui->progressBar->setRange(0, total);
for (int i = 0; i < total; ++i) {
// 处理单个模型
ProMdl model = models[i];
ProMdlName name;
ProMdlMdlnameGet(model, name);
// 这里执行具体的 Creo 操作
// ...
// 更新进度并让 Qt 处理事件
ui->progressBar->setValue(i + 1);
ui->statusLabel->setText(QString("正在处理: %1").arg(name));
QCoreApplication::processEvents();
// 可以加个标志位支持取消
if (m_cancelled) break;
}
}
这种方法的好处是改动最小,坏处也很明显:
- 如果单个操作耗时太长,还是会卡
- processEvents() 可能引发重入问题
- 代码结构不够优雅
不过对于每步耗时较短的批量操作,这个方案还是很实用的。
方案二:定时器分步执行 - 优雅可控
更好的办法是把任务拆分成小步骤,利用 Qt 的定时器机制:
class ModelProcessor : public QObject {
Q_OBJECT
public:
void startProcessing(const QList<ProMdl>& models) {
m_models = models;
m_currentIndex = 0;
m_cancelled = false;
ui->progressBar->setRange(0, models.size());
ui->progressBar->setValue(0);
// 立即开始第一步
QTimer::singleShot(0, this, &ModelProcessor::processNext);
}
void cancel() { m_cancelled = true; }
private slots:
void processNext() {
if (m_cancelled || m_currentIndex >= m_models.size()) {
emit finished();
return;
}
// 处理当前模型
ProMdl model = m_models[m_currentIndex];
processOneModel(model);
// 更新进度
ui->progressBar->setValue(++m_currentIndex);
// 安排下一步
QTimer::singleShot(0, this, &ModelProcessor::processNext);
}
signals:
void finished();
private:
QList<ProMdl> m_models;
int m_currentIndex;
bool m_cancelled;
};
这种方式的优点:
- UI 完全不会卡死
- 支持取消操作
- 可以方便地添加暂停/继续功能
方案三:任务队列 - 应对复杂场景
当你需要执行多种不同类型的操作时,任务队列模式就很有用了:
// 任务基类
class CreoTask : public QObject {
Q_OBJECT
public:
virtual ~CreoTask() = default;
virtual QString name() const = 0;
virtual void execute() = 0; // 执行具体操作
signals:
void finished();
void progressChanged(int value, int total);
};
// 特征识别任务
class FeatureRecognitionTask : public CreoTask {
public:
FeatureRecognitionTask(ProMdl model) : m_model(model) {}
QString name() const override {
return "识别模型特征";
}
void execute() override {
ProFeature* features;
ProError err = ProSolidFeaturesGet(m_model, &features);
if (err != PRO_TK_NO_ERROR) {
emit finished();
return;
}
// 处理特征...
emit finished();
}
private:
ProMdl m_model;
};
// 任务管理器
class TaskManager : public QObject {
Q_OBJECT
public:
void addTask(CreoTask* task) {
m_tasks.append(task);
connect(task, &CreoTask::finished,
this, &TaskManager::onTaskFinished);
}
void start() {
m_currentTask = 0;
ui->mainProgress->setRange(0, m_tasks.size());
executeNext();
}
private slots:
void executeNext() {
if (m_currentTask >= m_tasks.size()) {
emit allFinished();
return;
}
CreoTask* task = m_tasks[m_currentTask];
ui->taskLabel->setText(task->name());
task->execute();
}
void onTaskFinished() {
ui->mainProgress->setValue(++m_currentTask);
// 清理已完成的任务
if (m_currentTask > 0) {
delete m_tasks[m_currentTask - 1];
}
QTimer::singleShot(10, this, &TaskManager::executeNext);
}
signals:
void allFinished();
private:
QList<CreoTask*> m_tasks;
int m_currentTask = 0;
};
嵌套进度条 - 展示更详细的进度
有时候单个任务内部也需要显示进度,这就需要嵌套的进度条:
class BatchParameterTask : public CreoTask {
Q_OBJECT
public:
BatchParameterTask(const QList<ProParameter>& params)
: m_params(params), m_current(0) {}
QString name() const override {
return "批量更新参数";
}
void execute() override {
m_subProgress->setRange(0, m_params.size());
m_subProgress->setValue(0);
m_subProgress->show();
processNextParam();
}
void setSubProgressBar(QProgressBar* bar) {
m_subProgress = bar;
}
private slots:
void processNextParam() {
if (m_current >= m_params.size()) {
m_subProgress->hide();
emit finished();
return;
}
// 更新单个参数
ProParameter& param = m_params[m_current];
ProParameterValueSet(¶m, &m_newValue);
m_subProgress->setValue(++m_current);
emit progressChanged(m_current, m_params.size());
// 继续下一个
QTimer::singleShot(0, this, &BatchParameterTask::processNextParam);
}
private:
QList<ProParameter> m_params;
ProParamvalue m_newValue;
QProgressBar* m_subProgress;
int m_current;
};
实战经验分享
1. 合理拆分粒度
任务拆分不要太细,否则定时器调度的开销会很大。我的经验是每个步骤控制在 50-200ms 左右比较合适。
2. 避免频繁的模型重生成
// 错误示范:每次参数修改都重生成
for (auto& param : params) {
ProParameterValueSet(¶m, &value);
ProSolidRegenerate(solid, PRO_REGEN_NO_FLAGS); // 太慢!
}
// 正确做法:批量修改后统一重生成
for (auto& param : params) {
ProParameterValueSet(¶m, &value);
}
ProSolidRegenerate(solid, PRO_REGEN_NO_FLAGS); // 只重生成一次
3. 处理 Creo 异常
Creo API 调用可能失败,要做好错误处理:
void processNext() {
try {
ProError err = someCreoOperation();
if (err != PRO_TK_NO_ERROR) {
QString errorMsg = getCreoErrorString(err);
QMessageBox::warning(nullptr, "操作失败", errorMsg);
emit finished();
return;
}
} catch (...) {
// Creo 有时会抛出 C++ 异常
QMessageBox::critical(nullptr, "严重错误",
"Creo 操作发生未知错误");
emit finished();
return;
}
// 继续下一步...
}
4. 内存管理注意事项
Creo 的很多 API 会分配内存,记得释放:
ProFeature* features;
ProArrayAlloc(0, sizeof(ProFeature), 1, (ProArray*)&features);
// 使用 features
ProArrayFree((ProArray*)&features);
// 或者用智能指针包装
struct ProArrayDeleter {
void operator()(ProArray* arr) {
if (arr) ProArrayFree(arr);
}
};
using ProArrayPtr = std::unique_ptr<ProArray, ProArrayDeleter>;
小结
在 Creo + Qt 开发中实现流畅的进度条,核心思路就是把大任务拆分成小步骤,利用 Qt 的事件机制异步执行。根据实际需求选择合适的方案:
- 简单场景用 processEvents()
- 中等复杂度用定时器分步
- 复杂场景用任务队列
- 需要详细进度时加入嵌套进度条
记住一个原则:不要在主线程做耗时太长的同步操作。把任务切碎,让 UI 有机会喘口气,用户体验就会好很多。
希望这些经验对你有帮助。如果你有其他好的解决方案,欢迎交流!
相关文章
制造业数字化转型实践指南:从传统工厂到智能制造的完整路径
深入解析制造业数字化转型的关键技术、实施策略和成功案例,为企业提供可操作的数字化转型路径。
CAD与AI结合的智能设计实战案例:重塑现代产品开发流程
深入探讨CAD与AI技术融合的实际应用案例,展示智能设计在航空、汽车、机械等行业的创新实践。
从传统制造到智能制造的技术路径:企业转型升级的完整指南
深入解读制造业向智能制造转型的关键步骤、实际案例和成功经验,为企业数字化转型提供实用指南