利用信号完成这个联动需求

一、我们是如何利用信号完成这个联动需求的

1. 模型设置侧:通过 semaphore 发出统一的属性变更信号

CFDStructDataSolverKvislManager 中,与你截图对应的 UI 组件("模型"、"RANS 模型"、"S‑A 类型"等)在创建时都设置了同一个 semaphore

C++

复制代码
// 组:模型
new CUIConfig({
    {"type", "RadioComponent"},
    {"name", tr("Model")},
    {"widget", "GroupBox"},
    {"value_type", CUI_DATA_TYPE::CUI_DATA_TYPE_INT},
    {"value_origin", QVA_GLOBAL(&m_Model)},
    {"semaphore", (int)SolutionAnalysisModuleProperty::Viscosity_Model},
}, { ... }),

// 组:RANS模型
new CUIConfig({
    {"type", "RadioComponent"},
    {"name", tr("RANS Model")},
    {"widget", "GroupBox"},
    {"value_type", CUI_DATA_TYPE::CUI_DATA_TYPE_INT},
    {"value_origin", QVA_GLOBAL(&m_RNSModel)},
    {"semaphore", (int)SolutionAnalysisModuleProperty::Viscosity_Model},
}, { ... }),

这意味着:

用户一旦在模型设置面板中切换这些单选按钮,CUISigsCenter 会发出
sig_cuiPropertyChanged(type = Viscosity_Model, objId = ..., moduleId = ...)

所以,你的"无粘 / 层流 / RANS、S‑A / κ‑ω / κ‑ε"等操作,都会触发同一个属性变更信号 Viscosity_Model


2. 残差监控侧:监听这个属性变化信号

CFDStructDataSolverMonitorResidualManager 构造函数中,我们添加了对这个信号的监听:

C++

复制代码
connect(m_cuiSigsCenter, &CUISigsCenter::sig_cuiPropertyChanged,
        [ = ](int type, int objId, const QString &moduleId) {

    qDebug() << "[Residual] sig_cuiPropertyChanged received, type =" << type
             << ", objId =" << objId
             << ", moduleId =" << moduleId;

    if (type == (int)SolutionAnalysisModuleProperty::Viscosity_Model) {
        qDebug() << "[Residual]   -> matched Viscosity_Model, calling updateShowVarsByTurbulenceModel()";
        this->updateShowVarsByTurbulenceModel();
        // (最终版里去掉了全局刷新,以免跳页)
        // emit CFDStructSigsCenter::getInstance()->sig_updateSolverModuleProperty();
    }
});

这样一来:

只要"模型设置"那边有任何更改,残差监控这边就会收到通知,并调用
updateShowVarsByTurbulenceModel() 来更新自身的显示变量。


3. 在残差管理器里,通过 DataManager 读取当前模型类型

CFDStructDataManager 已经提供了访问当前粘性模型与 RANS 模型类型的方法:

C++

复制代码
int CFDStructDataManager::getModel()    { return m_solverKvislManager->getModel(); }
int CFDStructDataManager::getRNSModel() { return m_solverKvislManager->getRNSModel(); }

我们在残差管理器内部函数 updateShowVarsByTurbulenceModel() 里用它们来决定 Residual 面板显示哪些变量:

C++

复制代码
void CFDStructDataSolverMonitorResidualManager::updateShowVarsByTurbulenceModel()
{
    // 基础 5 个
    QVector<QString> vars;
    vars << "continuity"
         << "x-velocity"
         << "y-velocity"
         << "z-velocity"
         << "energy";

    CFDStructDataManager *pDataMgr = CFDStructDataManager::getInstance();
    if (pDataMgr) {
        int model   = pDataMgr->getModel();    // 0:无粘, 1:层流, 2:RANS, 3:DES, 4:CLES
        int rnsType = pDataMgr->getRNSModel(); // 0:S-A, 1:k-w, 2:k-e

        if (model == 2) { // 只在 RANS 下细分
            if (rnsType == 0) {
                // RANS + S-A(一方程)
                vars << "nu";
            } else if (rnsType == 1 || rnsType == 2) {
                // RANS + Menters κ-ω / κ-ε(两方程)
                vars << "k" << "omega";
            }
        }
    }

    // 用白名单过滤,保证变量名合法
    QVector<QString> filtered;
    for (const auto &name : vars) {
        if (m_vDefaultResidualHeads.contains(name)) {
            filtered << name;
        }
    }
    m_vShowVars = filtered;

    // 通知曲线控件更新变量
    if (m_pCurveWidget) {
        m_pCurveWidget->showVars(m_vShowVars, this->getShowVarsCH());
    }
}

