一、我们是如何利用信号完成这个联动需求的
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。
三、现在这版为什么是安全的?
最终版本的几个关键点,使它既满足需求又稳定:
-
构造函数中:
- 只做基础初始化(创建曲线控件、默认
m_vShowVars = m_vDefaultResidualHeads); - 不在此时调用
updateShowVarsByTurbulenceModel(),避免初始化顺序风险。
- 只做基础初始化(创建曲线控件、默认
-
运行期:
- 只在
CUISigsCenter::sig_cuiPropertyChanged(type = Viscosity_Model)时调用updateShowVarsByTurbulenceModel(); - 这时所有 DataManager 和 case.json 解析都已经完成,
getModel()/getRNSModel()的调用环境是安全的。
- 只在
-
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),其它结构可以保持不变。