From d1102ecc07ce3f0472fd04ce0faf74bf3bebfa64 Mon Sep 17 00:00:00 2001 From: lh <2334563547@qq.com> Date: Wed, 28 Oct 2026 09:52:18 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=A1=A5=E4=BA=A4=E7=BD=91=E6=A0=BC?= =?UTF-8?q?=E8=AF=8A=E6=96=AD=E4=B8=8E=E7=AA=97=E5=8F=A3=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E8=BE=85=E5=8A=A9=E5=A4=B4=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 补充遗漏的 nmPebiGridDiagnostics.h 和 nmDialogPreviewGuard.h --- .../nmCalculation/nmPebiGridDiagnostics.h | 75 ++++++++++++++++ Include/nmNum/nmSubWxs/nmDialogPreviewGuard.h | 85 +++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 Include/nmNum/nmCalculation/nmPebiGridDiagnostics.h create mode 100644 Include/nmNum/nmSubWxs/nmDialogPreviewGuard.h diff --git a/Include/nmNum/nmCalculation/nmPebiGridDiagnostics.h b/Include/nmNum/nmCalculation/nmPebiGridDiagnostics.h new file mode 100644 index 00000000..d028a44e --- /dev/null +++ b/Include/nmNum/nmCalculation/nmPebiGridDiagnostics.h @@ -0,0 +1,75 @@ +#ifndef NM_PEBI_GRID_DIAGNOSTICS_H +#define NM_PEBI_GRID_DIAGNOSTICS_H + +#include "pch.h" +#include +#include +#include + +// 网格失败提示辅助函数:检查输入并生成可翻译的简短原因,不修改输入、不写日志。 +// 调用方负责将返回的原因交给框架日志或界面显示。 +namespace nmPebiGridDiagnostics { + +// 翻译用户可见提示;原文仍保留翻译标记,便于更新中英文语言文件。 +inline QString text(const char* source) +{ + // 本文件不是 QObject 类,用固定翻译上下文与界面的 tr() 保持相同翻译机制。 + // 调用处用 QT_TRANSLATE_NOOP 标记原文,供 Qt 4.8 的 lupdate 提取;本函数负责运行时翻译。 + return QCoreApplication::translate("nmPebiGridDiagnostics", source); +} + +// 保留双精度数值的有效信息,避免很小的井半径在提示中被格式化成零。 +inline QString number(double value) +{ + return QString::number(value, 'g', 17); +} + +// 只拦截可以确定无效的输入,不人为规定“井半径必须大于 0.1 m”。 +// 同一真实井径可能因坐标量级、网格尺度不同而成功或失败,不能擅自修改物理参数。 +// 返回首个发现的问题;空字符串仅表示通过这些基础检查,不保证算法一定成功。 +inline QString validate(const HX_NWTM_GRID_INPUT& input) +{ + // 先检查网格维数和尺度,避免将不支持的维数或无效步长传给算法库。 + if(input.D != 2) + return text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Only two-dimensional grid input is supported.")); + if(!qIsFinite(input.GridControl) || input.GridControl <= 0.0) + return text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Grid spacing must be finite and greater than zero (current value: %1).")) + .arg(number(input.GridControl)); + if(input.Boundary.size() < 3) + return text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "The boundary must contain at least three edges.")); + // 每条边至少包含起点和终点的平面坐标;这里不检查整个边界是否闭合或自交。 + for(size_t i = 0; i < input.Boundary.size(); ++i) { + const dVec1& edge = input.Boundary[i]; + if(edge.size() < 4 || !qIsFinite(edge[0]) || !qIsFinite(edge[1]) || + !qIsFinite(edge[2]) || !qIsFinite(edge[3])) + return text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Boundary edge %1 has missing or non-finite coordinates.")).arg(i + 1); + // 捕获边界时当前程序会对坐标取整,原本相邻的点也可能因此重合。 + if(edge[0] == edge[2] && edge[1] == edge[3]) + return text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Boundary edge %1 has zero length. Check coincident points and coordinate rounding.")).arg(i + 1); + } + // 直井数据前三项依次为横坐标、纵坐标、井半径;与算法库约定一致,单位为米。 + for(size_t i = 0; i < input.VerticalWell.size(); ++i) { + const dVec1& well = input.VerticalWell[i]; + if(well.size() < 3 || !qIsFinite(well[0]) || !qIsFinite(well[1]) || !qIsFinite(well[2])) + return text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Vertical well %1 has missing or non-finite coordinates/radius.")).arg(i + 1); + if(well[2] <= 0.0) + return text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Vertical well %1 has invalid radius %2 m; it must be greater than zero.")) + .arg(i + 1).arg(number(well[2])); + // 这里判断实际浮点分辨能力:加上半径仍得到同一坐标,才能确定半径无法表达。 + // 普通正井径即使偏小也允许提交 DLL,避免把算法敏感性误判为输入错误。 + if(well[0] + well[2] == well[0] || well[1] + well[2] == well[1]) + return text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Vertical well %1: radius %2 m is too small to represent at coordinates (%3, %4) m.")) + .arg(i + 1).arg(number(well[2])).arg(number(well[0])).arg(number(well[1])); + } + return QString(); +} + +// 算法失败且无法确定根因时附加通用建议,不把建议当成已经确认的失败原因。 +inline QString failureHint() +{ + // DLL 没有给出具体原因时,只提供排查方向,不将小井径直接判定为错误。 + return text(QT_TRANSLATE_NOOP("nmPebiGridDiagnostics", "Check whether the well radius is too small, and check grid spacing and geometry positions.")); +} +} + +#endif diff --git a/Include/nmNum/nmSubWxs/nmDialogPreviewGuard.h b/Include/nmNum/nmSubWxs/nmDialogPreviewGuard.h new file mode 100644 index 00000000..73d8b71a --- /dev/null +++ b/Include/nmNum/nmSubWxs/nmDialogPreviewGuard.h @@ -0,0 +1,85 @@ +#ifndef NM_DIALOG_PREVIEW_GUARD_H +#define NM_DIALOG_PREVIEW_GUARD_H + +#include +#include +#include +#include + +// 修复对话框隐藏后仍残留在系统任务栏预览中的问题。 +// 旧版界面库即使设置了系统隐藏属性,也可能继续复用上一次的窗口预览图像。 +// 等隐藏事件处理完成后,通过界面库重建原生窗口,释放旧句柄对应的预览。 +// 对话框对象、子控件、用户输入和计算任务的归属保持不变。 +class nmDialogPreviewGuard : public QObject +{ +public: + // 由被保护窗口持有本对象,窗口销毁时自动释放;调用时窗口必须有效。 + explicit nmDialogPreviewGuard(QWidget* window) + : QObject(window), m_window(window), m_timerId(0), m_rebuilding(false) + { + window->installEventFilter(this); + } + +protected: + // 只监听目标窗口的显式隐藏和重新显示;重建过程产生的事件不重复处理。 + bool eventFilter(QObject* object, QEvent* event) + { +#ifdef Q_OS_WIN + if(object != m_window || m_rebuilding) + return QObject::eventFilter(object, event); + switch(event->type()) { + case QEvent::Hide: + // 最小化引起的隐藏来自系统,必须保留正常预览,不能重建其原生窗口。 + // 使用零间隔定时器延后处理,避免在隐藏事件内部直接重建窗口。 + if(!event->spontaneous() && !m_timerId && m_window->isWindow() && + m_window->internalWinId()) m_timerId = startTimer(0); + break; + case QEvent::Show: + // 窗口可能在延后处理前就被重新打开,此时取消清理,避免影响可见窗口。 + cancelPendingReset(); + break; + default: + break; + } +#endif + return QObject::eventFilter(object, event); + } + + // 隐藏完成后执行一次清理;再次检查窗口状态,避免处理已销毁或重新显示的窗口。 + void timerEvent(QTimerEvent* event) + { + if(event->timerId() != m_timerId) { + QObject::timerEvent(event); + return; + } + cancelPendingReset(); + if(m_window.isNull() || m_window->isVisible() || !m_window->isWindow() || + !m_window->internalWinId()) return; + + m_rebuilding = true; + // 重建原生窗口会触发事件,期间本对象可能被释放,用弱指针检查是否仍然存活。 + QPointer alive(this); + const QRect geometry = m_window->geometry(); + // 在当前界面库的系统后端,即使父对象和标志不变,双参数调用也会重建原生窗口。 + // 子控件的原生父窗口由界面库同步调整,不能绕过界面库直接销毁系统句柄。 + m_window->setParent(m_window->parentWidget(), m_window->windowFlags()); + if(alive.isNull()) return; + // 恢复原有位置和尺寸,确保下次打开时窗口布局不因句柄重建而改变。 + if(!m_window.isNull()) m_window->setGeometry(geometry); + if(!alive.isNull()) m_rebuilding = false; + } + +private: + // 清除尚未执行的重建请求,使定时处理保持为单次操作。 + void cancelPendingReset() + { + if(m_timerId) killTimer(m_timerId); + m_timerId = 0; + } + + QPointer m_window; // 被保护窗口的弱引用,窗口销毁后自动置空。 + int m_timerId; // 等待执行的定时器编号,为零表示没有待处理请求。 + bool m_rebuilding; // 重建期间屏蔽重复事件,避免递归触发清理。 +}; + +#endif