From 7d0e72dec241df4b9ebcc98fdfdfdce5943b5f9b Mon Sep 17 00:00:00 2001 From: gaozheng Date: Tue, 23 Jun 2026 17:35:54 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E5=85=A8=E5=B1=80=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E7=A9=BA=E6=80=81=E4=BC=98=E5=8C=96=20EmptyAwareCombo?= =?UTF-8?q?Box(=E5=8D=A0=E4=BD=8D+=E6=9A=82=E6=97=A0=E6=95=B0=E6=8D=AE,?= =?UTF-8?q?=E5=AF=B9=E9=BD=90Arco)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 下拉无数据时原为空白框+空弹窗,不优雅。新增统一空态感知下拉对齐原版 Arco ASelect: - EmptyAwareComboBox(QComboBox 子类):showPopup 无真实项时临时插禁用灰色「暂无数据」 条(关闭即移除,不污染取值),仍可点开;占位经 setPlaceholderText(currentIndex=-1)显示 - FormKit 加 comboBox(placeholder) 统一入口 - 全局替换全部 37 处 new QComboBox:7 处数据驱动给占位(白化文件/异常类型/反演模型/ 导入类型脚本/导出模板),30 处仅换类保留自动选首项语义(逐处判断,不破坏取值) build all 绿,341/341。 --- src/app/AnomalySaveDialog.cpp | 4 +- src/app/CMakeLists.txt | 1 + src/app/ColorGradientDialog.cpp | 6 +- src/app/ContourLevelDialog.cpp | 4 +- src/app/ContourLineDialog.cpp | 4 +- src/app/EmptyAwareComboBox.cpp | 65 +++++++++++++++++++ src/app/EmptyAwareComboBox.hpp | 34 ++++++++++ src/app/ExportDatasetDialog.cpp | 3 +- src/app/FormKit.cpp | 9 +++ src/app/FormKit.hpp | 7 ++ src/app/ImportDatasetDialog.cpp | 5 +- src/app/ObjectFormDialog.cpp | 4 +- src/app/ProjectListDialog.cpp | 4 +- src/app/SettingsDialog.cpp | 6 +- src/app/VolumeParamsDialog.cpp | 4 +- src/app/panels/DescriptionPanel.cpp | 8 ++- src/app/panels/DynamicFormEditor.cpp | 4 +- src/app/panels/chart/AutoAnnotationDialog.cpp | 3 +- .../panels/chart/ExceptionDetailDialog.cpp | 4 +- src/app/panels/chart/ExceptionDialog.cpp | 7 +- src/app/panels/chart/ExceptionTextDialog.cpp | 4 +- src/app/panels/chart/ExceptionTypeDialog.cpp | 10 +-- src/app/panels/chart/FilterDialog.cpp | 6 +- src/app/panels/chart/GridWizardDialog.cpp | 4 +- src/app/panels/chart/InversionFormDialog.cpp | 8 ++- src/app/panels/chart/RawDataChartView.cpp | 12 ++-- src/app/panels/chart/TablePager.cpp | 4 +- src/app/panels/chart/WhiteningDialog.cpp | 8 ++- src/app/panels/columns/Column2DDataset.cpp | 6 +- src/app/panels/columns/Column3DAnalysis.cpp | 4 +- src/app/panels/columns/Column3DDataset.cpp | 6 +- 31 files changed, 213 insertions(+), 45 deletions(-) create mode 100644 src/app/EmptyAwareComboBox.cpp create mode 100644 src/app/EmptyAwareComboBox.hpp diff --git a/src/app/AnomalySaveDialog.cpp b/src/app/AnomalySaveDialog.cpp index 574aceb..e4279be 100644 --- a/src/app/AnomalySaveDialog.cpp +++ b/src/app/AnomalySaveDialog.cpp @@ -1,6 +1,8 @@ #include "AnomalySaveDialog.hpp" #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -39,7 +41,7 @@ AnomalySaveDialog::AnomalySaveDialog(const QString& screenshotPath, int shotW, i formkit::capField(name_); form->addRow(formkit::editLabel(QStringLiteral("名称")), name_); - type_ = new QComboBox(); + type_ = new EmptyAwareComboBox(); for (const auto& t : kMockTypes) type_->addItem(QString::fromUtf8(t.label), QString::fromUtf8(t.id)); formkit::capField(type_); diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index fcac400..32fa716 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -22,6 +22,7 @@ add_executable(geopro_desktop WIN32 main.cpp Theme.cpp FormKit.cpp + EmptyAwareComboBox.cpp TopBar.cpp ToastOverlay.cpp Glyphs.cpp diff --git a/src/app/ColorGradientDialog.cpp b/src/app/ColorGradientDialog.cpp index 406aa97..b8d3125 100644 --- a/src/app/ColorGradientDialog.cpp +++ b/src/app/ColorGradientDialog.cpp @@ -5,6 +5,8 @@ #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -105,7 +107,7 @@ ColorGradientDialog::ColorGradientDialog(const std::vector& init, double m int rowIdx = 0; // 配色方案(下拉带预览色条)。 - schemeCombo_ = new QComboBox(this); + schemeCombo_ = new EmptyAwareComboBox(this); schemeCombo_->setIconSize(QSize(100, 16)); { auto* cell = new QHBoxLayout(); @@ -118,7 +120,7 @@ ColorGradientDialog::ColorGradientDialog(const std::vector& init, double m { auto* cell = new QHBoxLayout(); cell->addWidget(formkit::editLabel(QStringLiteral("分布方式:"))); - auto* distCombo = new QComboBox(this); + auto* distCombo = new EmptyAwareComboBox(this); distCombo->addItem(QStringLiteral("线性"), QStringLiteral("linear")); distCombo->addItem(QStringLiteral("对数"), QStringLiteral("log")); distCombo->setCurrentIndex(0); diff --git a/src/app/ContourLevelDialog.cpp b/src/app/ContourLevelDialog.cpp index ae223eb..afd7a25 100644 --- a/src/app/ContourLevelDialog.cpp +++ b/src/app/ContourLevelDialog.cpp @@ -3,6 +3,8 @@ #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -38,7 +40,7 @@ ContourLevelDialog::ContourLevelDialog(const ContourLevelParams& init, double or new QLabel(QStringLiteral("%1 ~ %2").arg(originMin_).arg(originMax_))); // 分层方式。 - methodCombo_ = new QComboBox(this); + methodCombo_ = new EmptyAwareComboBox(this); methodCombo_->addItem(QStringLiteral("一般的"), 0); methodCombo_->addItem(QStringLiteral("对数"), 1); methodCombo_->addItem(QStringLiteral("等积"), 2); diff --git a/src/app/ContourLineDialog.cpp b/src/app/ContourLineDialog.cpp index c7cc5f3..7402df1 100644 --- a/src/app/ContourLineDialog.cpp +++ b/src/app/ContourLineDialog.cpp @@ -4,6 +4,8 @@ #include #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -34,7 +36,7 @@ ContourLineDialog::ContourLineDialog(const ContourLineConfig& init, QWidget* par auto* form = formkit::makeEditForm(); // 复刻 contourLine.vue:选项顺序「虚线」在前、「实线」在后。 - lineTypeCombo_ = new QComboBox(this); + lineTypeCombo_ = new EmptyAwareComboBox(this); lineTypeCombo_->addItem(QStringLiteral("- - - - - - - - -"), true); // dashed lineTypeCombo_->addItem(QStringLiteral("——————"), false); // solid lineTypeCombo_->setCurrentIndex(cfg_.dashed ? 0 : 1); diff --git a/src/app/EmptyAwareComboBox.cpp b/src/app/EmptyAwareComboBox.cpp new file mode 100644 index 0000000..5477b37 --- /dev/null +++ b/src/app/EmptyAwareComboBox.cpp @@ -0,0 +1,65 @@ +#include "EmptyAwareComboBox.hpp" + +#include +#include +#include + +#include "Theme.hpp" + +namespace geopro::app { + +namespace { +// 临时「暂无数据」项用 UserRole 打标,便于 realItemCount/hidePopup 精准识别与移除。 +constexpr int kEmptyHintRole = Qt::UserRole + 9001; +const char* kEmptyHintText = "暂无数据"; +} // namespace + +EmptyAwareComboBox::EmptyAwareComboBox(QWidget* parent) : QComboBox(parent) { + // 不在此强设 currentIndex。占位语义由 setPlaceholderText 的「添加项前调用」时机决定: + // - 经 formkit::comboBox(placeholder,...) 建的(占位在添加项前设好)→ 即便后续异步加入 + // 数据项,Qt 仍维持 currentIndex=-1 显示占位,不自动选首项(Arco ASelect 占位语义)。 + // - 不带占位直接 new 的静态下拉 → addItem 首项后 Qt 自动选中索引 0(保持既有默认选中行为)。 +} + +int EmptyAwareComboBox::realItemCount() const { + int n = 0; + for (int i = 0; i < count(); ++i) { + // 排除临时「暂无数据」占位项。 + if (itemData(i, kEmptyHintRole).toBool()) continue; + // 排除不可选项(禁用 / NoItemFlags),它们不构成「真实可选数据」。 + if (!(itemData(i, Qt::UserRole - 1).value() & Qt::ItemIsSelectable)) + continue; + ++n; + } + return n; +} + +void EmptyAwareComboBox::showPopup() { + // 无真实可选项时,临时插入一条灰色禁用「暂无数据」,让弹窗不再空白(同 Arco)。 + if (realItemCount() == 0 && !emptyHintInserted_) { + addItem(QString::fromUtf8(kEmptyHintText)); + const int idx = count() - 1; + setItemData(idx, true, kEmptyHintRole); // 打标,关闭时按标移除 + setItemData(idx, Qt::AlignCenter, Qt::TextAlignmentRole); + // 灰色禁用观感:取项目 token 色(text/disabled),与 Arco 空态一致。 + setItemData(idx, tokenColor("text/disabled"), Qt::ForegroundRole); + if (auto* m = qobject_cast(model())) { + if (auto* it = m->item(idx)) it->setFlags(Qt::NoItemFlags); // 不可选/不可聚焦 + } + emptyHintInserted_ = true; + } + QComboBox::showPopup(); +} + +void EmptyAwareComboBox::hidePopup() { + QComboBox::hidePopup(); + // 移除临时「暂无数据」项,恢复纯净数据(取值/计数不受污染)。临时项是禁用不可选的, + // 用户无法选中它,故移除后 currentIndex 自然维持原值(占位组合仍为 -1,无需强设)。 + if (emptyHintInserted_) { + for (int i = count() - 1; i >= 0; --i) + if (itemData(i, kEmptyHintRole).toBool()) removeItem(i); + emptyHintInserted_ = false; + } +} + +} // namespace geopro::app diff --git a/src/app/EmptyAwareComboBox.hpp b/src/app/EmptyAwareComboBox.hpp new file mode 100644 index 0000000..b97c2c8 --- /dev/null +++ b/src/app/EmptyAwareComboBox.hpp @@ -0,0 +1,34 @@ +#pragma once + +// EmptyAwareComboBox —— 空态感知下拉框(对齐原版 web Arco ASelect 观感)。 +// +// 历史问题:数据驱动的下拉(白化文件、异常类型、反演模型……)异步加载,加载前/无数据时: +// 1) 裸 QComboBox 会自动选中首项或留空,无「请选择 X」灰色占位提示; +// 2) 弹窗里一片空白,用户不知是「加载中」还是「真的没有」。 +// Arco ASelect 的标准行为是:未选时显示灰色占位文案,无数据时弹窗显示一条灰色「暂无数据」。 +// 本类把这两点收敛到唯一实现,全局通过 formkit::comboBox(...) 建下拉即自动获得一致观感。 + +#include + +namespace geopro::app { + +// QComboBox 子类:未选→占位文案(Qt6 自带 placeholderText,currentIndex=-1 时显示); +// 无真实可选项→点开弹窗时临时插入一条禁用的灰色「暂无数据」,弹窗关闭后移除(不污染数据/取值)。 +class EmptyAwareComboBox : public QComboBox { + Q_OBJECT +public: + explicit EmptyAwareComboBox(QWidget* parent = nullptr); + + // 点开弹窗:若无真实可选项(排除占位/禁用项),临时插入一条禁用「暂无数据」再弹出。 + void showPopup() override; + // 关闭弹窗:移除临时「暂无数据」项,保证数据/取值不被污染。 + void hidePopup() override; + +private: + // 统计「真实可选条目数」:排除占位项与不可选(NoItemFlags/禁用)项。 + int realItemCount() const; + + bool emptyHintInserted_ = false; // 当前是否插入了临时「暂无数据」项 +}; + +} // namespace geopro::app diff --git a/src/app/ExportDatasetDialog.cpp b/src/app/ExportDatasetDialog.cpp index 36ab24c..8311c2d 100644 --- a/src/app/ExportDatasetDialog.cpp +++ b/src/app/ExportDatasetDialog.cpp @@ -35,7 +35,8 @@ ExportDatasetDialog::ExportDatasetDialog(geopro::data::IAsyncProjectRepository& auto* cardLay = formkit::cardBody(card); auto* fl = formkit::makeEditForm(); - templateCombo_ = new QComboBox(this); + // 空态感知下拉:数据驱动(异步 loadTemplates),未选显占位、无数据弹「暂无数据」。 + templateCombo_ = formkit::comboBox(QStringLiteral("请选择导出模板"), this); formkit::capField(templateCombo_); fl->addRow(formkit::editLabel(QStringLiteral("导出模板")), templateCombo_); cardLay->addLayout(fl); diff --git a/src/app/FormKit.cpp b/src/app/FormKit.cpp index f20809e..d12e93d 100644 --- a/src/app/FormKit.cpp +++ b/src/app/FormKit.cpp @@ -11,11 +11,20 @@ #include +#include "EmptyAwareComboBox.hpp" #include "Theme.hpp" #include "panels/KeyValueView.hpp" namespace geopro::app::formkit { +QComboBox* comboBox(const QString& placeholder, QWidget* parent) { + auto* cb = new EmptyAwareComboBox(parent); + // 有占位文案:设占位 + 维持 currentIndex=-1(构造已置 -1),未选时显灰字占位。 + // 无占位文案:留空,addItem 后自动选首项(保持静态下拉的既有默认选中行为)。 + if (!placeholder.isEmpty()) cb->setPlaceholderText(placeholder); + return cb; +} + DetailForm& DetailForm::group(const QString& name) { geopro::data::DynamicFormGroup g; g.name = name.toStdString(); diff --git a/src/app/FormKit.hpp b/src/app/FormKit.hpp index e73dc53..5c22cec 100644 --- a/src/app/FormKit.hpp +++ b/src/app/FormKit.hpp @@ -15,6 +15,7 @@ #include "repo/RepoTypes.hpp" class QBoxLayout; +class QComboBox; class QDialog; class QDialogButtonBox; class QFormLayout; @@ -23,6 +24,12 @@ class QWidget; namespace geopro::app::formkit { +// ── 下拉框:全局建下拉的标准入口(返回空态感知下拉 EmptyAwareComboBox)────────────── +// placeholder 非空时设占位文案 + currentIndex=-1(未选显灰字占位,对齐 Arco ASelect); +// placeholder 为空时保持 QComboBox 默认行为(addItem 后自动选首项),不强加占位。 +// 无真实可选项时点开弹窗会显示一条灰色「暂无数据」(实现于 EmptyAwareComboBox)。 +QComboBox* comboBox(const QString& placeholder = QString(), QWidget* parent = nullptr); + // ── 只读详情:唯一键值模型构建器 ───────────────────────────────────────────── // 链式 group()/row() 产出 data::DynamicForm,喂给唯一的 §6.4 渲染器 DynamicFormView。 // 三维体/切片/异常等「数据详情」对话框共用,杜绝裸 QFormLayout 漂移。 diff --git a/src/app/ImportDatasetDialog.cpp b/src/app/ImportDatasetDialog.cpp index e5b32ff..cd1e320 100644 --- a/src/app/ImportDatasetDialog.cpp +++ b/src/app/ImportDatasetDialog.cpp @@ -53,10 +53,11 @@ ImportDatasetDialog::ImportDatasetDialog(geopro::data::IAsyncProjectRepository& auto* fl = formkit::makeEditForm(); - typeCombo_ = new QComboBox(card); + // 空态感知下拉:数据类型/导入脚本均数据驱动(异步加载),未选显占位、无数据弹「暂无数据」。 + typeCombo_ = formkit::comboBox(QStringLiteral("请选择数据类型"), card); formkit::capField(typeCombo_); fl->addRow(formkit::editLabel(QStringLiteral("数据类型")), typeCombo_); - scriptCombo_ = new QComboBox(card); + scriptCombo_ = formkit::comboBox(QStringLiteral("请选择导入脚本"), card); formkit::capField(scriptCombo_); fl->addRow(formkit::editLabel(QStringLiteral("导入脚本")), scriptCombo_); diff --git a/src/app/ObjectFormDialog.cpp b/src/app/ObjectFormDialog.cpp index 83b8ac6..d4a481c 100644 --- a/src/app/ObjectFormDialog.cpp +++ b/src/app/ObjectFormDialog.cpp @@ -3,6 +3,8 @@ #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -140,7 +142,7 @@ void ObjectFormDialog::buildTopFields() { // 新建 GS/TM:类型下拉(数据源 gsList / tmList,选择后重载动态表单)。 const QString label = confType_ == kConfTm ? QStringLiteral("方法类型") : QStringLiteral("对象类型"); - typeCombo_ = new QComboBox(topBox_); + typeCombo_ = new EmptyAwareComboBox(topBox_); addRow(label, typeCombo_); QObject::connect(typeCombo_, qOverload(&QComboBox::currentIndexChanged), this, [this](int) { diff --git a/src/app/ProjectListDialog.cpp b/src/app/ProjectListDialog.cpp index 0324196..21b0b1e 100644 --- a/src/app/ProjectListDialog.cpp +++ b/src/app/ProjectListDialog.cpp @@ -3,6 +3,8 @@ #include #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -54,7 +56,7 @@ ProjectListDialog::ProjectListDialog(data::IAsyncProjectRepository& repo, QWidge filter->addWidget(nameEdit_); filter->addSpacing(8); filter->addWidget(new QLabel(QStringLiteral("项目类型"), this)); - typeCombo_ = new QComboBox(this); + typeCombo_ = new EmptyAwareComboBox(this); typeCombo_->setFixedWidth(160); filter->addWidget(typeCombo_); filter->addSpacing(8); diff --git a/src/app/SettingsDialog.cpp b/src/app/SettingsDialog.cpp index 13b9c2f..7aed352 100644 --- a/src/app/SettingsDialog.cpp +++ b/src/app/SettingsDialog.cpp @@ -1,6 +1,8 @@ #include "SettingsDialog.hpp" #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -63,7 +65,7 @@ QWidget* buildAppearancePage() { geopro::app::formkit::addSection(v, QStringLiteral("外观"), page, false); // 主题:跟随系统 / 浅色 / 深色(热切)。 - auto* themeCombo = new QComboBox(page); + auto* themeCombo = new EmptyAwareComboBox(page); themeCombo->addItem(QStringLiteral("跟随系统"), QStringLiteral("system")); themeCombo->addItem(QStringLiteral("浅色"), QStringLiteral("light")); themeCombo->addItem(QStringLiteral("深色"), QStringLiteral("dark")); @@ -76,7 +78,7 @@ QWidget* buildAppearancePage() { QStringLiteral("跟随系统 / 浅色 / 深色,切换即时生效"))); // 界面字号:小/标准/大/特大(重启生效)。 - auto* fontCombo = new QComboBox(page); + auto* fontCombo = new EmptyAwareComboBox(page); fontCombo->addItem(QStringLiteral("小"), 90); fontCombo->addItem(QStringLiteral("标准"), 100); fontCombo->addItem(QStringLiteral("大"), 115); diff --git a/src/app/VolumeParamsDialog.cpp b/src/app/VolumeParamsDialog.cpp index ce12b9e..bba5461 100644 --- a/src/app/VolumeParamsDialog.cpp +++ b/src/app/VolumeParamsDialog.cpp @@ -1,6 +1,8 @@ #include "VolumeParamsDialog.hpp" #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -43,7 +45,7 @@ VolumeParamsDialog::VolumeParamsDialog(int sourceCount, QWidget* parent) : QDial formkit::capField(name_); form->addRow(formkit::editLabel(QStringLiteral("名称")), name_); - model_ = new QComboBox(); + model_ = new EmptyAwareComboBox(); model_->addItem(QStringLiteral("反距离加权 (IDW)"), static_cast(geopro::data::VolumeBuildParams::Model::Idw)); model_->addItem(QStringLiteral("克里金 (Kriging)"), diff --git a/src/app/panels/DescriptionPanel.cpp b/src/app/panels/DescriptionPanel.cpp index b06b2a8..251cb4b 100644 --- a/src/app/panels/DescriptionPanel.cpp +++ b/src/app/panels/DescriptionPanel.cpp @@ -2,6 +2,8 @@ #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -117,7 +119,7 @@ void DescriptionPanel::buildToolbar(QToolBar* tb) { addAlignBtn(QStringLiteral("☰"), Qt::AlignJustify); // 标题下拉(正文 / H1~H4)——块级,作用于当前段。 - auto* headerBox = new QComboBox(tb); + auto* headerBox = new EmptyAwareComboBox(tb); headerBox->addItem(QStringLiteral("正文"), 0); for (int h = 1; h <= 4; ++h) headerBox->addItem(QStringLiteral("标题%1").arg(h), h); tb->addWidget(headerBox); @@ -131,7 +133,7 @@ void DescriptionPanel::buildToolbar(QToolBar* tb) { }); // 字号下拉(px)。 - auto* sizeBox = new QComboBox(tb); + auto* sizeBox = new EmptyAwareComboBox(tb); for (int px : kFontSizesPx) sizeBox->addItem(QStringLiteral("%1px").arg(px), px); sizeBox->setCurrentIndex(2); // 默认 16px(与原版一致)。 tb->addWidget(sizeBox); @@ -143,7 +145,7 @@ void DescriptionPanel::buildToolbar(QToolBar* tb) { }); // 字体族下拉(对照原版 ql-font)。 - auto* fontBox = new QComboBox(tb); + auto* fontBox = new EmptyAwareComboBox(tb); for (const auto& fo : kFontFamilies) fontBox->addItem(QString::fromUtf8(fo.label), QString::fromUtf8(fo.family)); tb->addWidget(fontBox); diff --git a/src/app/panels/DynamicFormEditor.cpp b/src/app/panels/DynamicFormEditor.cpp index c4e7cb5..3ab1879 100644 --- a/src/app/panels/DynamicFormEditor.cpp +++ b/src/app/panels/DynamicFormEditor.cpp @@ -2,6 +2,8 @@ #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -101,7 +103,7 @@ QWidget* buildWidget(const data::EditField& f) { if (ro) le->setEnabled(false); return le; } - auto* cb = new QComboBox(); + auto* cb = new EmptyAwareComboBox(); flattenOptions(f.options, cb); const int idx = cb->findData(val); if (idx >= 0) cb->setCurrentIndex(idx); diff --git a/src/app/panels/chart/AutoAnnotationDialog.cpp b/src/app/panels/chart/AutoAnnotationDialog.cpp index 8cf06a9..c47d940 100644 --- a/src/app/panels/chart/AutoAnnotationDialog.cpp +++ b/src/app/panels/chart/AutoAnnotationDialog.cpp @@ -299,7 +299,8 @@ void AutoAnnotationDialog::addRule() { rc.minPoints->setValue(kDefaultMinPoints); form->addRow(formkit::editLabel(QStringLiteral("最小点数")), rc.minPoints); - rc.type = new QComboBox(rc.body); + // 空态感知下拉:异常类型异步加载(loadExceptionTypes),未选显占位、无数据弹「暂无数据」。 + rc.type = formkit::comboBox(QStringLiteral("请选择异常类型"), rc.body); for (const QJsonValue& ov : exceptionTypeOptions_) { const QJsonObject o = ov.toObject(); rc.type->addItem(o.value(QStringLiteral("name")).toString(), diff --git a/src/app/panels/chart/ExceptionDetailDialog.cpp b/src/app/panels/chart/ExceptionDetailDialog.cpp index 1babbc5..1c8858b 100644 --- a/src/app/panels/chart/ExceptionDetailDialog.cpp +++ b/src/app/panels/chart/ExceptionDetailDialog.cpp @@ -1,6 +1,8 @@ #include "panels/chart/ExceptionDetailDialog.hpp" #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -134,7 +136,7 @@ QWidget* ExceptionDetailDialog::buildCoordTab() { // 坐标系切换 + 顶点数 + 导出(对照原版坐标信息 tab)。 auto* topRow = new QHBoxLayout(); topRow->addWidget(new QLabel(QStringLiteral("坐标系:"), tab)); - coordSysCombo_ = new QComboBox(tab); + coordSysCombo_ = new EmptyAwareComboBox(tab); coordSysCombo_->addItem(QStringLiteral("图形坐标"), QStringLiteral("jb")); // 条件显示(对照原版 drawerExceptionInfo:latLon.length===0 → 仅图形坐标;否则三项)。 // 纯展示响应坐标,不做客户端换算;响应未携带经纬度时退化为仅图形坐标,与原版一致。 diff --git a/src/app/panels/chart/ExceptionDialog.cpp b/src/app/panels/chart/ExceptionDialog.cpp index 726de2d..08ec595 100644 --- a/src/app/panels/chart/ExceptionDialog.cpp +++ b/src/app/panels/chart/ExceptionDialog.cpp @@ -3,6 +3,8 @@ #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -49,7 +51,7 @@ ExceptionDialog::ExceptionDialog(geopro::data::IDatasetCommandRepository* repo, auto* form = formkit::makeEditForm(); // 标注类型(remarkSourceType "1".."4",与原版 annotationType 一致)。 - markTypeCombo_ = new QComboBox(this); + markTypeCombo_ = new EmptyAwareComboBox(this); markTypeCombo_->addItem(QStringLiteral("点"), QStringLiteral("1")); markTypeCombo_->addItem(QStringLiteral("线"), QStringLiteral("2")); markTypeCombo_->addItem(QStringLiteral("面"), QStringLiteral("3")); @@ -58,7 +60,8 @@ ExceptionDialog::ExceptionDialog(geopro::data::IDatasetCommandRepository* repo, form->addRow(formkit::editLabel(QStringLiteral("标注类型")), markTypeCombo_); // 异常类型行:下拉 + 「新增异常类型」按钮(对照原版 exceptionDialog 同行布局)。 - exceptionTypeCombo_ = new QComboBox(this); + // 空态感知下拉:异常类型异步加载(loadExceptionTypes),未选显占位、无数据弹「暂无数据」。 + exceptionTypeCombo_ = formkit::comboBox(QStringLiteral("请选择异常类型"), this); formkit::capField(exceptionTypeCombo_); addTypeBtn_ = new QPushButton(QStringLiteral("新增异常类型"), this); auto* typeRow = new QWidget(this); diff --git a/src/app/panels/chart/ExceptionTextDialog.cpp b/src/app/panels/chart/ExceptionTextDialog.cpp index 07033ff..7d83369 100644 --- a/src/app/panels/chart/ExceptionTextDialog.cpp +++ b/src/app/panels/chart/ExceptionTextDialog.cpp @@ -2,6 +2,8 @@ #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -29,7 +31,7 @@ ExceptionTextDialog::ExceptionTextDialog(QWidget* parent) auto* form = formkit::makeEditForm(); // 字体(对照原版 1宋体/2微软雅黑/3黑体/4楷体,值为字符串 int)。 - fontCombo_ = new QComboBox(this); + fontCombo_ = new EmptyAwareComboBox(this); fontCombo_->addItem(QStringLiteral("宋体"), QStringLiteral("1")); fontCombo_->addItem(QStringLiteral("微软雅黑"), QStringLiteral("2")); fontCombo_->addItem(QStringLiteral("黑体"), QStringLiteral("3")); diff --git a/src/app/panels/chart/ExceptionTypeDialog.cpp b/src/app/panels/chart/ExceptionTypeDialog.cpp index 56c5e46..2828d02 100644 --- a/src/app/panels/chart/ExceptionTypeDialog.cpp +++ b/src/app/panels/chart/ExceptionTypeDialog.cpp @@ -5,6 +5,8 @@ #include #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -170,7 +172,7 @@ void ExceptionTypeDialog::pickColor(QPushButton* swatch, QColor& target) { QGroupBox* ExceptionTypeDialog::buildPointLegend() { auto* box = new QGroupBox(QStringLiteral("点"), this); auto* form = formkit::makeEditForm(); - pointShape_ = new QComboBox(box); + pointShape_ = new EmptyAwareComboBox(box); fillCombo(pointShape_, kPointShapes, QStringLiteral("circle")); form->addRow(formkit::editLabel(QStringLiteral("形状:")), pointShape_); pointSize_ = new QSpinBox(box); @@ -188,7 +190,7 @@ QGroupBox* ExceptionTypeDialog::buildPointLegend() { QGroupBox* ExceptionTypeDialog::buildPolylineLegend() { auto* box = new QGroupBox(QStringLiteral("多段线"), this); auto* form = formkit::makeEditForm(); - polylineShape_ = new QComboBox(box); + polylineShape_ = new EmptyAwareComboBox(box); fillCombo(polylineShape_, kLineShapes, QStringLiteral("solid")); form->addRow(formkit::editLabel(QStringLiteral("线形:")), polylineShape_); polylineWidth_ = new QSpinBox(box); @@ -206,7 +208,7 @@ QGroupBox* ExceptionTypeDialog::buildPolylineLegend() { QGroupBox* ExceptionTypeDialog::buildPolygonLegend() { auto* box = new QGroupBox(QStringLiteral("多边形"), this); auto* form = formkit::makeEditForm(); - polygonFill_ = new QComboBox(box); + polygonFill_ = new EmptyAwareComboBox(box); fillCombo(polygonFill_, kSurfaceFills, QString()); // 默认 '' = 颜色填充 form->addRow(formkit::editLabel(QStringLiteral("填充图例:")), polygonFill_); polygonFillColorBtn_ = makeColorSwatch(polygonFillColor_); @@ -220,7 +222,7 @@ QGroupBox* ExceptionTypeDialog::buildPolygonLegend() { QGroupBox* ExceptionTypeDialog::buildTextLegend() { auto* box = new QGroupBox(QStringLiteral("文字"), this); auto* form = formkit::makeEditForm(); - textFont_ = new QComboBox(box); + textFont_ = new EmptyAwareComboBox(box); fillCombo(textFont_, kTextFonts, QStringLiteral("1")); // 默认 1=宋体 form->addRow(formkit::editLabel(QStringLiteral("字体:")), textFont_); textSize_ = new QSpinBox(box); diff --git a/src/app/panels/chart/FilterDialog.cpp b/src/app/panels/chart/FilterDialog.cpp index 43c2e25..32a979e 100644 --- a/src/app/panels/chart/FilterDialog.cpp +++ b/src/app/panels/chart/FilterDialog.cpp @@ -3,6 +3,8 @@ #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -159,14 +161,14 @@ void FilterDialog::buildRight(QHBoxLayout* body) { // 滤波设置分组。 addSpecTitle(rightLay, QStringLiteral("滤波设置"), card); - dataEdge_ = new QComboBox(card); + dataEdge_ = new EmptyAwareComboBox(card); dataEdge_->addItem(QStringLiteral("设置为无效点"), QStringLiteral("whitening")); dataEdge_->addItem(QStringLiteral("忽略"), QStringLiteral("skip")); dataEdge_->addItem(QStringLiteral("复制边缘点"), QStringLiteral("edgePoint")); dataEdge_->addItem(QStringLiteral("填充"), QStringLiteral("filling")); dataEdgeValue_ = new QLineEdit(card); dataEdgeValue_->setEnabled(false); - noDataPoints_ = new QComboBox(card); + noDataPoints_ = new EmptyAwareComboBox(card); noDataPoints_->addItem(QStringLiteral("扩展"), QStringLiteral("expansion")); noDataPoints_->addItem(QStringLiteral("保留"), QStringLiteral("retain")); noDataPoints_->addItem(QStringLiteral("忽略"), QStringLiteral("skip")); // 原版 skip → 忽略 diff --git a/src/app/panels/chart/GridWizardDialog.cpp b/src/app/panels/chart/GridWizardDialog.cpp index 1d128ff..6ec362e 100644 --- a/src/app/panels/chart/GridWizardDialog.cpp +++ b/src/app/panels/chart/GridWizardDialog.cpp @@ -4,6 +4,8 @@ #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -158,7 +160,7 @@ void GridWizardDialog::buildStep2() { // 分组 2:数据值设置(数据值max/min + 恢复默认值 + 数据值保存为)。 addSectionTitle(p2Lay, QStringLiteral("数据值设置"), page2); - saveFormat_ = new QComboBox(page2); + saveFormat_ = new EmptyAwareComboBox(page2); saveFormat_->addItem(QStringLiteral("线性"), QStringLiteral("linear")); saveFormat_->addItem(QStringLiteral("对数"), QStringLiteral("log")); saveFormat_->setFixedWidth(120); diff --git a/src/app/panels/chart/InversionFormDialog.cpp b/src/app/panels/chart/InversionFormDialog.cpp index 4f6880d..4ac6abb 100644 --- a/src/app/panels/chart/InversionFormDialog.cpp +++ b/src/app/panels/chart/InversionFormDialog.cpp @@ -39,7 +39,10 @@ InversionFormDialog::InversionFormDialog(Mode mode, geopro::data::IDatasetComman // 模型选择行(label + 下拉)。生成视电阻率下拉禁用(复刻原版 disabled)。 auto* modelLay = new QVBoxLayout(); modelLay->addWidget(formkit::editLabel(QStringLiteral("反演模型"), this)); - modelCombo_ = new QComboBox(this); + // 空态感知下拉:反演模型异步加载(listInversionScripts)。反演运算模式占位「请选择反演模型」 + // (替代旧的空首项 allow-clear hack),未选显占位、无脚本弹「暂无数据」;生成视电阻率模式 + // 禁用并由 loadScripts 显式选中,占位不影响其默认选中。 + modelCombo_ = formkit::comboBox(QStringLiteral("请选择反演模型"), this); if (mode_ == Mode::ApparentResistivity) modelCombo_->setEnabled(false); modelLay->addWidget(modelCombo_); root->addLayout(modelLay); @@ -81,8 +84,7 @@ void InversionFormDialog::loadScripts() { if (!ok) return; QSignalBlocker block(self->modelCombo_); // 填充期不触发 onModelChanged self->modelCombo_->clear(); - // 反演运算:首项为空(对应原版 allow-clear,无默认选中)。 - if (mode == Mode::Inversion) self->modelCombo_->addItem(QString(), QString()); + // 反演运算:不再插空首项——占位文案 + currentIndex=-1 即「无默认选中」(对应原版 allow-clear)。 int defaultIdx = -1; for (const QJsonValue& v : list) { const QJsonObject o = v.toObject(); diff --git a/src/app/panels/chart/RawDataChartView.cpp b/src/app/panels/chart/RawDataChartView.cpp index 62f092f..b20f6a5 100644 --- a/src/app/panels/chart/RawDataChartView.cpp +++ b/src/app/panels/chart/RawDataChartView.cpp @@ -17,6 +17,8 @@ #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -78,7 +80,7 @@ RawDataChartView::RawDataChartView(QWidget* parent) : QWidget(parent) { auto* lblCurrentChart = new QLabel(QStringLiteral("当前图形:"), toolbar); - chartTypeCombo_ = new QComboBox(toolbar); + chartTypeCombo_ = new EmptyAwareComboBox(toolbar); chartTypeCombo_->addItem(QStringLiteral("散点图")); auto* btnSaveAs = new QToolButton(toolbar); @@ -822,16 +824,16 @@ void RawDataChartView::buildMeasurementToolbar(const geopro::core::ScatterToolba // x / y 下拉:本地换列重绘;v 下拉:重新请求散点+色阶(M6);值类型下拉:本地变换(M7)。 // 各下拉固定宽度对照原版 datasetTool.vue(X=120/Y=160/V=160/值类型=120)。 - xCombo_ = new QComboBox(toolbar); + xCombo_ = new EmptyAwareComboBox(toolbar); xCombo_->setFixedWidth(kComboW_X); fillCombo(xCombo_, conf.x, conf.defaultX, QString()); - yCombo_ = new QComboBox(toolbar); + yCombo_ = new EmptyAwareComboBox(toolbar); yCombo_->setFixedWidth(kComboW_Y); fillCombo(yCombo_, conf.y, conf.defaultY, QString()); - vCombo_ = new QComboBox(toolbar); + vCombo_ = new EmptyAwareComboBox(toolbar); vCombo_->setFixedWidth(kComboW_V); fillCombo(vCombo_, conf.v, conf.defaultV, QString()); - valueTypeCombo_ = new QComboBox(toolbar); + valueTypeCombo_ = new EmptyAwareComboBox(toolbar); valueTypeCombo_->setFixedWidth(kComboW_ValueType); // 值类型固定三项(原版 linearity/inverse/logarithm),本地变换无后端。 valueTypeCombo_->addItem(QStringLiteral("线性"), QStringLiteral("linearity")); diff --git a/src/app/panels/chart/TablePager.cpp b/src/app/panels/chart/TablePager.cpp index 3656647..446af0b 100644 --- a/src/app/panels/chart/TablePager.cpp +++ b/src/app/panels/chart/TablePager.cpp @@ -3,6 +3,8 @@ #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -90,7 +92,7 @@ TablePager::TablePager(QWidget* parent) : QWidget(parent) { lay->addWidget(new QLabel(QStringLiteral("页"), this)); - sizeCombo_ = new QComboBox(this); + sizeCombo_ = new EmptyAwareComboBox(this); for (int s : kPageSizes) sizeCombo_->addItem(QStringLiteral("%1条/页").arg(s), s); connect(sizeCombo_, &QComboBox::activated, this, [this](int i) { diff --git a/src/app/panels/chart/WhiteningDialog.cpp b/src/app/panels/chart/WhiteningDialog.cpp index c83c3a7..53c9779 100644 --- a/src/app/panels/chart/WhiteningDialog.cpp +++ b/src/app/panels/chart/WhiteningDialog.cpp @@ -4,6 +4,8 @@ #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -14,6 +16,7 @@ #include #include +#include "FormKit.hpp" // formkit::comboBox(空态感知下拉) #include "Theme.hpp" #include "panels/chart/InversionProcessOps.hpp" // buildWhitenBody #include "repo/IDatasetCommandRepository.hpp" @@ -61,7 +64,7 @@ WhiteningDialog::WhiteningDialog(geopro::data::IDatasetCommandRepository* repo, root->setSpacing(geopro::app::space::kMd); // 白化方式下拉(原版 3 项,数值对照 whiteningMethod 1/2/3)。 - methodCombo_ = new QComboBox(this); + methodCombo_ = new EmptyAwareComboBox(this); methodCombo_->addItem(QStringLiteral("数据边界自动白化"), 1); methodCombo_->addItem(QStringLiteral("白化文件"), 2); methodCombo_->addItem(QStringLiteral("模型白化"), 3); @@ -96,7 +99,8 @@ WhiteningDialog::WhiteningDialog(geopro::data::IDatasetCommandRepository* repo, auto* page2 = new QWidget(this); auto* p2 = new QVBoxLayout(page2); p2->setContentsMargins(0, 0, 0, 0); - fileCombo_ = new QComboBox(page2); + // 空态感知下拉:白化文件异步加载(listWhitenedData),未选显占位、无文件弹「暂无数据」。 + fileCombo_ = formkit::comboBox(QStringLiteral("请选择白化文件"), page2); p2->addLayout(optionRow(QStringLiteral("选择白化文件"), fileCombo_, page2)); stack_->addWidget(page2); diff --git a/src/app/panels/columns/Column2DDataset.cpp b/src/app/panels/columns/Column2DDataset.cpp index 3266a61..116dfad 100644 --- a/src/app/panels/columns/Column2DDataset.cpp +++ b/src/app/panels/columns/Column2DDataset.cpp @@ -1,6 +1,8 @@ #include "panels/columns/Column2DDataset.hpp" #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -23,7 +25,7 @@ Column2DDataset::Column2DDataset(QWidget* parent) : QWidget(parent) { // 地图 { auto* form = new QFormLayout(); - auto* basemap = new QComboBox(); + auto* basemap = new EmptyAwareComboBox(); basemap->addItem(QStringLiteral("天地图")); basemap->addItem(QStringLiteral("Google Map")); basemap->addItem(QStringLiteral("隐藏")); @@ -38,7 +40,7 @@ Column2DDataset::Column2DDataset(QWidget* parent) : QWidget(parent) { // 2D视图 { auto* form = new QFormLayout(); - auto* view2d = new QComboBox(); + auto* view2d = new EmptyAwareComboBox(); view2d->addItem(QStringLiteral("关闭")); view2d->addItem(QStringLiteral("Z=0")); view2d->addItem(QStringLiteral("顶部")); diff --git a/src/app/panels/columns/Column3DAnalysis.cpp b/src/app/panels/columns/Column3DAnalysis.cpp index 1fc4bb7..3433ad6 100644 --- a/src/app/panels/columns/Column3DAnalysis.cpp +++ b/src/app/panels/columns/Column3DAnalysis.cpp @@ -1,6 +1,8 @@ #include "panels/columns/Column3DAnalysis.hpp" #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -76,7 +78,7 @@ Column3DAnalysis::Column3DAnalysis(QWidget* parent) : QWidget(parent) { { auto* fr = new QHBoxLayout(); fr->addWidget(new QLabel(QStringLiteral("显示"))); - anomalyFilter_ = new QComboBox(); + anomalyFilter_ = new EmptyAwareComboBox(); anomalyFilter_->addItem(QStringLiteral("全部显示")); // 0 anomalyFilter_->addItem(QStringLiteral("随GS")); // 1 anomalyFilter_->addItem(QStringLiteral("随数据集")); // 2 diff --git a/src/app/panels/columns/Column3DDataset.cpp b/src/app/panels/columns/Column3DDataset.cpp index c3e4c9b..c17c46e 100644 --- a/src/app/panels/columns/Column3DDataset.cpp +++ b/src/app/panels/columns/Column3DDataset.cpp @@ -5,6 +5,8 @@ #include #include #include + +#include "EmptyAwareComboBox.hpp" #include #include #include @@ -36,7 +38,7 @@ Column3DDataset::Column3DDataset(QWidget* parent) : QWidget(parent) { // 坐标轴设置 { auto* form = new QFormLayout(); - auto* mode = new QComboBox(); + auto* mode = new EmptyAwareComboBox(); mode->addItem(QStringLiteral("标准"), static_cast(AxesMode::Standard)); mode->addItem(QStringLiteral("三维立体"), static_cast(AxesMode::Stereo)); mode->addItem(QStringLiteral("不显示"), static_cast(AxesMode::None)); @@ -44,7 +46,7 @@ Column3DDataset::Column3DDataset(QWidget* parent) : QWidget(parent) { [this, mode](int) { emit axesModeChanged(static_cast(mode->currentData().toInt())); }); auto* oPoint = new QPushButton(QStringLiteral("设置…")); connect(oPoint, &QPushButton::clicked, this, &Column3DDataset::oPointClicked); - auto* unit = new QComboBox(); + auto* unit = new EmptyAwareComboBox(); unit->addItem(QStringLiteral("无刻度"), static_cast(AxesUnit::None)); unit->addItem(QStringLiteral("米"), static_cast(AxesUnit::Meter)); unit->addItem(QStringLiteral("英尺"), static_cast(AxesUnit::Feet));