fix(numerical): 修复首次求解后结果窗口崩溃

- 恢复非模态进度框并分阶段隐藏、刷新结果,避免 Qt 4.8 界面重入
- 首次创建结果窗口时延迟填充 VTK 和参数页面
- 复用现有布局并移除 setLayout(nullptr) 的不兼容处理
- 使用队列连接接收求解完成和进度信号
feature/grid-solver-workflow-20260827
lh 2 weeks ago
parent 0e63dc2185
commit 869b5c3aeb

@ -140,6 +140,12 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
void on_calculationFinished(NM_Calculation_Result); void on_calculationFinished(NM_Calculation_Result);
// DLL线程只返回成功/失败,这里转换回原来的计算结果处理流程。 // DLL线程只返回成功/失败,这里转换回原来的计算结果处理流程。
void on_solverTaskFinished(bool isSuccessed); void on_solverTaskFinished(bool isSuccessed);
/** @brief 完成回调退出后隐藏求解进度框。 */
void dismissPendingSolverProgress();
/** @brief 进度框隐藏后的下一轮事件中完成结果界面刷新。 */
void completePendingSolverResult();
/** @brief 首次结果载体创建完成后,在下一轮事件中填充三维和参数页面。 */
void populatePendingResultWidgets();
/** @brief 进度框显示后再创建任务,避免输入捕获发生在首次绘制之前。 */ /** @brief 进度框显示后再创建任务,避免输入捕获发生在首次绘制之前。 */
void startSolverTask(); void startSolverTask();
/** @brief 每轮事件只捕获少量井输入,保持准备阶段界面和停止按钮可响应。 */ /** @brief 每轮事件只捕获少量井输入,保持准备阶段界面和停止按钮可响应。 */
@ -228,6 +234,15 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
void triggerToolBarAction(int index); void triggerToolBarAction(int index);
// 合并数据到流量端分析窗口 // 合并数据到流量端分析窗口
void mergeAnaResultToFitting(); void mergeAnaResultToFitting();
// 填充已经存在的数值三维和结果参数页面。
void populateResultWidgets(iSubWndFitting* pSubWndFit,
nmDataAnalyzeManager* pDataManager);
// 将首次结果载体的页面填充排队到当前模态调用结束之后。
void schedulePendingResultWidgets();
// 先隐藏求解进度框,再排队处理结果界面。
void scheduleSolverResultCompletion(bool bSucceeded,
int nPebiCount,
int nSolveTimeMs);
// 半对数曲线数据 // 半对数曲线数据
bool inithalfLog(QVector<QVector<double >> & vvecLogPreData, QVector<iCurveDesc>& vecDescs,bool isPreLoadData); bool inithalfLog(QVector<QVector<double >> & vvecLogPreData, QVector<iCurveDesc>& vecDescs,bool isPreLoadData);
// 双对数曲线数据 // 双对数曲线数据
@ -270,6 +285,15 @@ private:
QPointer<nmDataAnalyzeManager> m_pSolverDataManager; QPointer<nmDataAnalyzeManager> m_pSolverDataManager;
/** @brief 启动求解时捕获的拟合窗口,只把结果合并回该窗口。 */ /** @brief 启动求解时捕获的拟合窗口,只把结果合并回该窗口。 */
QPointer<iSubWndFitting> m_pSolverFitting; QPointer<iSubWndFitting> m_pSolverFitting;
/** @brief 首次结果载体创建后,延迟页面填充所需的数据管理器。 */
QPointer<nmDataAnalyzeManager> m_pPendingResultDataManager;
/** @brief 首次结果载体创建后,延迟页面填充所需的拟合窗口。 */
QPointer<iSubWndFitting> m_pPendingResultFitting;
bool m_bPendingResultWidgetRefresh; ///< 正在等待下一轮事件填充首次结果页面。
bool m_bSolverResultCompletionPending; ///< 等待进度框隐藏后处理求解结果。
bool m_bPendingSolverSucceeded; ///< 待处理求解结果是否成功。
int m_nPendingSolverPebiCount; ///< 待显示的网格数量。
int m_nPendingSolverTimeMs; ///< 待显示的求解耗时。
bool m_bSolverStartPending; ///< 进度框已显示,输入正分批捕获或任务等待启动。 bool m_bSolverStartPending; ///< 进度框已显示,输入正分批捕获或任务等待启动。
bool m_bSolverCancelRequested; ///< 已向当前任务发送停止请求,忽略后续阶段信号。 bool m_bSolverCancelRequested; ///< 已向当前任务发送停止请求,忽略后续阶段信号。

