修复五项交互缺陷:折起后滚动条 / 单点信号样式 / 线宽实时生效 / 网格菜单限高滚动 / 点选信号滚动绘图区

1) 分组折起后再展开,信号列表的滚动条消失(筛选态与常规态均可复现)
   - 根因:AdvanceGroupAnimations 只在“折起完成”时重建行集合;而“展开完成”那一帧
     m_contentHeight 从收起态跳回展开态,却没有任何路径重算滚动条可见性 ⇒ 滚动条停在隐藏。
   - 修法:任一方向动画结束都 m_rowsDirty = true + BuildRows(),并每帧 RefreshScrollBar(false)。
     ⚠️ 动画期间不夹取滚动偏移,避免打断与滚动同步的折起动画(scrollSynced)。

2) 单点信号(finiteCount == 1)默认绘制方式由「点线」改为「散点」
   - ApplySinglePointDefaultStyle → DrawMode::SCATTER(仍取最大线宽)。
   - 新增 SignalPanel::ModeOptionEnabled:单点信号的「线段」「点线」置灰、不可点、
     且不参与悬停高亮(避免“看着能点其实点不动”);样式区的绘制与命中都用它。
   - PlotView::UpdateAutoScatterStyles 跳过单点信号,否则“放大自动点线”会把它又改回「点线」。

3) 拖动线宽滑块完全不生效(拖动时线宽不变、松手瞬间变了、再移动鼠标又变回去)
   - 根因一:AcquirePolyline 的折线缓存键没有 lineWidth,而 BuildPolyline 把线宽烘进
     包络带几何(每个像素列的上下包络各向外扩 max(1,线宽)*scale*0.5,「梳状」判定也依赖它)
     ⇒ 稠密信号(采样数 > 绘图区宽度)永远复用旧线宽的顶点与包络带。
   - 根因二:样式补丁只更新“静态场景缓存 + 后备缓冲区”,而每网格图层(宽基底)仍是旧样式像素,
     下一帧命中“宽基底快路径”时把新样式覆盖回旧像素。
   - 修法:PolylineCacheEntry 增加 lineWidth 并进比较;
     样式补丁之后 QueueLaneRebuild(受影响网格) + RenderPendingLaneRebuilds(1)(沿用每帧至多 1 个的预算)。

4) 「目标」行的 GX 标签复用「所属网格」菜单,并先给 PopupMenu 增加「限高 + 条目区滚动 + 尾部固定项」
   - MenuLayoutConstraint::maxHeight(OpenLaneMenu 传客户区高度的 62%);
   - 固定区 = 最后一个分隔线及其之后的条目(即「新建网格」)始终钉在菜单底部,其余条目可滚动;
   - 滚轮滚动条目区(Application 在菜单内滚动时不再关闭菜单,其余情形保持既有行为);
   - 键盘移动悬停项自动滚入可视区(EnsureItemVisible);ItemAtPoint 要求条目同时命中滚动视口;
   - 滚动区文本用 UiTextClipped(GDI 文本不遵循 GDI+ SetClip),并跳过完全不可见的条目(性能);
   - BuildLayout 不再无条件清零 m_scrollOffset(否则 RebuildLayout 会把滚轮位置吃掉);
   - 「目标」行 GX 标签点击**不参与**联动滚动(悬停联动保留)。

5) 点选信号名 ⇒ 绘图区自动把目标网格滚动到可视区内(上百个独立网格时旧有高亮无法定位)
   - SignalPanel::RequestPlotLaneScroll / TakePlotLaneScrollRequest;
     Application::ApplyPendingPlotLaneScroll(WM_LBUTTONDOWN 的面板分支);
     PlotView::ScrollLaneIntoView(在可视区上方就上滚、在下方就下滚、已完整可见则不动)。
   - 只有“点击信号名”触发;悬停绝不改变视图;勾选框 / GX 徽标 / 移除 ✕ 也不触发。

验证(全部离屏执行,窗口 SW_HIDE + 只注入合成消息 + 进程与线程 BELOW_NORMAL,
不移动真实光标、不抢焦点、不截屏):
- 新增 .preflight/check(不入库)68/68 通过;
- A/B:五处修复全部回退 ⇒ 17 项如实失败(B 4 / C 4 / D 7 / E 2);“折起后滚动条”需要
  逐帧重绘驱动动画才能复现,单独回退时 ⇒ 5 项如实失败;恢复后 68/68;