逻辑对应关系:

  • 无粘/层流/其它:m_vShowVars = 5 个基础量;
  • RANS + S‑A:m_vShowVars = 5 个基础量 + nu(6 个);
  • RANS + κ‑ω/κ‑ε:m_vShowVars = 5 个基础量 + k, omega(7 个)。

4. MultiTableWidget 使用 m_vShowVars 同步 UI 行

MultiTableWidget 绑定的是 m_vShowVars

C++

复制代码
{"value_origin", QVA_GLOBAL(&m_vShowVars)},

而它的行是由 getTableSubCUI() 决定的。我们改成了"按 m_vShowVars 来生成行":

C++

复制代码
QVector<CUIConfig*> CFDStructDataSolverMonitorResidualManager::getTableSubCUI() {
    QVector<CUIConfig*> subCUI;

    // 如果 m_vShowVars 为空,用默认 8 个兜底;否则用当前 m_vShowVars
    QVector<QString> heads = m_vShowVars.isEmpty() ? m_vDefaultResidualHeads : m_vShowVars;

    for (const QString &head : heads) {
        if (m_mDefaultResidualHeadsCH.contains(head)) {
            subCUI.push_back(new CUIConfig({
                {"type", "TabLine"},
                {"head", head},
                {"headCH", m_mDefaultResidualHeadsCH[head]},
            }));
        }
    }

    return subCUI;
}

这样:

  • 在无粘/层流:残差表格只有 5 行;
  • 在 RANS + S‑A:6 行;
  • 在 RANS + κ‑ω/κ‑ε:7 行。

你看到的"行数变少,而不是 8 行里打勾/打叉",就是通过这个函数实现的。


二、闪退/崩溃的根本原因

你遇到的闪退现象是:

  • 在我们一开始把 updateShowVarsByTurbulenceModel() 放到构造函数里调用时:

    C++

    复制代码
    // 默认显示所有变量
    m_vShowVars = m_vDefaultResidualHeads;
    // 根据当前模型设置先做一次更新
    this->updateShowVarsByTurbulenceModel();
  • 程序一启动就崩溃/黑框一闪退出;

  • 把这一行注释掉后,程序恢复正常。

这说明:

崩溃发生在 构造阶段 调用 updateShowVarsByTurbulenceModel() 时,

而在后面通过信号触发调用是安全的(你现在的版本运行正常)。

结合工程结构,可以推断原因主要有两点:

1. 初始化顺序问题:在 DataManager / KvislManager 尚未完全准备好时调用

CFDStructDataManager::initDataManagers() 里大致顺序是:

C++

复制代码
m_solverKvislManager            = new CFDStructDataSolverKvislManager;
m_solverMonitorResidualManager  = new CFDStructDataSolverMonitorResidualManager;
// ...

然后在 loadCaseJson() / loadCaseJson1() 中才对这些 manager 调 readDataFromDom(),填充实际的 m_Model/m_RNSModel 等值。

我们在 残差管理器构造函数中 调用了:

C++

复制代码
CFDStructDataManager *pDataMgr = CFDStructDataManager::getInstance();
int model   = pDataMgr->getModel();    // 内部访问 m_solverKvislManager->getModel()
int rnsType = pDataMgr->getRNSModel();

这时:

  • KvislManager 已经 new 出来了,但它的内部状态 可能还没有从 case.json 里读完,甚至部分全局对象/单例还没完全构建好;
  • getModel()/getRNSModel() 在内部可能又访问了其它还未初始化完成的对象(比如 GeneralSetting、其他 DataManager,或者间接触发 UI 刷新等),就造成了访问非法指针或递归构造,从而崩溃。