@ -130,6 +130,13 @@ nmSubWndMain::nmSubWndMain(QWidget *parent, QString sExt) :
m_pSolverTask = nullptr; m_pSolverTask = nullptr;
m_pSolverDataManager = nullptr; m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr; m_pSolverFitting = nullptr;
m_pPendingResultDataManager = nullptr;
m_pPendingResultFitting = nullptr;
m_bPendingResultWidgetRefresh = false;
m_bSolverResultCompletionPending = false;
m_bPendingSolverSucceeded = false;
m_nPendingSolverPebiCount = -1;
m_nPendingSolverTimeMs = -1;
m_bSolverStartPending = false; m_bSolverStartPending = false;
m_bSolverCancelRequested = false; m_bSolverCancelRequested = false;
m_pPlotToolBar = nullptr; m_pPlotToolBar = nullptr;
@ -486,6 +493,7 @@ bool nmSubWndMain::runCmdBy(QString sName, QString sID)
case 5117: case 5117:
this->mergeAnaResultToFitting(); this->mergeAnaResultToFitting();
this->schedulePendingResultWidgets();
return true; return true;
case 5121: case 5121:
@ -1523,15 +1531,20 @@ void nmSubWndMain::generationMesh()
void nmSubWndMain::solveAndAnalyze() void nmSubWndMain::solveAndAnalyze()
{ {
if(!s_pPendingSolverWindow.isNull() || if(!s_pPendingSolverWindow.isNull() ||
!s_pRunningSolverTask.isNull()) { !s_pRunningSolverTask.isNull() ||
m_bSolverResultCompletionPending) {
QMessageBox::information(this, tr("solver error"), tr("task is running!")); QMessageBox::information(this, tr("solver error"), tr("task is running!"));
return; return;
} }
// 强制清理旧的(以防万一上次没删掉) // Qt 4.8 下复用已经隐藏的进度框,避免窗口析构触发主界面重绘重入。
if (m_pProgressDlg != nullptr) { if (m_pProgressDlg != nullptr) {
delete m_pProgressDlg; m_pProgressDlg->reset();
m_pProgressDlg = nullptr; QPushButton* pCancelButton =
m_pProgressDlg->findChild<QPushButton*>();
if(pCancelButton != nullptr) {
pCancelButton->setEnabled(true);
}
} }
// 新一次启动前清空旧上下文;后续所有检查通过后再成对写入。 // 新一次启动前清空旧上下文;后续所有检查通过后再成对写入。
@ -1645,9 +1658,9 @@ void nmSubWndMain::solveAndAnalyze()
0, 0,
getMainWindow()); getMainWindow());
m_pProgressDlg->setWindowTitle(tr("Solver Progress")); m_pProgressDlg->setWindowTitle(tr("Solver Progress"));
// 后台只读取任务值快照;应用级模态仍用于阻止重复启动、切换成果等会让 // 保持 8 月 25 日前的稳定行为。任务和输入快照负责数据安全,进度框不参与
// 用户误判当前计算归属的操作,数据安全不再依赖该对话框冻结界面 // 全局窗口阻塞,避免首次创建结果窗口时进入 Qt 4.8 模态布局重入
m_pProgressDlg->setWindowModality(Qt::ApplicationModal); m_pProgressDlg->setWindowModality(Qt::NonModal);
// 某些系统下,对话框右上角的关闭按钮可能仍存 // 某些系统下,对话框右上角的关闭按钮可能仍存
// 使用 WindowFlags 彻底禁用关闭按钮 // 使用 WindowFlags 彻底禁用关闭按钮
@ -1684,8 +1697,7 @@ void nmSubWndMain::startSolverTask()
s_pPendingSolverWindow = nullptr; s_pPendingSolverWindow = nullptr;
} }
if(m_pProgressDlg != nullptr) { if(m_pProgressDlg != nullptr) {
m_pProgressDlg->deleteLater(); m_pProgressDlg->hide();
m_pProgressDlg = nullptr;
} }
m_pSolverDataManager = nullptr; m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr; m_pSolverFitting = nullptr;
@ -1705,9 +1717,11 @@ void nmSubWndMain::startSolverTask()
true); true);
s_pRunningSolverTask = m_pSolverTask; s_pRunningSolverTask = m_pSolverTask;
connect(m_pSolverTask, SIGNAL(sig_calculateDone(bool)), connect(m_pSolverTask, SIGNAL(sig_calculateDone(bool)),
this, SLOT(on_solverTaskFinished(bool))); this, SLOT(on_solverTaskFinished(bool)),
Qt::QueuedConnection);
connect(m_pSolverTask, SIGNAL(sigStageChanged(QString,int,int)), connect(m_pSolverTask, SIGNAL(sigStageChanged(QString,int,int)),
this, SLOT(onSolverStageChanged(QString,int,int))); this, SLOT(onSolverStageChanged(QString,int,int)),
Qt::QueuedConnection);
// 给 Windows 一次完成首帧绘制的机会;后续每个事件循环只捕获一口井。 // 给 Windows 一次完成首帧绘制的机会;后续每个事件循环只捕获一口井。
QTimer::singleShot(20, this, SLOT(continueSolverInputCapture())); QTimer::singleShot(20, this, SLOT(continueSolverInputCapture()));
} }
@ -1741,13 +1755,14 @@ void nmSubWndMain::continueSolverInputCapture()
pTask->deleteLater(); pTask->deleteLater();
if(bCancelled) { if(bCancelled) {
delete m_pProgressDlg; if(m_pProgressDlg != nullptr) {
m_pProgressDlg = nullptr; m_pProgressDlg->hide();
}
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
} else { } else {
on_calculationFinished(NM_Calculation_Result_Fail); scheduleSolverResultCompletion(false, -1, -1);
} }
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_bSolverCancelRequested = false; m_bSolverCancelRequested = false;
return; return;
} }
@ -1805,11 +1820,7 @@ void nmSubWndMain::onSolverCancelRequested()
disconnect(pTask, nullptr, this, nullptr); disconnect(pTask, nullptr, this, nullptr);
pTask->deleteLater(); pTask->deleteLater();
} }
// canceled() 由进度框自身发出,延迟释放避免在信号调用栈内销毁发送者。 // QProgressDialog::cancel() 已隐藏窗口;保留对象供下一次求解复用。
if(m_pProgressDlg != nullptr) {
m_pProgressDlg->deleteLater();
}
m_pProgressDlg = nullptr;
m_pSolverDataManager = nullptr; m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr; m_pSolverFitting = nullptr;
m_bSolverCancelRequested = false; m_bSolverCancelRequested = false;
@ -1956,6 +1967,11 @@ void nmSubWndMain::mergeAnaResultToFitting()
return; return;
} }
// 新一轮主动刷新替代尚未执行的旧请求;已经投递的旧槽会检测标记并直接返回。
m_bPendingResultWidgetRefresh = false;
m_pPendingResultDataManager = nullptr;
m_pPendingResultFitting = nullptr;
QVector<nmSolverWellRef> vecWellsOrder = pInstance->getSolverWellOrder(); QVector<nmSolverWellRef> vecWellsOrder = pInstance->getSolverWellOrder();
QString sCurrentResultWellCode = pInstance->getCurrentResultWellCode(); QString sCurrentResultWellCode = pInstance->getCurrentResultWellCode();
@ -2023,69 +2039,126 @@ void nmSubWndMain::mergeAnaResultToFitting()
QString errorMessage4; QString errorMessage4;
pSubWndFit->adjustFitSubPlotBy(tag, vecHistory, true, &errorMessage4); pSubWndFit->adjustFitSubPlotBy(tag, vecHistory, true, &errorMessage4);
QWidget* widget1 = pSubWndFit->getFitSubRstWxOf(FSRT_Nm3D, true, &errorMessage); if(pInstance->getGridType() != NM_Grid_PEBI) {
if(widget1 == nullptr || pInstance->getGridType() != NM_Grid_PEBI) { return;
}
// getFitSubRstWxOf(..., true) 会同步创建 iWxResult、挂入 MDI 并切换当前页。
// 首次创建时只建立外层载体,待本轮 show/resize/layout 事件结束后再填充内容。
QWidget* p3DWidget = pSubWndFit->getFitSubRstWxOf(
FSRT_Nm3D, false, &errorMessage);
if(p3DWidget == nullptr) {
p3DWidget = pSubWndFit->getFitSubRstWxOf(
FSRT_Nm3D, true, &errorMessage);
if(p3DWidget == nullptr) {
return;
}
m_pPendingResultDataManager = pInstance;
m_pPendingResultFitting = pSubWndFit;
m_bPendingResultWidgetRefresh = true;
return;
}
populateResultWidgets(pSubWndFit, pInstance);
}
void nmSubWndMain::populateResultWidgets(
iSubWndFitting* pSubWndFit,
nmDataAnalyzeManager* pDataManager)
{
if(pSubWndFit == nullptr || pDataManager == nullptr ||
pDataManager->getGridType() != NM_Grid_PEBI ||
nmDataAnalyzeManager::findManagerByFitting(pSubWndFit) != pDataManager) {
return;
}
QString sErrorMessage;
QWidget* p3DWidget = pSubWndFit->getFitSubRstWxOf(
FSRT_Nm3D, true, &sErrorMessage);
if(p3DWidget == nullptr) {
return; return;
} }
nmWxPostprocessingAnimationWidget* vtkWidget = nmWxPostprocessingAnimationWidget* pVtkWidget =
widget1->findChild<nmWxPostprocessingAnimationWidget*>(); p3DWidget->findChild<nmWxPostprocessingAnimationWidget*>();
if(vtkWidget != nullptr) { if(pVtkWidget != nullptr) {
// 复用既有渲染窗口和 VTK 管道,只切换本轮已整体提交的数据。 // 复用既有渲染窗口和 VTK 管道,只切换本轮已整体提交的数据。
vtkWidget->refreshResult(); pVtkWidget->refreshResult();
} else { } else {
vtkWidget = new nmWxPostprocessingAnimationWidget(NULL, pInstance); pVtkWidget = new nmWxPostprocessingAnimationWidget(NULL, pDataManager);
QLayout* layout1 = widget1->layout(); QLayout* p3DLayout = p3DWidget->layout();
if(layout1) { if(p3DLayout == nullptr) {
QLayoutItem* item; p3DLayout = new QVBoxLayout(p3DWidget);
while((item = layout1->takeAt(0))) { } else {
if(item->widget()) { QLayoutItem* pItem = nullptr;
delete item->widget(); while((pItem = p3DLayout->takeAt(0)) != nullptr) {
if(pItem->widget() != nullptr) {
delete pItem->widget();
} }
delete item; delete pItem;
} }
delete layout1;
} }
p3DLayout->addWidget(pVtkWidget);
widget1->setLayout(nullptr);
QVBoxLayout* newLayout1 = new QVBoxLayout(widget1);
newLayout1->addWidget(vtkWidget);
widget1->setLayout(newLayout1);
} }
// 结果参数界面 // 结果参数界面
QWidget* widget2 = pSubWndFit->getFitSubRstWxOf(FSRT_NmRst, true, &errorMessage); QWidget* pResultPage = pSubWndFit->getFitSubRstWxOf(
if(widget2 == nullptr) { FSRT_NmRst, true, &sErrorMessage);
if(pResultPage == nullptr) {
return; return;
} }
nmWxParaPropertyPebi* resultWidget = nmWxParaPropertyPebi* pResultWidget =
widget2->findChild<nmWxParaPropertyPebi*>(); pResultPage->findChild<nmWxParaPropertyPebi*>();
if(resultWidget == nullptr) { if(pResultWidget == nullptr) {
// 与左侧共用属性面板类,结果区使用独立实例和只读参数列表。 // 与左侧共用属性面板类,结果区使用独立实例和只读参数列表。
resultWidget = new nmWxParaPropertyPebi; pResultWidget = new nmWxParaPropertyPebi;
resultWidget->initUI(); pResultWidget->initUI();
pResultWidget->setDataManager(pDataManager);
QLayout* layout2 = widget2->layout(); // 保持 8 月 24 日版本的稳定顺序:离屏完成参数构建后再加入可见布局。
if(layout2) { pResultWidget->rebuildResultParas();
QLayoutItem* item;
while((item = layout2->takeAt(0))) { QLayout* pResultLayout = pResultPage->layout();
if(item->widget()) { if(pResultLayout == nullptr) {
delete item->widget(); pResultLayout = new QVBoxLayout(pResultPage);
} else {
QLayoutItem* pItem = nullptr;
while((pItem = pResultLayout->takeAt(0)) != nullptr) {
if(pItem->widget() != nullptr) {
delete pItem->widget();
} }
delete item; delete pItem;
} }
delete layout2;
} }
widget2->setLayout(nullptr); pResultLayout->addWidget(pResultWidget);
QVBoxLayout* newLayout2 = new QVBoxLayout(widget2); return;
newLayout2->addWidget(resultWidget);
widget2->setLayout(newLayout2);
} }
resultWidget->setDataManager(pInstance); pResultWidget->setDataManager(pDataManager);
resultWidget->rebuildResultParas(); pResultWidget->rebuildResultParas();
}
void nmSubWndMain::schedulePendingResultWidgets()
{
if(m_bPendingResultWidgetRefresh) {
QTimer::singleShot(0, this, SLOT(populatePendingResultWidgets()));
}
}
void nmSubWndMain::populatePendingResultWidgets()
{
if(!m_bPendingResultWidgetRefresh) {
return;
}
iSubWndFitting* pSubWndFit = m_pPendingResultFitting.data();
nmDataAnalyzeManager* pDataManager = m_pPendingResultDataManager.data();
m_bPendingResultWidgetRefresh = false;
m_pPendingResultDataManager = nullptr;
m_pPendingResultFitting = nullptr;
populateResultWidgets(pSubWndFit, pDataManager);
} }
bool nmSubWndMain::inithalfLog(QVector<QVector<double >> & vvecLogPreData, QVector<iCurveDesc>& vecDescs, bool isHistoryData) bool nmSubWndMain::inithalfLog(QVector<QVector<double >> & vvecLogPreData, QVector<iCurveDesc>& vecDescs, bool isHistoryData)
@ -2302,24 +2375,72 @@ void nmSubWndMain::geoLayering()
void nmSubWndMain::on_calculationFinished(NM_Calculation_Result result) void nmSubWndMain::on_calculationFinished(NM_Calculation_Result result)
{ {
// 结果提交成功后仍保留进度框,直到曲线和结果页刷新全部完成。
if (m_pProgressDlg) {
m_pProgressDlg->setLabelText(tr("Updating result views..."));
m_pProgressDlg->setRange(0, 0);
}
if(result == NM_Calculation_Result_Success) { if(result == NM_Calculation_Result_Success) {
this->mergeAnaResultToFitting(); this->mergeAnaResultToFitting();
} else { } else {
// 如果失败,进度条其实没必要拉满了,直接提示错误 // 如果失败,进度条其实没必要拉满了,直接提示错误
QMessageBox::warning(this, tr("solver error"), tr("solver failed!")); QMessageBox::warning(this, tr("solver error"), tr("solver failed!"));
} }
}
if (m_pProgressDlg != nullptr) { void nmSubWndMain::scheduleSolverResultCompletion(
delete m_pProgressDlg; bool bSucceeded,
m_pProgressDlg = nullptr; int nPebiCount,
int nSolveTimeMs)
{
s_pPendingSolverWindow = this;
m_bSolverResultCompletionPending = true;
m_bPendingSolverSucceeded = bSucceeded;
m_nPendingSolverPebiCount = nPebiCount;
m_nPendingSolverTimeMs = nSolveTimeMs;
// 当前完成信号栈只登记后续工作;隐藏和结果刷新各占一轮独立事件。
QTimer::singleShot(0, this, SLOT(dismissPendingSolverProgress()));
}
void nmSubWndMain::dismissPendingSolverProgress()
{
if(m_pProgressDlg != nullptr) {
m_pProgressDlg->hide();
}
QTimer::singleShot(0, this, SLOT(completePendingSolverResult()));
}
void nmSubWndMain::completePendingSolverResult()
{
if(!m_bSolverResultCompletionPending) {
return;
} }
const bool bSucceeded = m_bPendingSolverSucceeded;
const int nPebiCount = m_nPendingSolverPebiCount;
const int nSolveTimeMs = m_nPendingSolverTimeMs;
m_bSolverResultCompletionPending = false;
m_bPendingSolverSucceeded = false;
m_nPendingSolverPebiCount = -1;
m_nPendingSolverTimeMs = -1;
on_calculationFinished(bSucceeded
? NM_Calculation_Result_Success
: NM_Calculation_Result_Fail);
// mergeAnaResultToFitting() 已使用完启动时捕获的上下文。
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_bSolverCancelRequested = false;
if(bSucceeded && nPebiCount >= 0 && nSolveTimeMs >= 0) {
QMessageBox::information(this, tr("Solver completed"),
tr("Grid count: %1\nSolve time: %2 ms")
.arg(nPebiCount)
.arg(nSolveTimeMs));
}
// 首次结果载体的填充必须在成功提示的模态事件循环完全退出后执行。
schedulePendingResultWidgets();
if(s_pPendingSolverWindow == this) {
s_pPendingSolverWindow = nullptr;
}
} }
void nmSubWndMain::on_solverTaskFinished(bool isSuccessed) void nmSubWndMain::on_solverTaskFinished(bool isSuccessed)
@ -2353,25 +2474,16 @@ void nmSubWndMain::on_solverTaskFinished(bool isSuccessed)
// 第二步:停止不属于失败,也不允许触发结果提交或覆盖旧成果。 // 第二步:停止不属于失败,也不允许触发结果提交或覆盖旧成果。
if(bCancelled) { if(bCancelled) {
delete m_pProgressDlg; if(m_pProgressDlg != nullptr) {
m_pProgressDlg = nullptr; m_pProgressDlg->hide();
}
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_bSolverCancelRequested = false;
} else { } else {
NM_Calculation_Result result = isSuccessed scheduleSolverResultCompletion(isSuccessed,
? NM_Calculation_Result_Success nPebiCount,
: NM_Calculation_Result_Fail; nSolveTimeMs);
this->on_calculationFinished(result);
}
// 第三步:结果处理结束后释放非拥有型上下文引用。
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_bSolverCancelRequested = false;
if(isSuccessed && nPebiCount >= 0 && nSolveTimeMs >= 0) {
QMessageBox::information(this, tr("Solver completed"),
tr("Grid count: %1\nSolve time: %2 ms")
.arg(nPebiCount)
.arg(nSolveTimeMs));
} }
} }

Loading…
Cancel
Save