feat: 新增 nmAttrRegistry 实现 UI↔Data 双向参数同步

- 新增 nmAttrRegistry 类:属性注册表,name→nmDataAttribute* 映射
  - regAttr/clear/collect 接口
  - setValue(slot) 接收 UI→Data 方向
  - sigAttrChanged(signal) 通知 Data→UI 方向
  - m_bSyncing 防循环,onAttrDestroyed 自动清理

- nmDataAnalyzeManager 持有 nmAttrRegistry 实例
  - 构造函数创建,析构函数末尾 delete
  - createReservoir() 中注册 h/Pi/K/phi/Cti/Cf/Soi/Swi

- nmGridRowUtils 新增 sigParaChanged 信号
  - onActiveItemChanged 中同步上一个编辑项的最终值
  - 编辑完成(失焦)时才同步,避免中间态非法值污染数据层

- nmWxParaPropertyPebi 新增 slotSetParaValue public slot
  - 转发调用 setParaValue,供 nmAttrRegistry::sigAttrChanged 连接

- nmSubWndUtils::fillNmDockWxs 简化
  - 用 pReg->collect() 替代逐个 getter 调用
  - 建立 UI→Data 和 Data→UI 双向 connect
feature/nm-para-property-20260615
lh 3 weeks ago
parent dc22f56c9c
commit 341b390860

@ -0,0 +1,62 @@
#pragma once
#include <QObject>
#include <QMap>
#include <QString>
#include <QStringList>
#include <QVariant>
#include "nmData_global.h"
class nmDataAttribute;
/// @brief 属性注册表,作为 UI 与数据层的双向同步中介
/// @note 每个分析窗口(nmDataAnalyzeManager)持有一个实例
/// name 对齐 XML ParaItem Name如 "Pi", "K", "h"
/// nmDataReservoir/nmSubWndUtils 不需任何修改
class NM_DATA_EXPORT nmAttrRegistry : public QObject
{
Q_OBJECT
public:
explicit nmAttrRegistry(QObject* parent = nullptr);
~nmAttrRegistry();
/// @brief 注册属性name 对齐 XML ParaItem Name
/// @param name 参数名(如 "Pi", "K", "h"
/// @param pAttr nmDataAttribute 指针(不拥有所有权)
void regAttr(QString name, nmDataAttribute* pAttr);
/// @brief 清除所有注册(重建数据对象时调用)
void clear();
/// @brief 批量读取属性值(初始化面板用)
/// @param names 参数名列表
/// @return name→value 映射,未找到的跳过
QMap<QString, QVariant> collect(const QStringList& names) const;
public slots:
/// @brief UI→Data 方向:按 name dispatch 到 nmDataAttribute::setValue
/// @note 内部设置 m_bSyncing=true 防循环
void setValue(QString name, QVariant value);
signals:
/// @brief Data→UI 方向:属性变更通知,携带 name 和新值
void sigAttrChanged(QString name, QVariant value);
private slots:
/// @brief 监听 nmDataAttribute::sigValueChanged用 sender() 反查 name
void onAttrValueChanged();
/// @brief 监听 nmDataAttribute::destroyed自动清理映射
void onAttrDestroyed(QObject* pObj);
private:
QMap<QString, nmDataAttribute*> m_nameToPtr; // name → ptr
QMap<nmDataAttribute*, QString> m_ptrToName; // ptr → namesender 反查用)
bool m_bSyncing; // 防循环标记
};

