diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 3f51c91b..c685584d 100644 Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index f776ef75..9cd4a640 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -8902,4 +8902,30 @@ Average pressure in contour: %2 MPa 模型已发生变化,请重新生成预测。 + + nmPebiGridDiagnostics + Only two-dimensional grid input is supported.当前仅支持二维网格输入。 + Grid spacing must be finite and greater than zero (current value: %1).网格尺度必须为有限正数,当前值:%1。 + The boundary must contain at least three edges.工区边界至少需要三条边。 + Boundary edge %1 has missing or non-finite coordinates.边界第 %1 条边的坐标缺失或不是有限数值。 + Boundary edge %1 has zero length. Check coincident points and coordinate rounding.边界第 %1 条边长度为零,请检查重合点及坐标取整造成的重合。 + Vertical well %1 has missing or non-finite coordinates/radius.第 %1 口直井的坐标或井半径缺失,或不是有限数值。 + Vertical well %1 has invalid radius %2 m; it must be greater than zero.第 %1 口直井的半径 %2 m 无效,井半径必须大于零。 + Vertical well %1: radius %2 m is too small to represent at coordinates (%3, %4) m.第 %1 口直井的半径 %2 m 相对于坐标(%3,%4)m 过小,已超出浮点数的可分辨精度。 + Grid input capture/preparation failed. Check the boundary and participating wells.网格输入读取或准备失败,请检查工区边界及参与计算的井。 + Unable to load HX_NWTM.dll (Windows error %1). Check the DLL and its runtime dependencies.无法加载 HX_NWTM.dll(Windows 错误码 %1),请检查网格库及其运行库依赖。 + HX_NWTM_GRID is missing from the DLL (Windows error %1). Check the library version.网格库缺少 HX_NWTM_GRID 入口(Windows 错误码 %1),请检查库版本。 + The grid algorithm encountered an access violation (0xC0000005).网格算法内部发生内存访问异常(0xC0000005)。 + The grid algorithm returned empty grid data. Check the license and geometry inputs.网格算法返回了空网格,请检查授权文件及几何输入。 + The generated grid cannot be displayed: invalid points, cell indices or degenerate cells.生成的网格无法显示,可能存在无效节点、单元索引错误或退化单元。 + Grid calculation raised an exception: %1网格计算发生异常:%1 + Grid calculation raised an unknown exception.网格计算发生未知异常。 + Unable to capture the grid boundary. Check that the analysis has a boundary.无法读取工区边界,请确认当前分析已设置边界。 + Unable to capture well geometry. Check participating well types and fracture endpoints.无法读取井几何信息,请检查参与计算的井类型及裂缝端点。 + Unable to capture fault/fracture geometry.无法读取断层或裂缝几何信息。 + Participating wells do not match the grid well list. Check the primary well and included wells.参与计算的井与网格井列表不匹配,请检查主井及包含井设置。 + The grid result was discarded because the analysis inputs changed. Generate the grid again.划分期间分析输入已改变,本次网格未提交,请重新划分网格。 + Grid generation failed: %1网格划分失败:%1 + Check whether the well radius is too small, and check grid spacing and geometry positions.请检查井半径是否过小、网格尺度及几何位置是否合适。 + diff --git a/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h b/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h index 34dad3f4..e479cd4e 100644 --- a/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h +++ b/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h @@ -103,6 +103,7 @@ struct nmPebiGridInputSnapshot bool m_bCaptured; ///< DataManager 值是否已在所属线程完整捕获。 bool m_bRequireSolverInput; ///< 是否还需组装产量、PVT 等求解输入。 bool m_bValid; ///< 后台场景准备是否完成,可否提交给网格 DLL。 + QString m_sCaptureFailure; ///< 输入捕获失败的具体步骤。 }; /** @@ -121,6 +122,7 @@ struct nmPebiGridResult HX_NWTM_GRID_OUTPUT2 m_oGridOutput2; ///< 网格 DLL 辅助输出。 vtkSmartPointer m_pUnstructuredGrid; ///< 局部构造的 VTK 网格。 bool m_bSucceeded; ///< DLL 和 VTK 转换是否全部成功。 + QString m_sFailureReason; ///< 可直接显示的失败原因及排查建议。 }; class NMCALCULATION_EXPORT nmCalculationPebiGrid @@ -155,6 +157,8 @@ public: nmPebiGridResult& oResult, bool bCreateUnstructuredGrid = true, const QAtomicInt* pCancelRequested = NULL); + /** @brief 向框架日志写入简短失败原因,不修改模型参数或已提交网格。 */ + void recordSnapshotFailure(nmPebiGridResult& oResult, const QString& reason); /** @brief 在主线程校验输入版本,并一次性提交井顺序、网格和单例缓存。 */ bool commitSnapshotResult(nmDataAnalyzeManager* pDataManager, const nmPebiGridInputSnapshot& oSnapshot, diff --git a/Include/nmNum/nmCalculation/nmCalculationPebiGridTask.h b/Include/nmNum/nmCalculation/nmCalculationPebiGridTask.h index 3dc3aba4..141d6ebc 100644 --- a/Include/nmNum/nmCalculation/nmCalculationPebiGridTask.h +++ b/Include/nmNum/nmCalculation/nmCalculationPebiGridTask.h @@ -20,6 +20,8 @@ public: ~nmCalculationPebiGridTask(); /** @brief 在主线程按版本提交后台局部结果;过期结果返回 false。 */ bool commitResult(nmDataAnalyzeManager* pDataManager); + /** @brief 返回已翻译的失败原因,供主线程在任务结束后显示。 */ + QString failureMessage() const; signals: /** @brief 无论成功或失败均通知主线程结束本轮网格任务。 */ diff --git a/Include/nmNum/nmSubWnd/nmSubWndGrid.h b/Include/nmNum/nmSubWnd/nmSubWndGrid.h index 449b1545..ca213328 100644 --- a/Include/nmNum/nmSubWnd/nmSubWndGrid.h +++ b/Include/nmNum/nmSubWnd/nmSubWndGrid.h @@ -64,7 +64,7 @@ class NM_SUB_WND_EXPORT nmSubWndGrid : public iSubWndBaseFit { /** @brief 从 DataManager 构建最新 ViewData 并刷新稳定的网格视图。 */ void showGridResult(); /** @brief 网格失败时恢复旧网格;没有旧网格则显示明确失败提示。 */ - void showGridFailure(); + void showGridFailure(const QString& reason = QString()); /** @brief 清除当前 ViewData,防止切换分析后继续显示旧分析网格。 */ void clearGridResultView(); /** @brief 从防抖开始登记建网活动,使同一分析的求解等待最终提交。 */ diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 8d8531bb..f5ac9617 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -2007,9 +2007,9 @@ bool nmCalculationDllPebiSolverTask::execPebiMode() oGridResult, bCreateUnstructuredGrid, pCancelRequested)) { - return isCancelRequested() - ? false - : fail(tr("Grid generation failed.")); + QString message = tr("Grid generation failed."); + if(!oGridResult.m_sFailureReason.isEmpty()) message += "\n" + oGridResult.m_sFailureReason; + return isCancelRequested() ? false : fail(message); } if(isCancelRequested()) { diff --git a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp index 8eae36a1..2171d3d3 100644 --- a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp @@ -12,6 +12,7 @@ #include #include "nmCalculationUtils.h" +#include "nmPebiGridDiagnostics.h" #include "zxLogInstance.h" #include "nmDataAnalyzeManager.h" @@ -1455,16 +1456,23 @@ bool nmCalculationPebiGrid::captureInputSnapshot( static_cast(vecEffectiveWells[nIndex].m_eMode)); } - // 第三步:把 Map 中的边界、有效井、断层和手工裂缝全部转成 DLL 值类型。 - if(!meshGenPebiBoundary(pDataManager, oSnapshot.m_oGridInput) || - !meshGenPebiWells(pDataManager, + // 第三步:分别记录输入捕获失败的步骤,避免界面只得到一个 false。 + if(!meshGenPebiBoundary(pDataManager, oSnapshot.m_oGridInput)) { + oSnapshot.m_sCaptureFailure = nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Unable to capture the grid boundary. Check that the analysis has a boundary.")); + return false; + } + if(!meshGenPebiWells(pDataManager, oSnapshot.m_oGridInput, oSnapshot.m_vecSolverWellOrder, - setEffectiveWellCodes) || - !meshGenPebiFault(pDataManager, oSnapshot.m_oGridInput) || + setEffectiveWellCodes)) { + oSnapshot.m_sCaptureFailure = nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Unable to capture well geometry. Check participating well types and fracture endpoints.")); + return false; + } + if(!meshGenPebiFault(pDataManager, oSnapshot.m_oGridInput) || !meshGenPebiCrack(pDataManager, oSnapshot.m_oGridInput, oSnapshot.m_vecSolverWellOrder)) { + oSnapshot.m_sCaptureFailure = nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Unable to capture fault/fracture geometry.")); return false; } @@ -1499,6 +1507,7 @@ bool nmCalculationPebiGrid::captureInputSnapshot( setOrderedWellCodes == setEffectiveWellCodes; if(!bSolverOrderValid) { qWarning() << "PEBI solver order does not match effective wells."; + oSnapshot.m_sCaptureFailure = nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Participating wells do not match the grid well list. Check the primary well and included wells.")); return false; } @@ -1822,20 +1831,42 @@ bool nmCalculationPebiGrid::buildPebiScene( return oSnapshot.m_bValid; } +void nmCalculationPebiGrid::recordSnapshotFailure( + nmPebiGridResult& oResult, + const QString& reason) +{ + oResult.m_bSucceeded = false; + oResult.m_sFailureReason = reason; + // 使用框架日志接口,由框架处理跨线程显示及保存;每次失败只写一条简短提示。 + zxLogInstance::getInstance()->writeLogW( + nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Grid generation failed: %1")).arg(reason.simplified())); +} + bool nmCalculationPebiGrid::calculateSnapshot( const nmPebiGridInputSnapshot& oSnapshot, nmPebiGridResult& oResult, bool bCreateUnstructuredGrid, const QAtomicInt* pCancelRequested) { + const nmPebiGridResult oEmptyResult; + oResult = oEmptyResult; nmInterruptibleMutexLocker oGridLocker; if(!oGridLocker.lock(&s_oPebiGridMutex, pCancelRequested)) { return false; } - const nmPebiGridResult oEmptyResult; - oResult = oEmptyResult; - if(!oSnapshot.m_bValid || isCancellationRequested(pCancelRequested)) { + if(isCancellationRequested(pCancelRequested)) { + return false; + } + if(!oSnapshot.m_bValid) { + recordSnapshotFailure(oResult, oSnapshot.m_sCaptureFailure.isEmpty() + ? nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Grid input capture/preparation failed. Check the boundary and participating wells.")) + : oSnapshot.m_sCaptureFailure); + return false; + } + const QString inputError = nmPebiGridDiagnostics::validate(oSnapshot.m_oGridInput); + if(!inputError.isEmpty()) { + recordSnapshotFailure(oResult, inputError); return false; } @@ -1847,8 +1878,9 @@ bool nmCalculationPebiGrid::calculateSnapshot( } HMODULE hGridModule = LoadLibrary(L"HX_NWTM.dll"); if(hGridModule == nullptr) { - qWarning() << "Failed to load HX_NWTM.dll. Error code:" - << GetLastError(); + const DWORD error = GetLastError(); + recordSnapshotFailure(oResult, + nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Unable to load HX_NWTM.dll (Windows error %1). Check the DLL and its runtime dependencies.")).arg(error)); return false; } @@ -1856,8 +1888,9 @@ bool nmCalculationPebiGrid::calculateSnapshot( reinterpret_cast( GetProcAddress(hGridModule, "HX_NWTM_GRID")); if(pfnGenerateGrid == nullptr) { - qWarning() << "Failed to resolve HX_NWTM grid functions. Error code:" - << GetLastError(); + const DWORD error = GetLastError(); + recordSnapshotFailure(oResult, + nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "HX_NWTM_GRID is missing from the DLL (Windows error %1). Check the library version.")).arg(error)); FreeLibrary(hGridModule); return false; } @@ -1871,6 +1904,11 @@ bool nmCalculationPebiGrid::calculateSnapshot( // 第二步:DLL 输出先落在局部结果,失败时不清空单例缓存和已有成果。 if(!invokePebiGridDllGuarded(pfnGenerateGrid, oSnapshot, oResult)) { // 本轮结果不再用于显示或求解,沿用现有失败通知保留旧成果。 + // 用户取消不是输入失败,不生成误导性的“半径过小”等排查提示。 + if(!isCancellationRequested(pCancelRequested)) + recordSnapshotFailure(oResult, + nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "The grid algorithm encountered an access violation (0xC0000005).")) + + "\n" + nmPebiGridDiagnostics::failureHint()); nmCalculationUtils::cleanupPebiGridDebugFiles(); FreeLibrary(hGridModule); hGridModule = nullptr; @@ -1888,6 +1926,16 @@ bool nmCalculationPebiGrid::calculateSnapshot( return false; } + if(oResult.m_oGridOutput1.PEBI_cell.p.empty() || + oResult.m_oGridOutput1.PEBI_cell.pindex.empty() || + oResult.m_oGridOutput2.Area.empty()) { + // 自动拟合跳过 VTK 转换时也必须检查空输出,不能把空网格当成功。 + recordSnapshotFailure(oResult, + nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "The grid algorithm returned empty grid data. Check the license and geometry inputs.")) + + "\n" + nmPebiGridDiagnostics::failureHint()); + return false; + } + // 自动拟合只需要 DLL 数组,可跳过 VTK 构造;网格任务必须生成完整 VTK。 if(bCreateUnstructuredGrid) { oResult.m_pUnstructuredGrid = @@ -1896,6 +1944,10 @@ bool nmCalculationPebiGrid::calculateSnapshot( pCancelRequested); if(oResult.m_pUnstructuredGrid == nullptr || oResult.m_pUnstructuredGrid->GetNumberOfCells() <= 0) { + if(!isCancellationRequested(pCancelRequested)) + recordSnapshotFailure(oResult, + nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "The generated grid cannot be displayed: invalid points, cell indices or degenerate cells.")) + + "\n" + nmPebiGridDiagnostics::failureHint()); return false; } } @@ -1903,18 +1955,20 @@ bool nmCalculationPebiGrid::calculateSnapshot( oResult.m_bSucceeded = !isCancellationRequested(pCancelRequested); } catch(const std::exception& e) { nmCalculationUtils::cleanupPebiGridDebugFiles(); - zxLogInstance::getInstance()->writeLogF( - QString("C++ Exception: %1").arg(e.what())); - logInputParameters(oSnapshot.m_oGridInput); + if(!isCancellationRequested(pCancelRequested)) + recordSnapshotFailure(oResult, + nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Grid calculation raised an exception: %1")).arg(QString::fromLocal8Bit(e.what())) + + "\n" + nmPebiGridDiagnostics::failureHint()); if(hGridModule != nullptr) { FreeLibrary(hGridModule); } return false; } catch(...) { nmCalculationUtils::cleanupPebiGridDebugFiles(); - zxLogInstance::getInstance()->writeLogF( - "SEH Exception Occurred"); - logInputParameters(oSnapshot.m_oGridInput); + if(!isCancellationRequested(pCancelRequested)) + recordSnapshotFailure(oResult, + nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Grid calculation raised an unknown exception.")) + + "\n" + nmPebiGridDiagnostics::failureHint()); if(hGridModule != nullptr) { FreeLibrary(hGridModule); } diff --git a/Src/nmNum/nmCalculation/nmCalculationPebiGridTask.cpp b/Src/nmNum/nmCalculation/nmCalculationPebiGridTask.cpp index a6b1d9bd..26281504 100644 --- a/Src/nmNum/nmCalculation/nmCalculationPebiGridTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationPebiGridTask.cpp @@ -1,6 +1,7 @@ #include "nmCalculationPebiGridTask.h" #include "nmCalculationPebiGrid.h" +#include "nmPebiGridDiagnostics.h" #include "nmDataAnalyzeManager.h" nmCalculationPebiGridTask::nmCalculationPebiGridTask( @@ -35,8 +36,8 @@ nmCalculationPebiGridTask::~nmCalculationPebiGridTask() void nmCalculationPebiGridTask::run() { // 第二步:后台只消费不可变快照,所有 DLL 输出和 VTK 数据先保存在任务内部。 - const bool success = m_bSnapshotValid && - nmCalculationPebiGrid::getInstance()->calculateSnapshot( + // 无效快照也交给统一入口记录捕获失败原因。 + const bool success = nmCalculationPebiGrid::getInstance()->calculateSnapshot( m_oInputSnapshot, m_oResult); // 完成通知可能引起成果窗口销毁,发信号前先解除 DataManager 使用权。 releaseDataManagerUse(); @@ -54,8 +55,16 @@ bool nmCalculationPebiGridTask::commitResult( return false; } - return nmCalculationPebiGrid::getInstance()->commitSnapshotResult( + const bool committed = nmCalculationPebiGrid::getInstance()->commitSnapshotResult( pDataManager, m_oInputSnapshot, m_oResult); + if(!committed) + m_oResult.m_sFailureReason = nmPebiGridDiagnostics::text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "The grid result was discarded because the analysis inputs changed. Generate the grid again.")); + return committed; +} + +QString nmCalculationPebiGridTask::failureMessage() const +{ + return m_oResult.m_sFailureReason; } void nmCalculationPebiGridTask::releaseDataManagerUse() diff --git a/Src/nmNum/nmSubWnd/nmSubWndGrid.cpp b/Src/nmNum/nmSubWnd/nmSubWndGrid.cpp index facaadc8..897424e8 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndGrid.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndGrid.cpp @@ -363,7 +363,7 @@ void nmSubWndGrid::onPebiGridGenerated(bool success) if(success) { showGridResult(); } else if(!hasPendingGridUpdate()) { - showGridFailure(); + showGridFailure(pCompletedTask->failureMessage()); } // 连续变化尚未完成时保持活动状态;求解只能在最后一次提交之后继续。 @@ -442,7 +442,7 @@ void nmSubWndGrid::clearGridResultView() m_pGridView->clearView(); } -void nmSubWndGrid::showGridFailure() +void nmSubWndGrid::showGridFailure(const QString& reason) { if(m_pMainLayout == nullptr || m_pGridView == nullptr) { return; @@ -453,9 +453,11 @@ void nmSubWndGrid::showGridFailure() m_pMainLayout->setCurrentWidget(m_pGridView); if(m_pGridView->hasViewData()) { m_pGridView->showFailure(tr( - "Grid generation failed. Current view is the last successful grid.")); + "Grid generation failed. Current view is the last successful grid.") + + (reason.isEmpty() ? QString() : "\n" + reason)); } else { - m_pGridView->showFailure(tr("Grid generation failed.")); + m_pGridView->showFailure(tr("Grid generation failed.") + + (reason.isEmpty() ? QString() : "\n" + reason)); } } diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index 5b23399f..87cbfd21 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -87,6 +87,7 @@ #include "nmWxNumericalDesign.h" #include "nmWxSensitive.h" #include "nmWxForecast.h" +#include "nmDialogPreviewGuard.h" #include "nmWxReservoirProperties.h" @@ -1875,6 +1876,7 @@ void nmSubWndMain::showSolverProgress() // 某些系统下,对话框右上角的关闭按钮可能仍存 // 使用 WindowFlags 彻底禁用关闭按钮 m_pProgressDlg->setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint); + new nmDialogPreviewGuard(m_pProgressDlg); m_pProgressDlg->setMinimumDuration(0); m_pProgressDlg->setAutoClose(false); m_pProgressDlg->setAutoReset(false); @@ -1981,6 +1983,7 @@ QDialog* nmSubWndMain::createResultDialog(const QString& name, const QString& ti dialog->setObjectName(name); dialog->setWindowTitle(title); dialog->setAttribute(Qt::WA_DeleteOnClose, false); + new nmDialogPreviewGuard(dialog); dialog->resize(900, 650); QVBoxLayout* layout = new QVBoxLayout(dialog); layout->setContentsMargins(0, 0, 0, 0); diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index f8fd803d..9da2e59e 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -1,5 +1,6 @@ #include "nmWxAutomaticFitting.h" #include "nmCalculationAutoFitPSO.h" +#include "nmDialogPreviewGuard.h" #include "nmCalculationAutoFitLM.h" #include "nmWxAutomaticfittingStart.h" #include "nmWxParameterProperty.h" @@ -318,6 +319,7 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett setupUI(); setWindowTitle(tr("Automatic fitting")); + new nmDialogPreviewGuard(this); setModal(true); // 恢复旧版默认尺寸,多井参数在右侧树表中滚动查看。 resize(sizeHint()); diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp index 3624112c..fdc4dc36 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp @@ -1,5 +1,6 @@ #include "nmWxAutomaticfittingStart.h" #include +#include "nmDialogPreviewGuard.h" #include #include #include @@ -398,6 +399,7 @@ nmWxAutomaticfittingStart::nmWxAutomaticfittingStart(QWidget *parent) // 设置窗口属性 setWindowTitle(tr("Auto Fitting Progress")); + new nmDialogPreviewGuard(this); setMinimumSize(1000, 650); setModal(false); } diff --git a/Src/nmNum/nmSubWxs/nmWxDFNLoadFromFile.cpp b/Src/nmNum/nmSubWxs/nmWxDFNLoadFromFile.cpp index 7976c740..1e2be44a 100644 --- a/Src/nmNum/nmSubWxs/nmWxDFNLoadFromFile.cpp +++ b/Src/nmNum/nmSubWxs/nmWxDFNLoadFromFile.cpp @@ -68,7 +68,7 @@ void nmWxDFNLoadFromFile::initUI() // 1. 数据源部分 m_pSourceGroup = new QGroupBox(tr("Data Source"), this); - m_pSourceGroup->setFixedSize(337, 106); + m_pSourceGroup->setMinimumWidth(337); // 使用垂直布局作为主布局(包含按钮行和文件路径行) QVBoxLayout* pSourceMainLayout = new QVBoxLayout(m_pSourceGroup); @@ -128,7 +128,7 @@ void nmWxDFNLoadFromFile::initUI() // 2. 分隔符部分 m_pSeparatorGroup = new QGroupBox(tr("Separators"), this); - m_pSeparatorGroup->setFixedSize(233,106); // 固定大小 + m_pSeparatorGroup->setMinimumWidth(233); // 使用复选框代替单选按钮 m_pSpaceCheck = new QCheckBox(tr("Space"), this); @@ -157,7 +157,7 @@ void nmWxDFNLoadFromFile::initUI() // 3. 信息提示 - 现在放在GroupBox中 m_pInfoGroup = new QGroupBox(tr("Info"), this); - m_pInfoGroup->setFixedSize(255,106); // 固定大小 + m_pInfoGroup->setMinimumWidth(255); m_pInfoLabel = new QLabel( tr("You need to assign at least 4 required types: X1, Y1, X2, Y2."), this); diff --git a/Src/nmNum/nmSubWxs/nmWxEditWellPlot.cpp b/Src/nmNum/nmSubWxs/nmWxEditWellPlot.cpp index 6adf2a5c..44eeddd7 100644 --- a/Src/nmNum/nmSubWxs/nmWxEditWellPlot.cpp +++ b/Src/nmNum/nmSubWxs/nmWxEditWellPlot.cpp @@ -313,7 +313,8 @@ QWidget* nmWxEditWellPlot::createRightPanel() QWidget* nmWxEditWellPlot::createBottomPanel() { m_pOverallBottomPanel = new QWidget(this); - m_pOverallBottomPanel->setFixedHeight(80); // 增加高度以容纳两行内容 + m_pOverallBottomPanel->setMinimumHeight(80); + m_pOverallBottomPanel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); QVBoxLayout* mainBottomVLayout = new QVBoxLayout(m_pOverallBottomPanel); mainBottomVLayout->setContentsMargins(5, 5, 5, 5); diff --git a/Src/nmNum/nmSubWxs/nmWxForecast.cpp b/Src/nmNum/nmSubWxs/nmWxForecast.cpp index 9414e893..fa63a9f1 100644 --- a/Src/nmNum/nmSubWxs/nmWxForecast.cpp +++ b/Src/nmNum/nmSubWxs/nmWxForecast.cpp @@ -1,5 +1,6 @@ #include "nmWxForecast.h" #include +#include "nmDialogPreviewGuard.h" #include #include #include @@ -40,6 +41,7 @@ nmWxForecast::nmWxForecast(QWidget *parent, nmDataAnalyzeManager* manager) setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); // 关闭时只隐藏,所属设计面板复用同一个输入窗口。 setAttribute(Qt::WA_DeleteOnClose, false); + new nmDialogPreviewGuard(this); PressureRow tempDefaults; m_currentDurationUnit = tempDefaults.duration.getUnit(); diff --git a/Src/nmNum/nmSubWxs/nmWxPebiGridViewWidget.cpp b/Src/nmNum/nmSubWxs/nmWxPebiGridViewWidget.cpp index b280df14..de9300b8 100644 --- a/Src/nmNum/nmSubWxs/nmWxPebiGridViewWidget.cpp +++ b/Src/nmNum/nmSubWxs/nmWxPebiGridViewWidget.cpp @@ -209,7 +209,7 @@ void nmWxPebiGridViewWidget::createUi() QVariant(static_cast( nmVTKMeshLayer::DisplayMode_SurfaceWithEdges))); m_pDisplayModeCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); - m_pDisplayModeCombo->setFixedHeight(24); + m_pDisplayModeCombo->setMinimumHeight(24); m_pDisplayModeCombo->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed); diff --git a/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp b/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp index ebba185a..9005ed70 100644 --- a/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp +++ b/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp @@ -467,7 +467,7 @@ void nmWxPebiResultControlBar::createUi() QVariant(static_cast( nmVTKMeshLayer::DisplayMode_SurfaceWithEdges))); m_pDisplayModeCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); - m_pDisplayModeCombo->setFixedHeight(24); + m_pDisplayModeCombo->setMinimumHeight(24); m_pDisplayModeCombo->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed); const int nInitialDisplayModeIndex = findItemByData( @@ -484,7 +484,7 @@ void nmWxPebiResultControlBar::createUi() m_pPropertyCombo = new QComboBox(this); m_pPropertyCombo->setMinimumWidth(PROPERTY_COMBO_MINIMUM_WIDTH); m_pPropertyCombo->setMaximumWidth(PROPERTY_COMBO_MAXIMUM_WIDTH); - m_pPropertyCombo->setFixedHeight(24); + m_pPropertyCombo->setMinimumHeight(24); m_pPropertyCombo->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed); m_pPropertyCombo->setToolTip(tr("Display property")); @@ -506,7 +506,7 @@ void nmWxPebiResultControlBar::createUi() TIME_COMBO_MINIMUM_CONTENT_LENGTH); m_pTimeCombo->setSizeAdjustPolicy( QComboBox::AdjustToMinimumContentsLengthWithIcon); - m_pTimeCombo->setFixedHeight(24); + m_pTimeCombo->setMinimumHeight(24); m_pTimeCombo->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed); m_pTimeCombo->setToolTip(tr("Result time")); diff --git a/Src/nmNum/nmSubWxs/nmWxPerforationClosing.cpp b/Src/nmNum/nmSubWxs/nmWxPerforationClosing.cpp index 135668ea..d6d10980 100644 --- a/Src/nmNum/nmSubWxs/nmWxPerforationClosing.cpp +++ b/Src/nmNum/nmSubWxs/nmWxPerforationClosing.cpp @@ -1,6 +1,7 @@ #include "nmWxPerforationClosing.h" #include +#include "nmDialogPreviewGuard.h" #include #include #include @@ -18,6 +19,7 @@ nmWxPerforationClosing::nmWxPerforationClosing(nmDataWellBase* pWellData, QWidge : iDlgBase(parent) { setWindowTitle(tr("Perforation closing intervals")); + new nmDialogPreviewGuard(this); setMinimumSize(500, 625); // 初始化成员变量 @@ -72,8 +74,10 @@ void nmWxPerforationClosing::initUI() // 创建工具栏容器 QWidget* toolbarContainer = new QWidget(this); - toolbarContainer->setFixedHeight(70); + toolbarContainer->setMinimumHeight(70); + toolbarContainer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); QHBoxLayout* toolbarLayout = new QHBoxLayout(toolbarContainer); + toolbarLayout->setContentsMargins(0, 0, 0, 0); // 创建Add按钮组合 QWidget* addButtonContainer = new QWidget(this); @@ -91,7 +95,8 @@ void nmWxPerforationClosing::initUI() addContainerLayout->addWidget(m_pAddBtn, 0, Qt::AlignHCenter); addContainerLayout->addWidget(addTextLabel); - addButtonContainer->setFixedSize(100, 70); + addButtonContainer->setMinimumWidth(100); + addButtonContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); toolbarLayout->addWidget(addButtonContainer); // 创建Remove按钮组合 @@ -110,7 +115,8 @@ void nmWxPerforationClosing::initUI() removeContainerLayout->addWidget(m_pRemoveBtn, 0, Qt::AlignHCenter); removeContainerLayout->addWidget(removeTextLabel); - removeButtonContainer->setFixedSize(100, 70); + removeButtonContainer->setMinimumWidth(100); + removeButtonContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); toolbarLayout->addWidget(removeButtonContainer); // 添加复选框和时间单位控件组 diff --git a/Src/nmNum/nmSubWxs/nmWxPostprocessingAnimationWidget.cpp b/Src/nmNum/nmSubWxs/nmWxPostprocessingAnimationWidget.cpp index 3c253736..52698420 100644 --- a/Src/nmNum/nmSubWxs/nmWxPostprocessingAnimationWidget.cpp +++ b/Src/nmNum/nmSubWxs/nmWxPostprocessingAnimationWidget.cpp @@ -1,6 +1,7 @@ #include "nmWxPostprocessingAnimationWidget.h" #include // 用于调试输出 #include // 用于 std::sort 排序算法 +#include "nmDialogPreviewGuard.h" // UI 相关的 Qt 头文件 #include @@ -134,6 +135,7 @@ nmContourResultDialog::nmContourResultDialog(QWidget *parent) { setWindowTitle(tr("Contour Pressure")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + new nmDialogPreviewGuard(this); m_resultLabel = new QLabel(this); m_resultLabel->setWordWrap(true); diff --git a/Src/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.cpp b/Src/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.cpp index 899d14dd..d7fb1383 100644 --- a/Src/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.cpp +++ b/Src/nmNum/nmSubWxs/nmWxReservoirPropertiesEditor.cpp @@ -212,7 +212,7 @@ QWidget* nmWxReservoirPropertiesEditor::createRibbonGroup( // 分组标题使用主题灰色和下划线,保持框架参考工具栏的层级表现。 QLabel* pCaptionLabel = new QLabel(sCaption, pGroupWidget); pCaptionLabel->setAlignment(Qt::AlignCenter); - pCaptionLabel->setFixedHeight(16); + pCaptionLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); QFont oCaptionFont = pCaptionLabel->font(); oCaptionFont.setUnderline(true); pCaptionLabel->setFont(oCaptionFont); diff --git a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp index 1a2ed1f8..b18ed03b 100644 --- a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp +++ b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp @@ -1,6 +1,7 @@ #include "nmWxSensitive.h" #include "nmDataAnalyzeManager.h" +#include "nmDialogPreviewGuard.h" #include "nmWxAutomaticFitting.h" #include "nmParameterField.h" #include "ZxBaseUtil.h" @@ -503,6 +504,7 @@ nmWxSensitivityShow::nmWxSensitivityShow( // 沿用原 Results 的白色表格及按钮布局,勾选在 Apply 或 OK 后生效。 setAttribute(Qt::WA_DeleteOnClose, false); setObjectName("nmSensitivityShowDialog"); + new nmDialogPreviewGuard(this); resize(700, 380); QVBoxLayout* layout = new QVBoxLayout(this); QTableView* table = m_table = new QTableView(this); @@ -598,6 +600,7 @@ nmWxSensitivityHistogramOptions::nmWxSensitivityHistogramOptions( // 复用运行表格;第四种在误差后追加拟合参数,选择变量后按原有 Apply/OK 流程生效。 setAttribute(Qt::WA_DeleteOnClose, false); setObjectName("nmHistogramOptions"); + new nmDialogPreviewGuard(this); setWindowTitle(tr("Histogram options")); resize(720, 400); QVBoxLayout* layout = new QVBoxLayout(this); @@ -680,6 +683,7 @@ nmWxSensitivityScatter::nmWxSensitivityScatter( setObjectName("nmSensitivityScatterPlot"); setAttribute(Qt::WA_DeleteOnClose, false); setWindowTitle(tr("Scatter plot")); + new nmDialogPreviewGuard(this); resize(850, 550); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(8, 8, 8, 8); @@ -1435,6 +1439,7 @@ nmWxSensitive::nmWxSensitive(QWidget *parent, nmDataAnalyzeManager* manager) { // 关闭只隐藏,由所属分析统一销毁,重新打开保留当前输入。 setAttribute(Qt::WA_DeleteOnClose, false); + new nmDialogPreviewGuard(this); // 显式绑定所属分析窗口,后续切换窗口不会改变配置保存目标。 if (m_pDataManager) m_sensitiveData = m_pDataManager->getSensitiveDataCopy(); refreshModelValues(); @@ -2933,6 +2938,7 @@ void nmWxSensitive::onPreviewClicked() dialog->setAttribute(Qt::WA_DeleteOnClose, false); dialog->setWindowTitle(tr("Sensitivity Runs")); dialog->setWindowFlags(dialog->windowFlags() & ~Qt::WindowContextHelpButtonHint); + new nmDialogPreviewGuard(dialog); dialog->setMinimumSize(420, 360); dialog->resize(470, 650); QVBoxLayout* layout = new QVBoxLayout(dialog); diff --git a/Src/nmNum/nmSubWxs/nmWxTimeDependentSkin.cpp b/Src/nmNum/nmSubWxs/nmWxTimeDependentSkin.cpp index 8fcfa394..bcd36785 100644 --- a/Src/nmNum/nmSubWxs/nmWxTimeDependentSkin.cpp +++ b/Src/nmNum/nmSubWxs/nmWxTimeDependentSkin.cpp @@ -1,5 +1,6 @@ #include "nmWxTimeDependentSkin.h" #include "nmWxPerforationClosing.h" +#include "nmDialogPreviewGuard.h" #include "nmWxPressFlowChartWidget.h" #include "nmWxChartWidget.h" @@ -150,6 +151,7 @@ nmWxTimeDependentSkin::nmWxTimeDependentSkin(nmDataWellBase* pWellData, QWidget } setWindowTitle(tr("Time Dependent Skin")); + new nmDialogPreviewGuard(this); setMinimumSize(1150, 800); // 初始化UI @@ -270,8 +272,11 @@ void nmWxTimeDependentSkin::initUI() // 创建工具栏容器 QWidget* toolbarContainer = new QWidget(this); - toolbarContainer->setFixedHeight(70); // 固定工具栏高度 + toolbarContainer->setMinimumHeight(70); + toolbarContainer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); m_pToolbarLayout = new QHBoxLayout(toolbarContainer); // 保存布局指针 + // 高度由图标、文字和字体共同决定,避免外层边距裁掉按钮下方文字。 + m_pToolbarLayout->setContentsMargins(0, 0, 0, 0); // 创建按钮并设置统一样式 auto createButton = [&](QPushButton*& button, const QString & iconPath, const QString & text) { @@ -294,8 +299,9 @@ void nmWxTimeDependentSkin::initUI() containerLayout->addWidget(button, 0, Qt::AlignHCenter); containerLayout->addWidget(textLabel); - // 设置容器的大小策略和固定高度 - buttonContainer->setFixedSize(100, 70); // 固定宽度和高度 + // 保留常规尺寸,大字体时允许布局按文字所需空间扩展。 + buttonContainer->setMinimumWidth(100); + buttonContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); m_pToolbarLayout->addWidget(buttonContainer); }; @@ -329,9 +335,9 @@ void nmWxTimeDependentSkin::initUI() checkboxLayout->addWidget(m_pShowDatesCheckBox); checkboxLayout->addWidget(m_pAddRateDependentCheckBox); - // 设置复选框容器的合适宽度以容纳三个复选框 - checkboxContainer->setFixedWidth(450); // 增加宽度以容纳水平排列的复选框 - checkboxContainer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + // 大字体或翻译文字较长时,按实际内容扩展复选框区域。 + checkboxContainer->setMinimumWidth(450); + checkboxContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); // 将复选框容器添加到工具栏布局 m_pToolbarLayout->addWidget(checkboxContainer); @@ -1295,7 +1301,8 @@ void nmWxTimeDependentSkin::createChartModeBtn() filterLayout->addWidget(m_pFilterBtn, 0, Qt::AlignHCenter); filterLayout->addWidget(filterLabel); - m_pFilterContainer->setFixedSize(100, 70); + m_pFilterContainer->setMinimumWidth(100); + m_pFilterContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); m_pFilterContainer->setVisible(false); m_pToolbarLayout->insertWidget(1, m_pFilterContainer); @@ -1316,7 +1323,8 @@ void nmWxTimeDependentSkin::createChartModeBtn() resetFilterLayout->addWidget(m_pResetFilterBtn, 0, Qt::AlignHCenter); resetFilterLayout->addWidget(resetFilterLabel); - m_pResetFilterContainer->setFixedSize(100, 70); + m_pResetFilterContainer->setMinimumWidth(100); + m_pResetFilterContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); m_pResetFilterContainer->setVisible(false); m_pToolbarLayout->insertWidget(2, m_pResetFilterContainer); @@ -1336,7 +1344,8 @@ void nmWxTimeDependentSkin::createChartModeBtn() resetLinesLayout->addWidget(m_pResetLinesBtn, 0, Qt::AlignHCenter); resetLinesLayout->addWidget(resetLinesLabel); - m_pResetLinesContainer->setFixedSize(100, 70); + m_pResetLinesContainer->setMinimumWidth(100); + m_pResetLinesContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); m_pResetLinesContainer->setVisible(false); m_pToolbarLayout->insertWidget(3, m_pResetLinesContainer); @@ -1458,7 +1467,8 @@ void nmWxTimeDependentSkin::createEditBoxes() editLayout->addLayout(dSkinLayout); // 设置容器大小 - editContainer->setFixedSize(180, 60); + editContainer->setMinimumWidth(180); + editContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); editContainer->setStyleSheet( "QWidget {" "background-color: transparent;" diff --git a/Src/nmNum/nmSubWxs/nmWxWellEditor.cpp b/Src/nmNum/nmSubWxs/nmWxWellEditor.cpp index aab9c1f9..95f386b9 100644 --- a/Src/nmNum/nmSubWxs/nmWxWellEditor.cpp +++ b/Src/nmNum/nmSubWxs/nmWxWellEditor.cpp @@ -584,7 +584,7 @@ QWidget* nmWxWellEditor::createRibbonGroup( QLabel* pCaptionLabel = new QLabel(sCaption, pGroupWidget); pCaptionLabel->setAlignment(Qt::AlignCenter); - pCaptionLabel->setFixedHeight(16); + pCaptionLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); QFont oCaptionFont = pCaptionLabel->font(); oCaptionFont.setUnderline(true); pCaptionLabel->setFont(oCaptionFont); diff --git a/Src/nmNum/nmSubWxs/nmWxWellboreStorageCalculator.cpp b/Src/nmNum/nmSubWxs/nmWxWellboreStorageCalculator.cpp index 1c17d8b0..1f77b98a 100644 --- a/Src/nmNum/nmSubWxs/nmWxWellboreStorageCalculator.cpp +++ b/Src/nmNum/nmSubWxs/nmWxWellboreStorageCalculator.cpp @@ -39,8 +39,8 @@ nmWxWellboreStorageCalculator::~nmWxWellboreStorageCalculator() void nmWxWellboreStorageCalculator::initUI() { setWindowTitle(tr("Wellbore Storage Calculator")); - // 设置一个稍大的固定窗口尺寸,以容纳所有组件并保持对齐 - setFixedSize(500, 320); + // 保留默认尺寸,允许窗口随字体和布局所需空间扩展。 + setMinimumSize(500, 320); // 1. 创建模式选择的单选按钮和分组 m_pClosedChamberRadio = new QRadioButton(tr("Closed chamber")); diff --git a/Src/nmNum/nmSubWxs/nmWxWellboreStorageCalculatorTubingData.cpp b/Src/nmNum/nmSubWxs/nmWxWellboreStorageCalculatorTubingData.cpp index 179424dc..dc8cc5d3 100644 --- a/Src/nmNum/nmSubWxs/nmWxWellboreStorageCalculatorTubingData.cpp +++ b/Src/nmNum/nmSubWxs/nmWxWellboreStorageCalculatorTubingData.cpp @@ -1,6 +1,7 @@ #include "nmWxWellboreStorageCalculatorTubingData.h" #include +#include "nmDialogPreviewGuard.h" #include #include #include @@ -11,6 +12,7 @@ nmWxWellboreStorageCalculatorTubingData::nmWxWellboreStorageCalculatorTubingData(QWidget *parent) : iDlgBase(parent) { + new nmDialogPreviewGuard(this); this->initUI(); this->initConnections(); this->setupTableData(); @@ -40,7 +42,7 @@ double nmWxWellboreStorageCalculatorTubingData::getSelectedTubingID() const void nmWxWellboreStorageCalculatorTubingData::initUI() { setWindowTitle(tr("Tubing geometry data")); - setFixedSize(480, 450); + setMinimumSize(480, 450); QVBoxLayout* mainLayout = new QVBoxLayout(this); diff --git a/Src/nmNum/nmSubWxs/nmWxWellboreStorageCalculatorVolume.cpp b/Src/nmNum/nmSubWxs/nmWxWellboreStorageCalculatorVolume.cpp index 3c08d388..c66ef0ce 100644 --- a/Src/nmNum/nmSubWxs/nmWxWellboreStorageCalculatorVolume.cpp +++ b/Src/nmNum/nmSubWxs/nmWxWellboreStorageCalculatorVolume.cpp @@ -1,6 +1,7 @@ #include "nmWxWellboreStorageCalculatorVolume.h" #include +#include "nmDialogPreviewGuard.h" #include #include #include @@ -13,6 +14,7 @@ nmWxWellboreStorageCalculatorVolume::nmWxWellboreStorageCalculatorVolume(QWidget *parent) : iDlgBase(parent) { + new nmDialogPreviewGuard(this); this->initUI(); // 初始化用户界面 this->initConnections(); // 连接信号和槽 } @@ -24,7 +26,7 @@ nmWxWellboreStorageCalculatorVolume::~nmWxWellboreStorageCalculatorVolume() void nmWxWellboreStorageCalculatorVolume::initUI() { setWindowTitle(tr("Tested chamber volume")); - setFixedSize(450, 350); + setMinimumSize(450, 350); // 1. 主布局 QVBoxLayout* mainLayout = new QVBoxLayout(this);