diff --git a/Include/nmNum/nmSubWnd/nmSubWndMain.h b/Include/nmNum/nmSubWnd/nmSubWndMain.h index 5f11d09e..97096a14 100644 --- a/Include/nmNum/nmSubWnd/nmSubWndMain.h +++ b/Include/nmNum/nmSubWnd/nmSubWndMain.h @@ -20,6 +20,7 @@ class nmDataWellBase; class nmDataAnalyzeManager; class nmCalculationDllPebiSolverTask; class nmWxSensitive; +class nmWxForecast; class iSubWndFitting; class QWidget; class QObject; @@ -166,8 +167,9 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit { void onSolverCancelRequested(); /** @brief 接管敏感性批量任务,使用当前窗口的普通求解进度框。 */ void onSensitivityTaskReady(nmWxSensitive* dialog, nmCalculationDllPebiSolverTask* task); - void onSensitivityTaskFinished(); - void onSensitivityTaskDestroyed(); + void onForecastTaskReady(nmWxForecast* dialog, nmCalculationDllPebiSolverTask* task); + void onAuxiliarySolverTaskFinished(); + void onAuxiliarySolverTaskDestroyed(); /** @brief 在框架已有的敏感性子窗口叠加显示本分析的双对数结果。 */ void onShowSensitiveResults(); // 预测结果复用所属分析内的 MDI 子窗口,模型变化时移除旧结果。 @@ -305,8 +307,10 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit { bool waitForPebiGridGeneration(); /** @brief 取消当前求解上下文对建网完成信号的订阅。 */ void disconnectPebiGridGenerationWait(); - /** @brief 普通求解和敏感性分析共用同一个进度框实例及初始化路径。 */ + /** @brief 普通求解、敏感性分析和预测共用进度框及初始化路径。 */ void showSolverProgress(); + // 敏感性与预测共用进度和停止处理,结果仍由各自参数窗口接收。 + bool prepareAuxiliarySolverTask(QWidget* dialog, nmCalculationDllPebiSolverTask* task); // 绘图相关 nmGuiPlot* m_pWxPlot; @@ -330,8 +334,8 @@ private: QProgressDialog* m_pProgressDlg; nmCalculationDllPebiSolverTask* m_pSolverTask; ///< 当前窗口持有的 DLL 求解线程任务。 - // 批量任务仍由敏感性窗口持有,这里只绑定进度和停止操作。 - QPointer m_pSensitivityTask; + // 敏感性和预测任务仍由参数窗口持有,这里只绑定进度和停止操作。 + QPointer m_pAuxiliarySolverTask; /** @brief 启动求解时捕获的所属数据管理器,防止完成时活动窗口已经切换。 */ QPointer m_pSolverDataManager; /** @brief 启动求解时捕获的拟合窗口,只把结果合并回该窗口。 */ diff --git a/Include/nmNum/nmSubWxs/nmWxForecast.h b/Include/nmNum/nmSubWxs/nmWxForecast.h index a5ff15d5..c61b80e4 100644 --- a/Include/nmNum/nmSubWxs/nmWxForecast.h +++ b/Include/nmNum/nmSubWxs/nmWxForecast.h @@ -2,6 +2,7 @@ #define NMWXFORECAST_H #include "iDlgBase.h" +#include "nmSubWxs_global.h" #include #include #include @@ -15,20 +16,23 @@ #include #include #include "nmDataForecast.h" -#include "nmGUIComponentLineEdit.h" #include "nmDataAnalyzeManager.h" class nmCalculationDllPebiSolverTask; -class QProgressDialog; +class nmGUIComponentLineEdit; -class nmWxForecast : public iDlgBase +class NM_SUB_WXS_EXPORT nmWxForecast : public iDlgBase { Q_OBJECT public: explicit nmWxForecast(QWidget *parent = 0, nmDataAnalyzeManager* manager = 0); ~nmWxForecast(); + // 主窗口接管共享进度框后才启动计算。 + void startForecastCalculation(); signals: + void sigForecastTaskReady(nmWxForecast* dialog, nmCalculationDllPebiSolverTask* task); + void sigForecastCancelRequested(); // 计算完成后由所属分析主界面承载结果,输入窗口不持有结果窗口。 void sigForecastReady(const QString& wellName, const QVector& times, const QVector& pressures, const QVector& durations, @@ -37,6 +41,9 @@ signals: public slots: void reject(); +protected: + void closeEvent(QCloseEvent* event); + private slots: // Forecast option slots void onConstantPressureSelected(); @@ -89,7 +96,7 @@ private: nmDataForecast m_forecastData; QPointer m_manager; ///< 固定所属分析窗口,不跟随全局当前窗口。 nmCalculationDllPebiSolverTask* m_task; - QProgressDialog* m_progress; + bool m_calculationRunning; QString m_sourceSnapshotId; QString m_wellName; QVector m_durations; ///< 本次计算的分段时长(hr)。 diff --git a/Include/nmNum/nmSubWxs/nmWxNumericalDesign.h b/Include/nmNum/nmSubWxs/nmWxNumericalDesign.h index 0963728c..da4e3f7d 100644 --- a/Include/nmNum/nmSubWxs/nmWxNumericalDesign.h +++ b/Include/nmNum/nmSubWxs/nmWxNumericalDesign.h @@ -22,6 +22,7 @@ class nmDataDiagnostic; class nmDataReservoir; class nmDataAnalyzeManager; class nmWxSensitive; +class nmWxForecast; class nmCalculationDllPebiSolverTask; /** @@ -70,6 +71,8 @@ public: void activateCurrentInstance(); signals: + // 预测任务交给所属主窗口,复用普通求解进度框。 + void sigForecastTaskReady(nmWxForecast* dialog, nmCalculationDllPebiSolverTask* task); // 将预测结果转交给本面板所属的分析主界面。 void sigForecastReady(const QString& wellName, const QVector& times, const QVector& pressures, const QVector& durations, diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index 8b0d857c..39735bbc 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -85,6 +85,7 @@ #include "nmWxParaPropertyPebi.h" #include "nmWxNumericalDesign.h" #include "nmWxSensitive.h" +#include "nmWxForecast.h" #include "nmWxReservoirProperties.h" @@ -257,13 +258,13 @@ nmSubWndMain::~nmSubWndMain() s_pPendingSolverWindow = nullptr; } - // 批量任务由敏感性窗口释放;主窗口销毁前先解除回调并等待停止。 - if(!m_pSensitivityTask.isNull()) { - disconnect(m_pSensitivityTask.data(), nullptr, this, nullptr); - m_pSensitivityTask->requestCancel(); - m_pSensitivityTask->wait(); - if(s_pRunningSolverTask == m_pSensitivityTask) s_pRunningSolverTask = nullptr; - m_pSensitivityTask = nullptr; + // 敏感性和预测任务由参数窗口释放;主窗口销毁前先解除回调并等待停止。 + if(!m_pAuxiliarySolverTask.isNull()) { + disconnect(m_pAuxiliarySolverTask.data(), nullptr, this, nullptr); + m_pAuxiliarySolverTask->requestCancel(); + m_pAuxiliarySolverTask->wait(); + if(s_pRunningSolverTask == m_pAuxiliarySolverTask) s_pRunningSolverTask = nullptr; + m_pAuxiliarySolverTask = nullptr; } if (m_pProgressDlg) { delete m_pProgressDlg; @@ -1846,22 +1847,41 @@ void nmSubWndMain::showSolverProgress() } -void nmSubWndMain::onSensitivityTaskReady( - nmWxSensitive* dialog, nmCalculationDllPebiSolverTask* task) +bool nmSubWndMain::prepareAuxiliarySolverTask( + QWidget* dialog, nmCalculationDllPebiSolverTask* task) { // 与普通求解共用防重入状态,避免两个任务同时写同一进度框。 if(!s_pPendingSolverWindow.isNull() || !s_pRunningSolverTask.isNull() || m_bSolverResultCompletionPending) { QMessageBox::information(dialog, tr("solver error"), tr("task is running!")); - return; + return false; } - m_pSensitivityTask = task; + m_pAuxiliarySolverTask = task; s_pRunningSolverTask = task; m_bSolverCancelRequested = false; - // 整个批次保持同一个滚动进度,不订阅每组求解的阶段刷新信号。 // 先隐藏共享进度框,再让参数窗口的完成槽读取结果。 - connect(task, SIGNAL(finished()), this, SLOT(onSensitivityTaskFinished()), Qt::QueuedConnection); - connect(task, SIGNAL(destroyed(QObject*)), this, SLOT(onSensitivityTaskDestroyed())); + connect(task, SIGNAL(finished()), this, SLOT(onAuxiliarySolverTaskFinished()), Qt::QueuedConnection); + connect(task, SIGNAL(destroyed(QObject*)), this, SLOT(onAuxiliarySolverTaskDestroyed())); + showSolverProgress(); + m_pProgressDlg->setLabelText(nmCalculationDllPebiSolverTask::tr("Solving model...")); + return true; +} + +void nmSubWndMain::onForecastTaskReady( + nmWxForecast* dialog, nmCalculationDllPebiSolverTask* task) +{ + // 预测沿用共享进度框和停止路径,计算结果不进入正式成果提交。 + if(!prepareAuxiliarySolverTask(dialog, task)) return; + connect(dialog, SIGNAL(sigForecastCancelRequested()), + this, SLOT(onSolverCancelRequested()), Qt::UniqueConnection); + dialog->startForecastCalculation(); +} + +void nmSubWndMain::onSensitivityTaskReady( + nmWxSensitive* dialog, nmCalculationDllPebiSolverTask* task) +{ + // 整个批次保持同一个滚动进度,不订阅每组求解的阶段刷新信号。 + if(!prepareAuxiliarySolverTask(dialog, task)) return; connect(dialog, SIGNAL(sigSensitivityCancelRequested()), this, SLOT(onSolverCancelRequested()), Qt::UniqueConnection); // 坐标标题与单位跟随求解时的双对数窗口,后来切换模型不改变已有批次。 @@ -1876,27 +1896,25 @@ void nmSubWndMain::onSensitivityTaskReady( result->m_sAxisYUnit = source->getAxisOf(1)->getUnit(); } } - showSolverProgress(); - m_pProgressDlg->setLabelText(nmCalculationDllPebiSolverTask::tr("Solving model...")); dialog->startSensitivityCalculation(); } -void nmSubWndMain::onSensitivityTaskFinished() +void nmSubWndMain::onAuxiliarySolverTaskFinished() { // 完整退出线程后再释放进度框的占用,不进入普通求解的成果提交路径。 - if(m_pSensitivityTask.isNull() || sender() != m_pSensitivityTask.data()) return; - m_pSensitivityTask->wait(); - if(s_pRunningSolverTask == m_pSensitivityTask) s_pRunningSolverTask = nullptr; - disconnect(m_pSensitivityTask.data(), nullptr, this, nullptr); - m_pSensitivityTask = nullptr; + if(m_pAuxiliarySolverTask.isNull() || sender() != m_pAuxiliarySolverTask.data()) return; + m_pAuxiliarySolverTask->wait(); + if(s_pRunningSolverTask == m_pAuxiliarySolverTask) s_pRunningSolverTask = nullptr; + disconnect(m_pAuxiliarySolverTask.data(), nullptr, this, nullptr); + m_pAuxiliarySolverTask = nullptr; m_bSolverCancelRequested = false; m_pProgressDlg->hide(); } -void nmSubWndMain::onSensitivityTaskDestroyed() +void nmSubWndMain::onAuxiliarySolverTaskDestroyed() { // 参数面板提前销毁时,其析构函数已等待任务结束,解除共享进度框占用。 - m_pSensitivityTask = nullptr; + m_pAuxiliarySolverTask = nullptr; m_bSolverCancelRequested = false; m_pProgressDlg->hide(); } @@ -2722,8 +2740,8 @@ void nmSubWndMain::onSolverCancelRequested() } // 共享停止按钮只控制当前占用进度框的任务,DLL 返回后的处理各自保持原路径。 - nmCalculationDllPebiSolverTask* pRunningTask = !m_pSensitivityTask.isNull() - ? m_pSensitivityTask.data() : m_pSolverTask; + nmCalculationDllPebiSolverTask* pRunningTask = !m_pAuxiliarySolverTask.isNull() + ? m_pAuxiliarySolverTask.data() : m_pSolverTask; if(pRunningTask != nullptr && pRunningTask->isRunning()) { m_bSolverCancelRequested = true; pRunningTask->requestCancel(); @@ -3126,7 +3144,7 @@ bool nmSubWndMain::onConfirmClosing() { // 正常关闭入口在求解结束前保持当前成果和 DataManager 存活;应用退出等绕过 // 该确认流程的路径仍由析构函数 wait() 兜底。 - if(m_bSolverStartPending || !m_pSensitivityTask.isNull() || + if(m_bSolverStartPending || !m_pAuxiliarySolverTask.isNull() || (m_pSolverTask != nullptr && m_pSolverTask->isRunning())) { QMessageBox::information(this, tr("solver error"), tr("task is running!")); return false; diff --git a/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp b/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp index 0930c556..7b13e9be 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp @@ -481,6 +481,8 @@ void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager // 数值设计面板只发出操作请求,具体处理仍由主窗口完成。 connect(pAnalWx, SIGNAL(sigGenerateClicked()), pSubWndMain, SLOT(onGenerateButtonClicked())); + connect(pAnalWx, SIGNAL(sigForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*)), + pSubWndMain, SLOT(onForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*))); connect(pAnalWx, SIGNAL(sigForecastReady(QString,QVector,QVector,QVector,QVector)), pSubWndMain, SLOT(onShowForecastResult(QString,QVector,QVector,QVector,QVector))); connect(pAnalWx, SIGNAL(sigSensitivityTaskReady(nmWxSensitive*,nmCalculationDllPebiSolverTask*)), diff --git a/Src/nmNum/nmSubWxs/nmWxForecast.cpp b/Src/nmNum/nmSubWxs/nmWxForecast.cpp index d415bb05..66c8155c 100644 --- a/Src/nmNum/nmSubWxs/nmWxForecast.cpp +++ b/Src/nmNum/nmSubWxs/nmWxForecast.cpp @@ -5,16 +5,17 @@ #include #include #include -#include +#include #include #include "nmCalculationDllPebiSolverTask.h" +#include "nmGUIComponentLineEdit.h" #include "nmPebiResultSnapshot.h" nmWxForecast::nmWxForecast(QWidget *parent, nmDataAnalyzeManager* manager) : iDlgBase(parent) , m_manager(manager ? manager : nmDataAnalyzeManager::getCurrentInstance()) , m_task(NULL) - , m_progress(NULL) + , m_calculationRunning(false) , m_initialPressureComponent(nullptr) , m_abandonmentRateComponent(nullptr) , m_maximumRateComponent(nullptr) @@ -1039,16 +1040,23 @@ void nmWxForecast::onGenerateClicked() m_durations = durations; m_rates = rates; m_sourceSnapshotId = solved->getSnapshotId(); + // 与敏感性分析一样,主窗口接管成功后才启动,忙碌时释放未启动任务。 + emit sigForecastTaskReady(this, m_task); + if(!m_calculationRunning) { + delete m_task; + m_task = NULL; + } +} + +void nmWxForecast::startForecastCalculation() +{ + // 先连接主窗口的完成处理,再由本窗口读取结果和释放任务。 + connect(m_task, SIGNAL(finished()), this, SLOT(onForecastFinished()), Qt::QueuedConnection); + m_calculationRunning = true; m_parametersGroup->setEnabled(false); m_generateButton->setEnabled(false); - m_progress = new QProgressDialog(tr("Calculating forecast..."), tr("Cancel"), 0, 0, this); - m_progress->setWindowTitle(tr("Forecast")); - m_progress->setWindowModality(Qt::ApplicationModal); - m_progress->setMinimumDuration(0); - m_progress->setAutoClose(false); - connect(m_progress, SIGNAL(canceled()), this, SLOT(onCancelClicked())); - connect(m_task, SIGNAL(finished()), this, SLOT(onForecastFinished())); - m_progress->show(); + // 与敏感性分析一致,隐藏输入窗口,保留对象接收计算结果。 + hide(); m_task->start(); } @@ -1061,16 +1069,24 @@ void nmWxForecast::onCancelClicked() void nmWxForecast::reject() { // 正在求解时保留窗口,等线程结束后恢复输入,避免关闭操作阻塞界面。 - if(m_task) { - m_task->requestCancel(); - m_progress->setLabelText(tr("Stopping after the current solver call finishes...")); - m_progress->setCancelButton(NULL); - m_progress->show(); + if(m_calculationRunning) { + emit sigForecastCancelRequested(); return; } iDlgBase::reject(); } +void nmWxForecast::closeEvent(QCloseEvent* event) +{ + // 非模态进度框允许操作输入窗口,点关闭时同样请求停止并等待线程退出。 + if(m_calculationRunning) { + emit sigForecastCancelRequested(); + event->ignore(); + return; + } + iDlgBase::closeEvent(event); +} + bool nmWxForecast::readRateSchedule(QVector& durations, QVector& rates, QString& error) const { // 求解器统一使用小时及标准体积产量,完整空行跳过,半空行必须补全。 @@ -1107,7 +1123,7 @@ void nmWxForecast::onForecastFinished() { // finished 后才读取及销毁任务;输入变化或模型重新生成时丢弃本次预测。 m_task->wait(); - const bool cancelled = m_task->wasCancelled() || m_progress->wasCanceled(); + const bool cancelled = m_task->wasCancelled(); const bool current = m_manager && m_manager->isPebiResultSnapshotCurrent() && !m_task->hasInputChanged(m_manager) && m_manager->getPebiResultSnapshot()->getSnapshotId() == m_sourceSnapshotId; @@ -1116,8 +1132,7 @@ void nmWxForecast::onForecastFinished() const QVector > pressure = m_task->getRateForecastPressure(); delete m_task; m_task = NULL; - delete m_progress; - m_progress = NULL; + m_calculationRunning = false; m_parametersGroup->setEnabled(true); m_generateButton->setEnabled(true); if(cancelled) return; @@ -1131,7 +1146,7 @@ void nmWxForecast::onForecastFinished() void nmWxForecast::onModelChanged() { // 模型或输入改变时停止本次计算;已显示的结果由所属主界面清理。 - if(m_task) m_task->requestCancel(); + if(m_calculationRunning) emit sigForecastCancelRequested(); } void nmWxForecast::onPressureCellChanged(int row, int column) diff --git a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp index 9c4990f3..cfbcc73f 100644 --- a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp +++ b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp @@ -1449,6 +1449,8 @@ void nmWxNumericalDesign::onForecastClicked() updateSensitiveButtonEnabled(); if(!m_pForecastBtn->isEnabled()) return; nmWxForecast* dlg = new nmWxForecast(this, m_pDataManager); + connect(dlg, SIGNAL(sigForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*)), + this, SIGNAL(sigForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*))); connect(dlg, SIGNAL(sigForecastReady(QString,QVector,QVector,QVector,QVector)), this, SIGNAL(sigForecastReady(QString,QVector,QVector,QVector,QVector))); dlg->show();