@ -86,6 +86,7 @@ class ZxDataWell;
class iSubWndFitting; class iSubWndFitting;
class nmDataTimeStepSetting; class nmDataTimeStepSetting;
class nmDataSensitive; class nmDataSensitive;
class nmAttrRegistry;
class NM_DATA_EXPORT nmDataAnalyzeManager : public ZxDataObjectBin class NM_DATA_EXPORT nmDataAnalyzeManager : public ZxDataObjectBin
{ {
@ -541,6 +542,9 @@ class NM_DATA_EXPORT nmDataAnalyzeManager : public ZxDataObjectBin
void setLicensePath(const QString& licensePath); void setLicensePath(const QString& licensePath);
QString getLicensePath() const; QString getLicensePath() const;
// 获取属性注册表
nmAttrRegistry* getAttrRegistry() const;
signals: signals:
void dataChanged(); void dataChanged();
@ -641,6 +645,9 @@ class NM_DATA_EXPORT nmDataAnalyzeManager : public ZxDataObjectBin
// 时间步设置数据 // 时间步设置数据
nmDataTimeStepSetting* m_pTimeStep; nmDataTimeStepSetting* m_pTimeStep;
// 属性注册表(普通指针,手动管理,析构函数末尾 delete
nmAttrRegistry* m_pAttrRegistry;
// 许可证路径 // 许可证路径
QString m_licensePath; QString m_licensePath;

@ -141,14 +141,20 @@ protected:
public slots: public slots:
/// @brief 参数值改变前的预览回调
/// @param o1 主要数据m_oRawValue
/// @param o2 辅助数据(如下拉填充内容)
virtual void slotValuePrevChanged(QVariant& o1, QVariant& o2) override;
/// @brief 获取指定参数的当前值(供子项内部查询用) /// @brief 获取指定参数的当前值(供子项内部查询用)
/// @param sPara 参数名称 /// @param sPara 参数名称
/// @param o 输出值 /// @param o 输出值
/// @param bOk 是否成功获取 /// @param bOk 是否成功获取
virtual void slotGetValueOf(QString sPara, QVariant& o, bool& bOk) override; virtual void slotGetValueOf(QString sPara, QVariant& o, bool& bOk) override;
/// @brief 激活项切换回调(用户离开当前编辑项时触发)
/// @note 在此时同步上一个编辑项的最终值,避免中间态非法值污染数据层
virtual void onActiveItemChanged(iGridRowItem* p) override;
signals:
/// @brief 参数值变更信号(简化版,供 nmAttrRegistry 同步用)
/// @param name 参数名(来自当前激活项的 Name
/// @param value 变更后的值
void sigParaChanged(QString name, QVariant value);
}; };

@ -55,6 +55,10 @@ public slots:
// help msg // help msg
virtual void slotHelpMsg(QString& s); virtual void slotHelpMsg(QString& s);
/// @brief 设置参数值的槽(供 nmAttrRegistry::sigAttrChanged 连接用)
/// @note setParaValue 是 virtual 函数在 public 区,不能直接作为 SLOT
void slotSetParaValue(QString sPara, QVariant o);
protected: protected:
virtual void paintEvent(QPaintEvent*); virtual void paintEvent(QPaintEvent*);
@ -70,4 +74,11 @@ protected:
// Help // Help
QTextEdit* m_pHelpBox; QTextEdit* m_pHelpBox;
signals:
/// @brief 参数值变更信号(转发 nmGridRowUtils::sigParaChanged
/// @param name 参数名
/// @param value 变更后的值
void sigParaChanged(QString name, QVariant value);
}; };