一旦我们把调用改成:

  • 不在构造函数里调用,只在属性已经加载、用户真正改动模型设置(运行时)之后,通过 sig_cuiPropertyChanged 里的回调调用 updateShowVarsByTurbulenceModel()
  • 此时 DataManager / KvislManager / 通用设置等都已经初始化完成,调用就变得安全,程序不再闪退。

这也是"初始化顺序不当"常见造成崩溃的典型情况。

2. 我们最初还在回调里用了全局 sig_updateSolverModuleProperty(),导致界面频繁重建

中途你还遇到一个"点击模型设置会被自动跳到别的属性页(图一)"的问题,这不是崩溃,但也是副作用的来源之一:

C++

复制代码
emit CFDStructSigsCenter::getInstance()->sig_updateSolverModuleProperty();

这个信号在前处理阶段用于"整体刷新 SolutionAnalysis 属性 UI",放在模型设置回调里会导致:

  • 每点一次模型按钮,就重建整个属性树;
  • 选中模块可能回到第一个(例如你看到的那块"流动类型/方程类型/流动模型"的页面),看起来像界面"跳走"了。

不过这不是崩溃的直接原因,只是我们后来为了避免 UI 跳页,把它删掉了,最终只用 m_vShowVars + getTableSubCUI() 来控制残差模块自己的 UI。


三、现在这版为什么是安全的?

最终版本的几个关键点,使它既满足需求又稳定:

  1. 构造函数中:

    • 只做基础初始化(创建曲线控件、默认 m_vShowVars = m_vDefaultResidualHeads);
    • 不在此时调用 updateShowVarsByTurbulenceModel(),避免初始化顺序风险。
  2. 运行期:

    • 只在 CUISigsCenter::sig_cuiPropertyChanged(type = Viscosity_Model) 时调用 updateShowVarsByTurbulenceModel()
    • 这时所有 DataManager 和 case.json 解析都已经完成,getModel()/getRNSModel() 的调用环境是安全的。
  3. UI 更新:

    • 曲线控件通过 m_pCurveWidget->showVars(m_vShowVars, ...) 即时更新显示变量;
    • MultiTableWidget 的行通过 getTableSubCUI() 里的 m_vShowVars 动态生成,不依赖全局刷新信号,因此不会引起属性树乱跳。

所以,崩溃的本质是:

在构造阶段过早调用了依赖全局状态(DataManager/KvislManager)的逻辑,触发了未完全初始化对象的访问。

把调用时机推迟到"信号回调(运行时)",并只在那时读取模型状态,就解决了闪退问题。


如果后面你再想把 DES/CLES 也细分成类似规则(比如 DES+S‑A 时显示 nu,DES+Menters κ‑ω 显示 k/omega),只需要在当前稳定的 updateShowVarsByTurbulenceModel() 里,把 if (model == 2) 改成 if (model == 2 || model == 3 || model == 4),其它结构可以保持不变。

相关推荐
灰乌鸦乌卡2 小时前
泛微OA集成档案信息包生成
java
世转神风-2 小时前
qt-弹框提示-界面提醒
开发语言·qt·策略模式
500842 小时前
鸿蒙 Flutter 超级终端适配:多设备流转与状态无缝迁移
java·人工智能·flutter·华为·性能优化·wpf
codealy2 小时前
Spring 事务失效的八大场景深度解析
java·spring boot·后端·spring
计算衎2 小时前
基于python的FastAPI框架目录结构介绍、开发思路和标准开发模板总结
开发语言·python·fastapi
好学且牛逼的马2 小时前
【手写Mybatis | version0.0.1 附带源码 项目文档】
java·开发语言·mybatis
AM越.2 小时前
Java设计模式超详解--单例设计模式(含uml图)
java·设计模式·uml
wjykp2 小时前
第八章异常
开发语言·python
Neoest2 小时前
【Java 填坑日记】Excel里的“1.00“存入数据库解密后,Integer说它不认识:一次 NumberFormatException 翻车实录
java·数据库·excel