- make release / make debug 零警告。

其它:
- 按要求删除工作区 Tools/(structcheck 脚本)并移除 Makefile 的 structcheck 目标;
- Documents/agent_mem.md 同步:新增 §9.38、§11.2 第 97–103 条与变更日志一行。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-16 11:06:31 +08:00
1 parent 9cf6b2fedc
commit 2904699eae
19 files changed
+743 -350

No files matched your search

-275
View File
@@ -1,275 +0,0 @@
# Structural verification (NOT part of the shipped app; under .preflight/, gitignored).
#
# Proves on disk -- not by prose -- that the two structural requirements are satisfied:
# [1] The giant implementation files were split by responsibility into sibling files
# under each component's own Src/ (and each new translation unit is compiled).
# [2] The common UI controls live in a real `Components/Shared` component, are compiled
# into the binary, and are consumed by the other components.
#
# Run: powershell -File .preflight\perf\structcheck.ps1
# ASCII only (PowerShell 5.1 reads .ps1 as ANSI).
$ErrorActionPreference = "Stop"
$root = Split-Path -Parent $PSScriptRoot
Set-Location $root
$pass = 0
$fail = 0
function Check([bool]$ok, [string]$what) {
if ($ok) { $script:pass++ ; Write-Host (" OK " + $what) }
else { $script:fail++ ; Write-Host (" FAIL " + $what) }
}
function Lines([string]$path) {
if (-not (Test-Path $path)) { return -1 }
return (Get-Content $path | Measure-Object -Line).Lines
}
Write-Host "== [1] split of the giant implementation files =="
# 1a. every expected file of the split exists and is a sibling under the component's Src/
$split = @(
"Components/PlotView/Src/PlotView.cpp",
"Components/PlotView/Src/PlotViewPaint.cpp",
"Components/PlotView/Src/PlotViewInteraction.cpp",
"Components/PlotView/Src/PlotViewFeatures.cpp",
"Components/PlotView/Src/PlotViewInternal.hpp",
"Components/SignalPanel/Src/SignalPanel.cpp",
"Components/SignalPanel/Src/SignalPanelPaint.cpp",
"Components/SignalPanel/Src/SignalPanelInteraction.cpp",
"Components/SignalPanel/Src/SignalPanelInternal.hpp",
"Components/Application/Src/Application.cpp",
"Components/Application/Src/ApplicationCommands.cpp",
"Components/Application/Src/ApplicationLoading.cpp",
"Components/Application/Src/ApplicationInternal.hpp",
"Utilities/Src/DataModel.cpp",
"Utilities/Src/DataModelTab.cpp",
"Utilities/Src/DataModelInternal.hpp"
)
foreach ($f in $split) {
$n = Lines $f
Check ($n -gt 0) ("exists + non-empty: " + $f + " (" + $n + " lines)")
}
# 1b. the core file of each split is a small fraction of the original monster file
Check ((Lines "Components/PlotView/Src/PlotView.cpp") -lt 900) "PlotView.cpp shrunk to < 900 lines (was 13 695)"
Check ((Lines "Components/PlotView/Src/PlotViewPaint.cpp") -gt 4000) "PlotViewPaint.cpp carries the rendering (> 4000 lines)"
Check ((Lines "Components/SignalPanel/Src/SignalPanel.cpp") -lt 1200) "SignalPanel.cpp shrunk to < 1200 lines (was 4 184)"
Check ((Lines "Components/Application/Src/Application.cpp") -lt 1500) "Application.cpp shrunk to < 1500 lines (was 3 135)"
Check ((Lines "Utilities/Src/DataModel.cpp") -lt 1600) "DataModel.cpp shrunk to < 1600 lines (was 2 615)"
# 1c. responsibility really moved: each concern is defined in exactly one file
$concerns = @(
@{ symbol = "PlotView::Paint"; file = "PlotViewPaint.cpp" },
@{ symbol = "PlotView::PaintLane"; file = "PlotViewPaint.cpp" },
@{ symbol = "PlotView::PaintHint"; file = "PlotViewPaint.cpp" },
@{ symbol = "PlotView::LaneAtY"; file = "PlotViewInteraction.cpp" },
@{ symbol = "PlotView::CancelDrag"; file = "PlotViewInteraction.cpp" },
@{ symbol = "PlotView::FitAll"; file = "PlotViewFeatures.cpp" },
@{ symbol = "PlotView::SetHint"; file = "PlotViewFeatures.cpp" },
@{ symbol = "PlotView::Layout"; file = "PlotView.cpp" },
@{ symbol = "PlotView::BuildLaneVisuals"; file = "PlotView.cpp" },
@{ symbol = "SignalPanel::Paint"; file = "SignalPanelPaint.cpp" },
@{ symbol = "SignalPanel::OnLButtonDown"; file = "SignalPanelInteraction.cpp" },
@{ symbol = "SignalPanel::EnsureRows"; file = "SignalPanel.cpp" }
)
foreach ($c in $concerns) {
$hits = @(Select-String -Path "Components/*/Src/*.cpp", "Utilities/Src/*.cpp" -Pattern ("^\s*\w[\w:<>, ]* " + [regex]::Escape($c.symbol) + "\(") |
ForEach-Object { Split-Path $_.Path -Leaf })
$ok = ($hits.Count -eq 1) -and ($hits[0] -eq $c.file)
Check $ok ($c.symbol + " defined only in " + $c.file + " (found: " + ($hits -join ",") + ")")
}
# 1d. the constant/tool block of each big file became a named internal header + using-declaration
$internal = @(
@{ file = "Components/PlotView/Src/PlotViewInternal.hpp"; ns = "pvdetail" },
@{ file = "Components/SignalPanel/Src/SignalPanelInternal.hpp"; ns = "spdetail" },
@{ file = "Components/Application/Src/ApplicationInternal.hpp"; ns = "appdetail" },
@{ file = "Utilities/Src/DataModelInternal.hpp"; ns = "dmdetail" }
)
foreach ($i in $internal) {
$text = Get-Content $i.file -Raw
$hasNs = $text -match ("namespace\s+" + $i.ns)
$hasUsing = $text -match ("using namespace\s+" + $i.ns + "\s*;")
$inlineCount = ([regex]::Matches($text, "(?m)^\s*(?:\[\[[^\]]*\]\]\s*)?inline\s")).Count
Check $hasNs ($i.file + " declares namespace " + $i.ns)
Check $hasUsing ($i.file + " injects it via the using-declaration for " + $i.ns)
Check ($inlineCount -ge 4) ($i.file + " marks the shared helpers inline (" + $inlineCount + " definitions)")
}
# 1e. the split files are compiled, and their objects hold the moved code
$objs = @("PlotView.o","PlotViewPaint.o","PlotViewInteraction.o","PlotViewFeatures.o",
"SignalPanel.o","SignalPanelPaint.o","SignalPanelInteraction.o",
"Application.o","ApplicationCommands.o","ApplicationLoading.o",
"DataModel.o","DataModelTab.o")
$missing = @()
foreach ($o in $objs) { if (-not (Test-Path ("Build/Release/" + $o))) { $missing += $o } }
Check ($missing.Count -eq 0) ("all split translation units were compiled (missing: " + ($missing -join ",") + ")")
$nm = Get-Command nm -ErrorAction SilentlyContinue
if ($nm -ne $null) {
$paintInPaint = @(nm --defined-only -C "Build/Release/PlotViewPaint.o" 2>$null | Select-String "jdi::PlotView::Paint")
$paintInCore = @(nm --defined-only -C "Build/Release/PlotView.o" 2>$null | Select-String "jdi::PlotView::Paint")
Check ($paintInPaint.Count -gt 0) "nm: paint symbols live in PlotViewPaint.o"
Check ($paintInCore.Count -eq 0) "nm: no paint symbol left in PlotView.o"
} else {
Check $false "nm not available (cannot verify symbol placement)"
}
# 1f. every implementation file of every component is registered in the Makefile
$srcFiles = @(Get-ChildItem "Components/*/Src/*.cpp", "Utilities/Src/*.cpp" | ForEach-Object {
$_.FullName.Substring($root.Length + 1).Replace("\", "/")
})
$makefile = Get-Content "Makefile" -Raw
$unregistered = @()
foreach ($s in $srcFiles) {
$dir = $s.Substring(0, $s.LastIndexOf("/"))
if ($makefile -notmatch [regex]::Escape($dir)) { $unregistered += $s }
}
Check ($unregistered.Count -eq 0) ("every implementation directory is registered in the Makefile (missing: " + ($unregistered -join ",") + ")")
Check ($srcFiles.Count -ge 24) ("implementation files in the tree: " + $srcFiles.Count)
Write-Host ""
Write-Host "== [2] Components/Shared =="
Check (Test-Path "Components/Shared/Inc/UiControls.hpp") "Components/Shared/Inc/UiControls.hpp exists"
Check (Test-Path "Components/Shared/Src/UiControls.cpp") "Components/Shared/Src/UiControls.cpp exists"
Check ((Lines "Components/Shared/Inc/UiControls.hpp") -gt 100) ("UiControls.hpp declares the control API (" + (Lines "Components/Shared/Inc/UiControls.hpp") + " lines)")
# 2a. the shared header really owns the enumerated control families
$families = @("UiDrawPlusIcon","UiDrawCrossIcon","UiDrawTriangleIcon","UiDrawCheckBox",
"UiDrawSlider","UiDrawEditField","UiEditFieldMouseDown","UiDrawSwatch",
"UiSwatchNeedsOutline","UiContrastInk","UiSliderRatioFromX")
$header = Get-Content "Components/Shared/Inc/UiControls.hpp" -Raw
$missingFamilies = @()
foreach ($f in $families) { if ($header -notmatch ("\b" + $f + "\b")) { $missingFamilies += $f } }
Check ($missingFamilies.Count -eq 0) ("shared header exposes all control families (missing: " + ($missingFamilies -join ",") + ")")
# 2b. symbol-level proof: every declared control is DEFINED by UiControls.o and by no other object.
# (Text matching cannot tell a definition from a call, so ask the linker's view instead.)
$declared = @([regex]::Matches($header, "(?m)^\s*[\w:<>, ]+\s+(Ui\w+)\s*\(") | ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique)
Check ($declared.Count -ge 15) ("UiControls.hpp declares " + $declared.Count + " control functions")
$definitionOwner = @{}
$objectFiles = @(Get-ChildItem "Build/Release/*.o" -ErrorAction SilentlyContinue)
if ($nm -eq $null) {
Check $false "nm not available (cannot verify control definitions)"
} else {
foreach ($o in $objectFiles) {
$syms = nm --defined-only -C $o.FullName 2>$null
foreach ($name in $declared) {
if ($syms | Select-String (" T jdi::" + [regex]::Escape($name) + "\(")) {
$definitionOwner[$name] = $o.Name
}
}
}
$wrongOwner = @($declared | Where-Object { -not $definitionOwner.ContainsKey($_) -or $definitionOwner[$_] -ne "UiControls.o" })
Check ($wrongOwner.Count -eq 0) ("nm: all " + $declared.Count + " declared controls are defined in UiControls.o (wrong/undefined: " + ($wrongOwner -join ",") + ")")
$uiControlsSymbols = @(nm --defined-only -C "Build/Release/UiControls.o" 2>$null | Select-String " T jdi::Ui").Count
Check ($uiControlsSymbols -ge 15) ("nm: UiControls.o defines " + $uiControlsSymbols + " Ui* symbols")
}
# 2c. other components actually consume them (>= 3 components, >= 30 call sites)
$consumers = @(Select-String -Path "Components/*/Src/*.cpp", "Components/*/Inc/*.hpp" -Pattern "UiDrawPlusIcon|UiDrawCrossIcon|UiDrawTriangleIcon|UiDrawCheckBox|UiDrawSlider|UiDrawEditField|UiEditFieldMouseDown|UiEditFieldMouseMove|UiDrawSwatch|UiSwatchNeedsOutline|UiContrastInk|UiSliderRatioFromX|UiEditTextLeft|UiEditOffsetAt" |
Where-Object { (Split-Path $_.Path -Leaf) -ne "UiControls.hpp" } |
Group-Object { ($_.Path.Substring($root.Length + 1) -split "[\\/]")[1] })
$componentNames = @($consumers | ForEach-Object { $_.Name })
$totalCalls = 0
foreach ($g in $consumers) { $totalCalls += $g.Count }
Check ($componentNames.Count -ge 3) ("shared controls consumed by >= 3 components (" + ($componentNames -join ",") + ")")
Check ($totalCalls -ge 30) ("shared control call sites across components: " + $totalCalls)
# 2d. layering, stated exactly as the code obeys it:
# Components/Shared -> Utilities only (it must not include any component header)
# Utilities/PaintUtil = base layer (must not include UiControls.hpp)
# Utilities must not include feature components (PlotView / SignalPanel / Toolbar / TabBar)
# NOTE: Utilities/Src/PopupMenu.cpp and Utilities/Src/ColorPickerPopup.cpp are hand-drawn UI
# widgets that live in the Utilities folder historically; they consume the shared controls
# (same direction as the components do) and are deliberately excluded from the ban below.
$sharedIncludes = @(Select-String -Path "Components/Shared/Inc/*.hpp", "Components/Shared/Src/*.cpp" -Pattern '^#include "' |
Where-Object { $_.Line -match '"(Application|PlotView|SignalPanel|Toolbar|TabBar|PopupMenu|ColorPickerPopup)\.hpp"' })
Check ($sharedIncludes.Count -eq 0) "Components/Shared depends on Utilities only (no component header)"
$paintUtilIncludes = @(Select-String -Path "Utilities/Inc/PaintUtil.hpp", "Utilities/Src/PaintUtil.cpp" -Pattern '#include "UiControls.hpp"' -ErrorAction SilentlyContinue)
Check ($paintUtilIncludes.Count -eq 0) "Utilities/PaintUtil (base layer) does not depend on Components/Shared"
$badDir = @(Select-String -Path "Utilities/Src/*.cpp", "Utilities/Inc/*.hpp" -Pattern '#include "(PlotView|SignalPanel|Toolbar|TabBar)' -ErrorAction SilentlyContinue)
Check ($badDir.Count -eq 0) "Utilities never includes a feature component"
Write-Host ""
Write-Host "== [3] user-requested behaviour invariants (source level) =="
$utf8 = New-Object System.Text.UTF8Encoding($false)
$panelFile = "Components/SignalPanel/Src/SignalPanel.cpp"
$panelIntFile = "Components/SignalPanel/Src/SignalPanelInteraction.cpp"
$modelFile = "Utilities/Inc/DataModel.hpp"
$panelText = [System.IO.File]::ReadAllText($panelFile, $utf8)
$panelIntText = [System.IO.File]::ReadAllText($panelIntFile, $utf8)
$modelText = [System.IO.File]::ReadAllText($modelFile, $utf8)
# 3a. collapsing a group needs the disclosure triangle OR the "Group N" label -- clicking the name
# itself must not collapse it. (User 2026-10-16.)
Check ($panelText -match "return RowPart::GROUP_TITLE;") "hit test yields a dedicated GROUP_TITLE part"
Check ($panelText -match "const gd::RectF labelRect\(labelLeft, rect\.Y, GroupLabelWidth\(row\), rect\.Height\);") "GROUP_TITLE hot zone is exactly the Group-N label rect"
$gate = @(Select-String -Path $panelIntFile -Pattern "^\s*if \(part != RowPart::DISCLOSURE && part != RowPart::GROUP_TITLE\)")
Check ($gate.Count -eq 1) ("single-click toggle gate accepts only DISCLOSURE / GROUP_TITLE (found " + $gate.Count + ")")
$toggles = @(Select-String -Path $panelIntFile -Pattern "^\s*ToggleGroupFromRow\(item\.groupIndex, row\);")
Check ($toggles.Count -eq 2) ("ToggleGroupFromRow has exactly 2 entry points, single click + double click (found " + $toggles.Count + ")")
Check ($panelIntText -match "(?s)if \(part == RowPart::DISCLOSURE\)\s*\{\s*//[^\n]*\n(\s*//[^\n]*\n)*\s*ToggleGroupFromRow") "double click on the triangle still collapses"
Check ($panelIntText -match "(?s)if \(part != RowPart::BODY\)\s*\{\s*return false;\s*\}") "double click outside the name body never starts a rename"
# 3b. the group rename box must stop before the "N items" counter. (User 2026-10-16.)
Check ($panelText -match "right = statsRight - statsWidth - 6\.0f \* m_scale;") "rename box right edge excludes the item counter"
Check ($panelText -notmatch "right = statsRight;") "rename box never extends over the counter"
Check ($panelText -match "const float statsWidth = UiMeasureTextWidth\(m_fonts->uiSmall, GroupStatsText\(row\)\);") "counter width comes from GroupStatsText"
# 3c. SignalData / SignalGroup keep exactly ONE name field. (User 2026-10-16: drop displayName.)
$data = [regex]::Match($modelText, "(?s)struct SignalData\s*\{(.*?)\n\t\};")
Check ($data.Success) "SignalData struct found in the data model"
if ($data.Success) {
$body = $data.Groups[1].Value
$fields = @([regex]::Matches($body, "std::wstring\s+\w+;"))
Check ($fields.Count -eq 1) ("SignalData declares exactly one string field (found " + $fields.Count + ")")
Check ($body -match "std::wstring name;") "that single field is name"
}
$group = [regex]::Match($modelText, "(?s)struct SignalGroup\s*\{(.*?)\n\t\};")
Check ($group.Success) "SignalGroup struct found in the data model"
if ($group.Success) {
$gbody = $group.Groups[1].Value
$gfields = @([regex]::Matches($gbody, "std::wstring\s+\w+;"))
Check ($gfields.Count -eq 1) ("SignalGroup declares exactly one string field (found " + $gfields.Count + ")")
}
Check ($modelText -match "bool RenameSignal\(") "DataSet::RenameSignal replaces the old SetSignalDisplayName"
Check ($modelText -match "bool RenameGroup\(") "DataSet::RenameGroup replaces the old SetGroupDisplayName"
$staleNames = @(Select-String -Path "Components/*/Src/*.cpp","Components/*/Inc/*.hpp","Components/*/Src/*.hpp",
"Utilities/Src/*.cpp","Utilities/Inc/*.hpp" -Pattern "displayName|SetSignalDisplayName|SetGroupDisplayName|\.Title\(\)|->Title\(\)")
Check ($staleNames.Count -eq 0) ("no displayName / Title() left in any source (found " + $staleNames.Count + ")")
# 3d. comment hygiene, driven by a UTF-8 data file (the .ps1 itself must stay ASCII for PowerShell 5.1)
$phraseFile = "Tools/structcheck_phrases.txt"
Check (Test-Path $phraseFile) "comment-hygiene phrase list exists"
if (Test-Path $phraseFile) {
$scanFiles = @(Get-ChildItem "Components/*/Src/*.cpp","Components/*/Src/*.hpp","Components/*/Inc/*.hpp",
"Utilities/Src/*.cpp","Utilities/Inc/*.hpp" | ForEach-Object { $_.FullName })
$scanText = @{}
foreach ($f in $scanFiles) { $scanText[$f] = [System.IO.File]::ReadAllText($f, $utf8) }
foreach ($line in [System.IO.File]::ReadAllLines($phraseFile, $utf8)) {
if ([string]::IsNullOrWhiteSpace($line)) { continue }
$parts = $line -split "`t"
$mode = $parts[0]
if ($mode -eq "forbid") {
$phrase = $parts[1]
$offenders = @($scanFiles | Where-Object { $scanText[$_].Contains($phrase) })
Check ($offenders.Count -eq 0) ("stale wording absent everywhere: " + $phrase + " (found in " + $offenders.Count + " files)")
} elseif ($mode -eq "require") {
$target = $parts[1]
$phrase = $parts[2]
Check ((Test-Path $target) -and ([System.IO.File]::ReadAllText($target, $utf8).Contains($phrase))) ("corrected comment present in " + $target + ": " + $phrase)
}
}
}
Write-Host ""
Write-Host ("structcheck: " + $pass + " passed, " + $fail + " failed")
if ($fail -ne 0) { exit 1 }
exit 0
-13
View File
@@ -1,13 +0,0 @@
forbid 文件下标
forbid 所属文件下标
forbid 原信号所属文件下标
forbid 文件分组
forbid 原文件下标
forbid 文件序号
forbid displayName
forbid SetSignalDisplayName
forbid SetGroupDisplayName
require Components/Application/Inc/Application.hpp 原信号所属分组下标
require Utilities/Inc/DataModel.hpp 信号名称(加载时取列名 / 通道名;用户可在界面上重命名)
require Utilities/Inc/DataModel.hpp 分组名(加载时取源文件名、含扩展名;用户可重命名)
require Utilities/Inc/DataModel.hpp 与磁盘上的文件**没有任何关系**