@ -0,0 +1,125 @@
#include "nmAttrRegistry.h"
#include "nmDataAttribute.h"
nmAttrRegistry::nmAttrRegistry(QObject* parent)
: QObject(parent)
, m_bSyncing(false)
{
}
nmAttrRegistry::~nmAttrRegistry()
{
// 映射中的指针不拥有所有权,不需 delete
// nmDataAttribute 的 destroyed 信号已自动清理映射
m_nameToPtr.clear();
m_ptrToName.clear();
}
void nmAttrRegistry::regAttr(QString name, nmDataAttribute* pAttr)
{
if (name.isEmpty() || pAttr == nullptr) {
return;
}
// 如果同名已注册,先断开旧连接
if (m_nameToPtr.contains(name)) {
nmDataAttribute* pOld = m_nameToPtr[name];
if (pOld != nullptr && pOld != pAttr) {
disconnect(pOld, SIGNAL(sigValueChanged()), this, SLOT(onAttrValueChanged()));
disconnect(pOld, SIGNAL(destroyed(QObject*)), this, SLOT(onAttrDestroyed(QObject*)));
m_ptrToName.remove(pOld);
}
}
// 如果同指针已注册(换名),先清理旧名
if (m_ptrToName.contains(pAttr)) {
QString oldName = m_ptrToName[pAttr];
if (oldName != name) {
m_nameToPtr.remove(oldName);
}
}
m_nameToPtr[name] = pAttr;
m_ptrToName[pAttr] = name;
// 连接 nmDataAttribute 的信号
connect(pAttr, SIGNAL(sigValueChanged()),
this, SLOT(onAttrValueChanged()));
connect(pAttr, SIGNAL(destroyed(QObject*)),
this, SLOT(onAttrDestroyed(QObject*)));
}
void nmAttrRegistry::clear()
{
// 断开所有 nmDataAttribute 的信号连接
QMap<nmDataAttribute*, QString>::iterator iter = m_ptrToName.begin();
for (; iter != m_ptrToName.end(); iter++) {
nmDataAttribute* pAttr = iter.key();
if (pAttr != nullptr) {
disconnect(pAttr, SIGNAL(sigValueChanged()), this, SLOT(onAttrValueChanged()));
disconnect(pAttr, SIGNAL(destroyed(QObject*)), this, SLOT(onAttrDestroyed(QObject*)));
}
}
m_nameToPtr.clear();
m_ptrToName.clear();
}
QMap<QString, QVariant> nmAttrRegistry::collect(const QStringList& names) const
{
QMap<QString, QVariant> mapResult;
for (int i = 0; i < names.size(); i++) {
QString name = names[i];
nmDataAttribute* pAttr = m_nameToPtr.value(name, nullptr);
if (pAttr != nullptr) {
mapResult[name] = pAttr->getValue();
}
}
return mapResult;
}
void nmAttrRegistry::setValue(QString name, QVariant value)
{
nmDataAttribute* pAttr = m_nameToPtr.value(name, nullptr);
if (pAttr == nullptr) {
return;
}
m_bSyncing = true;
pAttr->setValue(value); // → nmDataAttribute 发 sigValueChanged
// → onAttrValueChanged 检测 m_bSyncing=true → 跳过 sigAttrChanged
m_bSyncing = false;
}
void nmAttrRegistry::onAttrValueChanged()
{
nmDataAttribute* pAttr = qobject_cast<nmDataAttribute*>(sender());
if (pAttr == nullptr) {
return;
}
// 防循环UI→Data 链路中跳过
if (m_bSyncing) {
return;
}
QString name = m_ptrToName.value(pAttr, "");
if (name.isEmpty()) {
return;
}
emit sigAttrChanged(name, pAttr->getValue());
}
void nmAttrRegistry::onAttrDestroyed(QObject* pObj)
{
// nmDataAttribute 析构时发出 destroyed此时对象已部分销毁
// 只用指针值做 key不访问任何成员
nmDataAttribute* pAttr = static_cast<nmDataAttribute*>(pObj);
QString name = m_ptrToName.value(pAttr, "");
if (!name.isEmpty()) {
m_nameToPtr.remove(name);
}
m_ptrToName.remove(pAttr);
}

