Qt开发 特色文章

在 Qt + Creo 二次开发中实现流畅的进度条显示

本文分享在 Creo 二次开发中,如何结合 Qt 框架实现不卡UI的进度条展示。包含任务调度、嵌套进度等实战经验。

作者:青岛辰时 发布:2025年7月4日 阅读:5分钟 字数:1,427
分享:

做过 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(&param, &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(&param, &value);
    ProSolidRegenerate(solid, PRO_REGEN_NO_FLAGS);  // 太慢!
}

// 正确做法:批量修改后统一重生成
for (auto& param : params) {
    ProParameterValueSet(&param, &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 Agent技术研发, 拥有丰富的项目实战经验和深厚的技术积累。

相关文章