@ -1,5 +1,7 @@
#include "nmDataAnalyzeManager.h" #include "nmDataAnalyzeManager.h"
#include "nmAttrRegistry.h"
#include "nmDataAnalyzeContext.h" #include "nmDataAnalyzeContext.h"
#include "nmDataAnalyzeContextProvider.h" #include "nmDataAnalyzeContextProvider.h"
#include "nmDataPlotContext.h" #include "nmDataPlotContext.h"
@ -390,6 +392,10 @@ ZX_DEFINE_DYNAMIC(DataAnalyzeManager, nmDataAnalyzeManager)
//m_pFlowSegmentData = nullptr; //m_pFlowSegmentData = nullptr;
m_pForecastData = nullptr; m_pForecastData = nullptr;
m_pSensitiveData = nullptr; m_pSensitiveData = nullptr;
// 属性注册表
m_pAttrRegistry = new nmAttrRegistry();
m_backgroundImageInfo.bIsVisible = false; m_backgroundImageInfo.bIsVisible = false;
m_dScalarRangeP[0] = 0.0; m_dScalarRangeP[0] = 0.0;
m_dScalarRangeP[1] = 0.0; m_dScalarRangeP[1] = 0.0;
@ -476,6 +482,9 @@ nmDataAnalyzeManager::~nmDataAnalyzeManager()
delete m_pTimeStep; delete m_pTimeStep;
m_pTimeStep = nullptr; m_pTimeStep = nullptr;
// 最后 delete 属性注册表确保数据对象先销毁nmAttrRegistry 能接收 destroyed 信号清理映射)
delete m_pAttrRegistry;
m_pAttrRegistry = nullptr;
} }
QMap<iSubWndFitting*, nmDataAnalyzeManager*> nmDataAnalyzeManager::s_mapDataAnalManager; QMap<iSubWndFitting*, nmDataAnalyzeManager*> nmDataAnalyzeManager::s_mapDataAnalManager;
@ -1289,6 +1298,17 @@ void nmDataAnalyzeManager::createReservoir()
} }
m_reservoirData = new nmDataReservoir; m_reservoirData = new nmDataReservoir;
// 注册储层属性到 nmAttrRegistryname 对齐 XML ParaItem Name
m_pAttrRegistry->clear();
m_pAttrRegistry->regAttr("h", &m_reservoirData->getThickness());
m_pAttrRegistry->regAttr("Pi", &m_reservoirData->getInitialPressure());
m_pAttrRegistry->regAttr("K", &m_reservoirData->getPermeability());
m_pAttrRegistry->regAttr("phi", &m_reservoirData->getPorosity());
m_pAttrRegistry->regAttr("Cti", &m_reservoirData->getCt());
m_pAttrRegistry->regAttr("Cf", &m_reservoirData->getCf());
m_pAttrRegistry->regAttr("Soi", &m_reservoirData->getSoi());
m_pAttrRegistry->regAttr("Swi", &m_reservoirData->getSwi());
// 按相态填充PVT字段和压缩系数 // 按相态填充PVT字段和压缩系数
populateReservoirByPhase(m_reservoirData, eType, mapPvtValues, dCf); populateReservoirByPhase(m_reservoirData, eType, mapPvtValues, dCf);
@ -1319,6 +1339,11 @@ nmDataReservoir* nmDataAnalyzeManager::getReservoirData() const
return m_reservoirData; return m_reservoirData;
} }
nmAttrRegistry* nmDataAnalyzeManager::getAttrRegistry() const
{
return m_pAttrRegistry;
}
nmDataReservoir nmDataAnalyzeManager::getReservoirDataCopy() const nmDataReservoir nmDataAnalyzeManager::getReservoirDataCopy() const
{ {
if(m_reservoirData) { if(m_reservoirData) {

@ -141,9 +141,15 @@ void nmGridRowUtils::bindItems()
// 槽函数 // 槽函数
// ================================================================ // ================================================================
void nmGridRowUtils::slotValuePrevChanged(QVariant& o1, QVariant& o2) void nmGridRowUtils::onActiveItemChanged(iGridRowItem* p)
{ {
iGridRowUtils::slotValuePrevChanged(o1, o2); // 基类调用前m_pItemSel 仍是上一个编辑项
// 在此时同步其最终值,避免中间态非法值污染数据层
if (m_pItemSel && m_pItemSel != p) {
emit sigParaChanged(m_pItemSel->getName(), m_pItemSel->getCurValue());
}
iGridRowUtils::onActiveItemChanged(p);
} }
void nmGridRowUtils::slotGetValueOf(QString sPara, QVariant& o, bool& bOk) void nmGridRowUtils::slotGetValueOf(QString sPara, QVariant& o, bool& bOk)

@ -39,7 +39,7 @@
#include "ZxObjBase.h" #include "ZxObjBase.h"
#include "nmDataAnalyzeManager.h" #include "nmDataAnalyzeManager.h"
#include "nmDataReservoir.h" #include "nmAttrRegistry.h"
#include "iRibbonXmlTab.h" #include "iRibbonXmlTab.h"
#include "nmWxParameterProperty.h" #include "nmWxParameterProperty.h"
#include "nmWxParaPropertyPebi.h" #include "nmWxParaPropertyPebi.h"
@ -592,22 +592,24 @@ bool nmSubWndUtils::fillNmDockWxs(iSubWnd* pSubWnd)
pParaWx->refreshUIs(listParas); pParaWx->refreshUIs(listParas);
// 从数据中心批量读取储层参数初始化UI // 通过 nmAttrRegistry 批量读取储层参数初始化UI替代逐个 getter 调用)
nmDataReservoir* pRes = pDataManager->getReservoirData(); nmAttrRegistry* pReg = pDataManager->getAttrRegistry();
if (pRes) if (pReg != nullptr)
{ {
QMap<QString, QVariant> mapInit; QMap<QString, QVariant> mapInit = pReg->collect(listParas);
mapInit["h"] = pRes->getThickness().getValue();
mapInit["Pi"] = pRes->getInitialPressure().getValue();
mapInit["K"] = pRes->getPermeability().getValue();
mapInit["phi"] = pRes->getPorosity().getValue();
mapInit["Cti"] = pRes->getCt().getValue();
mapInit["Cf"] = pRes->getCf().getValue();
mapInit["Soi"] = pRes->getSoi().getValue();
mapInit["Swi"] = pRes->getSwi().getValue();
pParaWx->restoreOldParas(mapInit); pParaWx->restoreOldParas(mapInit);
} }
// 双向同步连接UI→Data
if (pReg != nullptr)
{
connect(pParaWx, SIGNAL(sigParaChanged(QString, QVariant)),
pReg, SLOT(setValue(QString, QVariant)));
// Data→UI
connect(pReg, SIGNAL(sigAttrChanged(QString, QVariant)),
pParaWx, SLOT(slotSetParaValue(QString, QVariant)));
}
// 建立求解调用连接 // 建立求解调用连接
connect(pAnalWx, SIGNAL(sigGenerateClicked()), connect(pAnalWx, SIGNAL(sigGenerateClicked()),
pSubWndMain, SLOT(onGenerateButtonClicked())); pSubWndMain, SLOT(onGenerateButtonClicked()));

@ -110,6 +110,9 @@ void nmWxParaPropertyPebi::refreshUIs(QStringList& listParas, bool bReserveOlds
m_pGridItemUtils->setParaSrcTag(s_Nm_Serie); m_pGridItemUtils->setParaSrcTag(s_Nm_Serie);
connect(m_pGridItemUtils, SIGNAL(sigHelpMsg(QString&)), connect(m_pGridItemUtils, SIGNAL(sigHelpMsg(QString&)),
this, SLOT(slotHelpMsg(QString&))); this, SLOT(slotHelpMsg(QString&)));
// 信号转发nmGridRowUtils::sigParaChanged → nmWxParaPropertyPebi::sigParaChanged
connect(m_pGridItemUtils, SIGNAL(sigParaChanged(QString, QVariant)),
this, SIGNAL(sigParaChanged(QString, QVariant)));
m_pMainLayout->addWidget(m_pGridItemUtils); m_pMainLayout->addWidget(m_pGridItemUtils);
m_pGridItemUtils->buildRowUtils(listParas, false, this); m_pGridItemUtils->buildRowUtils(listParas, false, this);
@ -170,6 +173,11 @@ bool nmWxParaPropertyPebi::setParaValue(QString sPara, QVariant o)
return false; return false;
} }
void nmWxParaPropertyPebi::slotSetParaValue(QString sPara, QVariant o)
{
setParaValue(sPara, o);
}
bool nmWxParaPropertyPebi::getParaValue(QString sPara, QVariant& o) bool nmWxParaPropertyPebi::getParaValue(QString sPara, QVariant& o)
{ {
if (nullptr != m_pGridItemUtils) if (nullptr != m_pGridItemUtils)

Loading…
Cancel
Save