Files

37 KiB
Raw Permalink Blame History

DataGrid 桌面版实现原理

本文档描述 DataGrid 桌面版的内部 ownership、源码职责、状态流、生命周期、资源边界和维护规则。公共设计与 API 契约见 DataGrid 桌面版架构设计,不可变查询、范围数据源、异步协调和虚拟化算法见 DataGrid Query 与 Range Source 设计,列宽算法和 presenter 协作见 DataGrid 列宽分配设计,Control Own Token 见 DataGrid Token 设计,变化记录见 DataGrid Changelog。

Popup 接入边界:DataGrid 负责 column-filter Query intent 与候选内容,filter Flyout 只作为 relay 适配层,filter Popup 负责 实际显示。模板重建或宿主切换时先释放旧 relay,再绑定新的 Popup;普通外点、Escape、失焦和业务关闭在 pinned 状态下被 拦截,detach、窗口销毁、跨 TopLevel 和无效锚点走生命周期关闭并释放 Popup host。完整状态机见 Popup 钉住打开设计。

1. 实现定位

DataGrid 的实现同时维护四条互不争夺 ownership 的链路:

  • Data:不可变 Query、Source schema、range request/result、snapshot 和 selection。
  • Presentation:range cache、display slot、已提交 viewport、row/group container 与 scroll extent。
  • Columns:列定义、列宽、冻结列、水平虚拟化和 header/cell 视觉投影。
  • Theme:ControlTheme、Template Part、伪类、Token、Popup 和平台宿主。

DataGrid 是 public contract 与 UI transition owner;Source 执行业务查询;presenter 只布局已经提交的数据;template part 只投影 状态。任何功能都不能通过在这些层之间建立第二份可变状态来完成同步。

2. 源码文件结构

稳定 ownership 按职责组织:

src/AtomUI.Desktop.Controls.DataGrid/
├── DataGrid.cs                         public contract + lifecycle
├── DataGrid.Query.cs                   Query commit and visual projection
├── DataGrid.RangeLoading.cs            viewport-to-coordinator integration
├── DataGrid.Virtualization.cs          desired/committed viewport + container bridge
├── Data/
│   ├── Query/                          immutable values, scalar and validation
│   ├── Source/                         schema, request, result and capability contracts
│   │   └── Local/                      typed local source and projection
│   └── Virtualization/                 viewport scope, request scheduler, snapshot, cache, index and heights
├── Column/                             column contract, header, sorting/filtering interaction
├── Column/Filters/                     filter indicator, flyout and candidate presentation
├── Row/                                row, group header, details and row presenters
├── Cell/                               cell state and horizontal virtualization
├── EventArgs/                          public event payloads
├── Themes/                             static templates, selectors and resource binding
├── Localization/                       generated-catalog-backed localized strings
└── Utils/                              narrow shared algorithms and converters

维护规则:

  • 主文件保留 public/protected API、Avalonia 属性注册、事件和主要生命周期入口。
  • partial 与 helper 按稳定 owner 拆分,不按 public/private 或单个触发点拆分。
  • Theme 文件负责静态视觉结构、Template Part、selector 和资源绑定;不在 C# 中动态复制同一结构。
  • Token 只保存组件视觉变量,不保存 Query、loading、selection、expanded 或 popup runtime state。
  • Gallery 只展示用法和验证行为,不成为运行时状态 owner。
  • GeneratedFiles 由对应 generator 维护,不手工编辑。

3. 核心类职责

类型 Ownership 主要输入与输出
DataGrid public API、模板生命周期、presentation transition 接收 Query/Source/input,发布 applied state 与视觉更新。
DataGridQueryController Query validation、revision 和交互策略 把外部设置、sort/filter/group intent 归一为唯一 Query。
IDataGridSource schema、range fetch、snapshot 与 invalidation 消费 DataGridFetchRequest,返回不可变精确切片。
DataGridRangeCoordinator generation、viewport scope、请求优先级、block lease、pending cache 和错误 保留最新目标仍需要的工作,取消过时工作,确保 visible range 覆盖并形成可提交 snapshot。
DataGridPresentationSnapshot 一次完整 applied presentation 固定 Source/query/generation/snapshot、page、expansion、totals 与 viewport。
DataGridPresentationIndex slot/entry/offset 映射 管理有限 block、sparse height delta 与 key/index metadata。
DataGridDisplayData 已实现纵向容器和回收池 在 committed range 上复用 row/group header,不读取 Source。
DataGridRowsPresenter 同步纵向布局与 child index 只测量/排列 committed containers。
DataGridCellsPresenter 可见列和冻结列布局 维持水平 virtualization,不拥有纵向 range 请求。
DataGridColumn FieldId、列级能力、SortState 从 Query/schema 推导 header 与 cell 状态。
DataGridLocalSource<T> 本地 typed projection 在稳定快照上 filter/sort/group,并只物化请求 range。

协作边界:

  • Source 不捕获 DataGrid,也不返回可继续修改的 entry collection。
  • Coordinator 不解释业务协议;LocalSource 和业务 Source 不生成视觉对象。
  • RowsPresenter、DisplayData、Row 和 Cell 不发起 FetchAsync。
  • Header、FilterIndicator 和 Flyout 只产生 Query intent,不直接改 range cache。
  • 可选 editable、movable、key-lookup 和 bulk-selection 能力与只读 Source 分离。
  • DataGrid 是列宽 solver owner;header/rows/cells presenter 只提供有限 viewport 或内容测量输入。

4. 状态与数据流

4.1 Query 到 presentation

Public Query / column gesture / filter confirmation / group intent
  -> DataGridQueryController validates structure and Source.Schema
  -> commit pending edit
  -> structural no-op check
  -> QueryRevision++ and visual projection
  -> QueryChanged
  -> DataGridRangeCoordinator
  -> IDataGridSource.FetchAsync
  -> result contract validation
  -> atomic DataGridPresentationSnapshot commit
  -> rows / groups / pagination / scrollbar / selection projection

Query 是 sort/filter/group 的唯一 owner;AppliedQuery 只属于已提交 snapshot。QueryChanged handler 若同步设置另一个 Query,新的 revision 使外层 transition 失效。没有 suppress flag、ignore flag 或 Dispatcher 延时。

Source replacement、Reload、Invalidated、snapshot expiry、PageRequest 与 GroupExpansion 变化递增 DataGeneration。每个 request 同时携带 QueryRevision 与 DataGeneration,任何迟到或来自旧 Source/snapshot 的结果在 commit 前丢弃。

4.2 Desired 与 committed viewport

scroll / thumb / keyboard / bring-into-view intent
  -> coalesced DesiredViewport
  -> replace active ViewportRequestScope
  -> pure ViewportPlanner
  -> retain shared visible block leases and cancel orphaned work
  -> foreground scheduler fills missing blocks (synchronous hit or asynchronous miss)
  -> atomic CommittedViewport swap
  -> one layout pass over committed blocks

DesiredViewport 是最新用户意图;CommittedViewport 是当前可以同步布局的完整范围。缓存 miss 时继续显示旧 committed rows, 不创建 placeholder 或 null row。同一 generation 只有一个 active viewport scope;新 scope 先取得仍需 block 的 lease,再释放旧 scope,避免相邻目标取消并重取共享 block。失去所有有效 lease 的工作从可发现 inflight 集合移除并取消,后续请求不会附着到 已经取消的 task。prefetch 从属于产生它的 committed scope,只填 cache,不改变 loading visual、不触发布局、不创建 control, 也不能阻塞新的 visible target。

4.3 Load 与错误状态

ItemsSource == null                    -> Idle
no applied snapshot + active request  -> Loading
applied snapshot + active request     -> Refreshing
visible target atomically committed   -> Ready
non-cancellation failure              -> Error

internal EffectiveIsOperating 只合并用户显式 IsOperating 与 Loading:

EffectiveIsOperating = IsOperating || LoadState == Loading

Loading 表示没有可展示的已提交 snapshot,实际挂起时驱动现有 Spin。Refreshing 保留旧 rows 的几何与完整不透明度, 不自动启动 Spin;读取类 selection 可以继续投影,edit/delete/move 仍被禁止。调用方显式设置 IsOperating=true 时,任意 LoadState 都继续显示 Spin。

只有 ValueTask 实际挂起时才发布 Loading/Refreshing。同步 Source 与 cache hit 在当前调用中完成 result validation、snapshot commit 和 Ready 投影,不增加一次 Dispatcher 调度,也不让嵌套 DataGrid 在一帧内停留于虚假的 Loading。

用户 Query/page/group/scroll 失败时恢复最后成功 snapshot 的 Query、page、expansion、viewport、totals 和视觉;只有实际 Query 回退发出一次 LoadRollback QueryChanged。初次失败保持空 presentation。Invalidated 刷新失败保留 stale 只读 snapshot 并等待 Reload。Cancellation 和 superseded request 不进入 Error。

4.4 选择与 current

DataGridSelectionState + CurrentRowKey
  -> committed DataGridSourceEntry(RowKey, DataIndex)
  -> realized DataGridRow/DataGridCell pseudo-classes

Selection 使用 ExplicitKeys、AllMatchingQuery、IndexIntervals 和 ExcludedKeys 表达声明式状态,内存不随 total row count 增长。 Row/Cell 只投影当前 entry 的判定结果,container recycle 不改变 selection。CurrentRowKey 是 identity;DataIndex hint 只用于定位。 未加载 key 的解析依赖可选 IDataGridKeyLookupSource,不能伪造 SelectedItem 或 index。

4.5 分页

PageSize / current page intent
  -> checked DataGridPageRequest(long data start, int count)
  -> DataGridFetchRequest
  -> applied TotalDataCount and page state
  -> PART_TopPagination / PART_BottomPagination

PageSize 为 0 时使用连续模式。非零时先按业务行切 PageRequest,再插入 group header、应用 expansion 和 display Range。上下两个 Pagination 只投影同一 applied state;模板 reapply 先回放 state,再订阅 input。翻页成功后 vertical offset 归零,失败时整体 回退。

4.6 列宽

DataGridColumnHeadersPresenter / DataGridGroupColumnHeadersPresenter
  -> header desired widths + finite viewport when rows are absent
DataGridRowsPresenter / DataGridCellsPresenter
  -> CellsWidth + realized cell desired widths when rows are present
DataGrid
  -> finish Auto measurement
  -> AdjustColumnWidths resolves star widths
DataGridColumn display widths
  -> headers / rows / cells / filler / scrollbars

普通与分组列头共享有限宽度入口;有行时使用 CellsWidth。Presenter 不独立修改 star 列,不把 filler 当作求解结果。完整矩阵见 DataGrid 列宽分配设计。

4.7 过滤与 pinned popup

Column.Filters / presentation metadata
  -> filter Flyout Query intent
  -> immutable DataGridQuery.Filters
  -> Source request + Header/Indicator/Flyout projection

Filters 只表示候选内容;Query.Filters 是 applied 条件 owner。Flyout 打开时从 Query 初始化 checked state,确认时构建一个完整新 Query。Schema 声明 operator、arity 和 scalar kinds;LocalSource descriptor 提供 typed evaluator,远端 Source 显式翻译。

Pinned filter 由 DataGrid 独占 ownership:

DataGrid.IsPopupPinnedOpen
  -> first eligible column in DisplayIndex order
  -> DataGridColumnHeader
  -> DataGridFilterIndicator
  -> current filter Flyout
  -> Popup

replacement 先 lifecycle-close 旧 Popup、释放 relay 和 callback,再接入新目标。Unpin 只解除关闭拦截;DataGrid detach、template reapply、禁用、隐藏、TopLevel 变化或锚点失效强制关闭旧 Popup。

4.8 行拖动

PointerPressed
  -> RowReorderSession(Pressed, pointer, Source, snapshot, RowKey)
  -> drag threshold
  -> RowReordering
  -> revalidate owner/query/snapshot/capability
  -> RowsPresenter ghost row
  -> key-relative IDataGridMovableSource request
  -> cleanup
  -> RowReordered only after successful mutation

每个 DataGrid 只有一个会话。handle 不修改数据,presenter 不决定移动语义,Source 不持有视觉对象。Source、Query、snapshot、 row、pointer 或 capability 变化均取消会话。完成和取消共用幂等清理入口,释放 capture、ghost、offset、transition 和引用。

5. 组合结构模型

5.1 控件角色图

DataGrid (DataGridTheme.axaml)
  -> PixelAlignedBorder#Frame (template-stable)
     -> Border#FrameContentClip (template-stable)
        -> Spin (internal-observable)
           -> DockPanel
              -> Pagination#PART_TopPagination (template-stable)
              -> PixelAlignedBorder#TitleFrame
                 -> ContentPresenter#Title
              -> Pagination#PART_BottomPagination (template-stable)
              -> ContentPresenter#Footer
              -> Grid
                 -> DataGridTopLeftColumnHeader#PART_TopLeftCorner (template-stable)
                 -> Border#ColumnHeadersPresenterFrame
                    -> Panel
                       -> DataGridColumnHeadersPresenter#PART_ColumnHeadersPresenter (template-stable)
                       -> DataGridGroupColumnHeadersPresenter#PART_GroupColumnHeadersPresenter (template-stable)
                 -> PixelAlignedBorder#ColumnHeadersAndRowsSeparator
                 -> DataGridRowsPresenter#PART_RowPresenter (template-stable)
                    -> DataGridRow (internal-observable, virtualized)
                       -> DataGridCellsPresenter (internal-observable, horizontally virtualized)
                    -> DataGridRowGroupHeader (internal-observable, virtualized)
                 -> ContentPresenter#EmptyIndicator
                 -> ScrollBar#PART_VerticalScrollbar (template-stable)
                 -> ScrollBar#PART_HorizontalScrollbar (template-stable)
                 -> Border#DisabledVisualElement
                 -> DataGridColumnDraggingOverIndicator#PART_DraggingOverIndicator (template-stable)

模板中的 Spin 节点复用 DataGrid 的 operating state;Query/Range Source 只改变该状态的有效输入,不增加平行 overlay。RowsPresenter 下面的 row、group header 和 cells 由 container lifecycle 动态接入,但必须继续遵守同一个 template/scroll ownership。

5.2 协作节点

节点 类型 来源 生命周期 owner 影响的 public API 稳定性 使用边界
DataGrid public control DataGrid.cs 应用/VisualTree Source、Query、Selection、layout/theme API public 唯一 UI state owner。
Frame / FrameContentClip template node DataGridTheme.axaml DataGrid template border、corner、background template-stable 维护外框和内容裁剪,不能下沉到 row。
Spin public child control DataGridTheme.axaml DataGrid template IsOperating、LoadState internal-observable 只投影 effective operating state。
Top/Bottom Pagination public child control DataGridTheme.axaml DataGrid template PageSize、visibility、align template-stable 只投影 applied page state。
Column headers presenters internal presenters DataGridTheme.axaml DataGrid template headers、column width、Query intent template-stable 提供测量与交互,不拥有 Query。
RowsPresenter internal presenter DataGridTheme.axaml DataGrid template rows、scrolling、selection template-stable 只布局 committed entries,不调用 Source。
Row / GroupHeader item containers C# container generation DataGridDisplayData item/group/details/selection internal-observable recycle 时完整清理 entry state。
CellsPresenter internal presenter Row theme/C# row container columns、frozen、horizontal scroll internal-observable 继续拥有横向 virtualization。
EmptyIndicator template node DataGridTheme.axaml DataGrid template EmptyIndicator internal-observable 只由 committed empty state 控制。
ScrollBars template nodes DataGridTheme.axaml DataGrid template scrollbar visibility template-stable 与 PresentationIndex 的 extent/offset 同步。
DraggingOverIndicator internal control DataGridTheme.axaml row reorder session CanUserReorderRows template-stable 只绘制反馈,不提交数据。
Filter Flyout / Popup internal overlay chain filter themes/C# current header/indicator/flyout column filter options、pinned state internal-observable relay Query intent,replacement 对称释放。

6. 生命周期与模板接入

6.1 控件生命周期

  • 构造阶段注册属性与静态状态,不依赖 Template Part。
  • OnApplyTemplate 开始先解绑旧 part,再获取新 part、回放 applied state 并建立事件连接。
  • Attach 时订阅当前 Source.Invalidated;Source replacement 与 detach 时对称退订。
  • Detach 先失效 revision/generation identity,再取消 request、dispose CTS、释放 pending/applied cache、part 与 relay。
  • 迟到 continuation 必须核对 Source、revision、generation 和 snapshot;identity 失效后直接结束。
  • DataGrid 不 dispose 外部 Source;Source 自己拥有数据库连接、集合订阅和自身 IDisposable/IAsyncDisposable 生命周期。

6.2 Source replacement

Source replacement 遵循预验证事务:候选 schema、当前 Query、列与 page 首先在旧状态外验证;随后提交 edit;只有这些步骤成功 才一次性切换 Source identity 和 generation。切换后取消/退订旧 owner、清空旧 cache/totals/key resolution,订阅新 Source 并 加载首个范围。切换点之后旧 Source 永不重新成为有效 owner。

6.3 Request 与 cache 生命周期

资源 Owner 释放或失效
generation CTS RangeCoordinator Query/generation change、detach、完成后 Dispose
viewport request scope RangeCoordinator 不同 DesiredViewport、generation replacement 或 detach 时 supersede;先转移共享 lease,再释放其余 lease
block request lease active viewport scope scope supersede、visible commit 后 prefetch 失效、generation replacement 或 detach
block work item CTS block request scheduler 最后一个有效 lease 释放或 generation/detach 失效时取消;work item 进入终态后 Dispose
Source request CancellationToken 单 block work item 由 generation 与 work item 生命周期共同约束;不受已失效 caller continuation 复活
pending blocks pending snapshot commit、failure、query/source change、detach
applied blocks applied snapshot snapshot/source change、detach
realized/edit/drag block pin 对应 visual/session owner container/session detach 后
pooled entry/index arrays LocalSource 或 block projection/block eviction 或 Source dispose
sparse height entries PresentationIndex block/user state block eviction 或显式 state 清理

6.4 Template Part 接入

  • PART_RowPresenter 只承载 committed row/group containers;空数据隐藏,不成为列宽求解前置。
  • 显式有限高度的首个 snapshot 可以先只提交数据,由首次布局按真实 viewport 实现 rows;自动高度且尚无 Bounds 的嵌套 DataGrid 在模板就绪后实现一个已提交 bootstrap entry,以建立非零 DesiredSize,期间不从 Measure 请求 Source。
  • PART_TopPagination 与 PART_BottomPagination 先接收 applied state,再订阅翻页 intent。
  • PART_ColumnHeadersPresenter 与 PART_GroupColumnHeadersPresenter 接入同一个 DataGrid-owned column-width solver。
  • 现有 Spin 绑定 EffectiveIsOperating;首次异步 loading 显示 Spin,refreshing 保持已提交内容完全可见,两者都不改变 Frame/Header/scroll geometry。
  • SortIndicator、FilterIndicator、row/cell/group header 继续使用既有 theme、part 与 pseudo-class。
  • FrameContentClip、FrameCornerRadius 和 FrameBorderThickness 的 ownership 不因异步数据架构改变。

7. 交互与事件处理

  • Header pointer、keyboard、tooltip 与 SetSort 都调用同一 DataGridSortPolicy;resize 边界命中优先于排序手势。
  • Filter Flyout 只收集候选值并提交 Query intent;Query validation、edit commit 和 request scheduling 仍由 DataGrid 管理。
  • 滚轮、触控惯性、scrollbar line/page/thumb、Home/End/PageUp/PageDown 和 ScrollIntoView 统一产生 DesiredViewport。
  • Scroll handler 只合并 intent;到内部边界前由 DataGrid 消费,边界后允许外层 ScrollViewer 继续滚动。
  • QueryChanged 在内存提交与视觉投影后、Source request 前触发;回调重入后重新核对 revision。
  • SelectionChanged 描述声明式 OldSelection/NewSelection,不为事件参数加载 cache 外 item。
  • RowReordering 在超过拖动阈值且创建 ghost 前最多触发一次;回调后重新验证 Source、Query、snapshot、row 和 capability。
  • RowReordered 只在 Source mutation 成功且 capture、ghost、offset、transition 和会话引用清理后触发。
  • Disabled、detach、template reapply、Source/query replacement、capture lost 或 row recycle 终止当前 mutation/drag session。
  • Loading/Refreshing/stale 阻止 edit/delete/move 提交,但不能截断滚动 intent 的合并或破坏现有 focus/keyboard 路径。

8. 内部算法与关键流程

8.1 Source result validation

UI commit 前验证:

  • Source identity、QueryRevision、DataGeneration、request Range 与 ExpectedSnapshot 匹配。
  • StartIndex、Entries.Length 与 totals 满足精确切片公式。
  • Data/Group entry 的 payload、RowKey/GroupKey、WindowDataIndex 与 DataIndex 合法。
  • 当前 cache 范围内 row/group key 分域唯一且 data indices 严格递增。
  • 相同 snapshot 下 TotalDataCount、WindowDataCount 和 TotalEntryCount 的条件恒定。

任何合同错误进入 DataGridSourceContractException,不部分提交。连续模式不能用 int 精确表示活动 data/display count 时抛出 DataGridPresentationLimitExceededException,不能截断、饱和或取模。

8.2 三个索引域

Domain 类型 使用位置
Source data long TotalDataCount、PageRequest.DataStartIndex、entry.DataIndex
Window data int WindowDataCount、entry.WindowDataIndex、DataGridRow.Index、IChildIndexProvider
Display slot int TotalEntryCount、Range.StartIndex、DataGridRow.Slot、vertical virtualization

不同 domain 只通过 committed entry 显式映射。GroupHeader 不占 WindowDataIndex。分页可以访问 long global index,但单个 UI window 的 child/slot count 必须保持准确 int。

8.3 Range planning、cache 与 commit

Block size 由 Schema.PreferredRangeSize 在 [32, MaximumRangeSize] 内确定。generation 尚未取得 result identity 时,bootstrap 是 generation 级前置工作,不随每个中间 viewport 重启;identity 建立后只为最新 target 补齐 visible block。最大 Source 并发 为 2。

Coordinator 在锁内完成 scope 交换和 lease 转移,在锁外执行取消与 Source 调用,避免业务 Source continuation 或 cancellation callback 进入内部 gate。新 scope 先取得仍需 visible block 的 lease,再释放旧 scope;没有 lease 的排队 work item 立即从调度队列 和可发现 inflight 集合移除,没有 lease 的执行中 work item 立即取消。相同 generation/block 的有效 work item 继续去重;已经标记 取消的 work item 不得被新 scope 复用,其迟到结果也不能写入 active pending cache、错误状态或 presentation。

调度器维护 visible 与 prefetch 两个有界优先级。空闲并发槽始终先取 visible;prefetch 只在没有 visible 缺口时进入 Source。 新 visible intent 会先撤销旧 scope 的 prefetch lease,再调度自身缺口。已经进入 Source 的旧工作依赖 Source 协作取消释放槽位, 但无论 Source 是否遵守取消,迟到结果都不能通过 generation、scope 与 lease identity 校验。

同步 Source 或 cache hit 仍沿当前调用栈完成,不被优先级队列强制异步化。caller cancellation 只结束该 caller 的等待;只要同一 work item 仍被 active scope 使用,就不能由单个 waiter 取消共享 Source 工作。scope supersede 和 generation 失效才改变 work item 的有效 lease。旧 scope completion 不得覆盖新 scope 的 Loading/Refreshing;prefetch failure 只保留诊断信息,不改变可见状态。

Cache 是有限 LRU,容量不依赖 TotalDataCount。prefetch block 不生成 control。visible blocks 完整且全部通过验证后一次性 pin 新 block、交换 snapshot/viewport、更新 totals、回收旧容器、实现新容器、回放 selection/current/edit/details、更新 extent/offset/scrollbar 并进入 Ready。旧容器 detach 后才解除旧 block pin。

若 totals 变化导致 page/scroll target 越界,丢弃 pending presentation,递增 generation 并只请求最终合法 target,不能显示 瞬时空页。

8.4 Height、extent 与 anchor

固定行高使用 O(1) offset/slot 公式。自动行高、GroupHeader 和 RowDetails 使用:

estimatedOffset(slot) = slot * defaultEstimate + sparsePrefixDelta(slot)

SparseHeightDeltaIndex 只保存 pinned/current-LRU block 的实测差值;passive block eviction 时把样本吸收到常量大小、按 entry kind/group level 分类的 estimator 并删除明细。prefix sum 和 offset-to-slot 是 O(log M),M 受 cache/pin 与用户显式状态上限 约束。禁止从 slot 0 扫描到 first slot,也禁止按 total/历史访问量创建全局 Fenwick array。

自动 RowHeightEstimate 的 owner 是 committed data generation。CommitRangePresentation 先用 DefaultRowHeight 构建新 generation 的高度索引;只有 range、snapshot、pins 与 staged containers 均准备成功、即将交换 presentation 时,才调用 ResetRowHeightEstimateForDataGeneration。这样失败或迟到的 generation 不污染当前高度状态;Source 换绑、Query/filter、分页和 Invalidated/collection Reset 成功后都从新 baseline 采样。首个有效 data row 的正常 measure 会更新 RowHeightEstimate 并 rebase 稀疏索引,显式 RowHeight 继续优先。重置为 O(1),不新增订阅、timer、CTS 或按总行数分配的结构。

测量修正以首个完整可见 entry key 与 intra-row offset 为锚。Query/PageRequest 成功后 vertical offset 归零;GroupExpansion 保持操作 header 的屏幕 Y;Invalidated 优先按首行 key 恢复,失败时 clamp 到最近合法 slot。

DataGrid 写入 vertical scrollbar 前仍把 extent travel clamp 到非负;AbstractScrollBar 再对所有有限值统一保证 Maximum >= Minimum。小于一个物理像素的 travel range 收敛到 Minimum,因此 Auto scrollbar 不抖动,也不会通过非法负 Maximum 触发 Avalonia RangeBase 校验异常。

8.5 Container recycle

回收时清理 DataContext、RowKey、DataIndex、WindowDataIndex、Slot、group metadata、selection/current/edit/hover、RowDetails、 cell sort/filter 状态和事件订阅。DataGridDisplayData 继续复用循环 displayed list 与 row/group pool;不能引入第二个 ItemsRepeater、VirtualizingStackPanel 或 scroll owner。

RowsPresenter.Children 只包含 visible 加一个 editing row 和一个 drag row 上限的 controls。Measure、Arrange、prepare 和 recycle 不 fetch、不等待、不分配 range buffer。嵌套滚动在内部边界前由 DataGrid 消费,到边界后交给外层 ScrollViewer。

8.6 Query 执行顺序

Source 使用固定顺序:

filter
  -> stable Groups then Sorts
  -> PageRequest over business rows
  -> insert GroupHeader
  -> apply GroupExpansion
  -> flatten display entries
  -> Range slice

Groups 形成领先 sort,同一 FieldId 不能重复出现在 Groups 与 Sorts。Source 省略 collapsed descendants;DataGrid 不为完整结果 建立全局 group-header 或 collapsed-slot table。GroupEntry.LeafCount 表示当前 PageRequest 内包含 collapsed descendants 的业务 行数,不是跨页 aggregate。

8.7 LocalSource

DataGridLocalSource 在 owner thread 捕获稳定引用快照和 sourceVersion;后台只处理快照的 int index buffer。Filter 不复制业务 对象,sort 依次比较 Groups、Sorts 和原 source ordinal,保证稳定输出。PageRequest 先切业务行;plain table 不建 group 对象; range fetch 只创建请求范围大小的 entry buffer。

空 Query、无分页、无分组的 plain table 直接按 request range 读取稳定 source snapshot,不创建全量 projection cache。只有 filter/sort/group/page 路径进入 index projection 与有限 LRU;因此百万行 plain 首屏仍只物化一个有界 range。

typed descriptor 提供 Func<T,TValue> getter、IComparer<TValue>、operator evaluator 与 scalar converter。比较循环不执行 LINQ、 反射、字符串 path、文化对象创建或 delegate 组合分配。projection cache 以 sourceVersion、Query、PageRequest 和 Expansion 的 相应 identity 分层,并受有限 LRU 管理。

8.8 Sort/filter visual delta

Query.Sorts 被归一为 FieldId 到 (direction, priority) 的小型映射,只更新 old/new delta 涉及的 column。Column.SortState 驱动 Header 的既有 sort pseudo-class 和 indicator。Cell 不长期订阅 Header;prepare/recycle 从 OwningColumn.SortState 初始化,Query delta 只遍历已实现 cells。

Filter Flyout 从 Query.Filters 初始化,提交完整新 Query。候选 Filters 的集合变化只重新物化候选内容,不能绕过 Query owner。

8.9 Frame、列宽与水平 virtualization

  • FrameCornerRadius 与 FrameContentClip 共同约束根内容裁剪;FrameBorderThickness 只由 frame border option 决定。
  • 最后一行 divider 与 Frame 底边保持唯一绘制责任;Footer、底部分页或横向滚动条存在时恢复行 divider。
  • 有限列 viewport 只进入 DataGrid-owned solver;Auto completion 与 star distribution 是独立阶段。
  • Auto / SizeToCells 只消费已实现 cells 的正常 measure 结果;列宽发现不得 fetch 未显示 range 或扩大纵向 realized window。
  • 首屏需要稳定内容 extent 的场景由列 Pixel Width 或 MinWidth 提供基线;水平 scrollbar 继续只投影真实 extent,不承担宽度策略。
  • Star 列仍能吸收空间时 filler 为零;只有所有可调列达到 min/max 后才允许正 filler。
  • 水平可见列、冻结列和 cell count 继续由 DataGridCellsPresenter 管理;纵向 commit 不全量重建 cells。

9. 资源、性能与 AOT 边界

9.1 热路径

  • Scroll input 只更新/合并 DesiredViewport,handler 返回后由 coordinator 调度 request。
  • 每次不同的 DesiredViewport 只替换一个 active scope;旧排队项不会作为无界 task 留在 semaphore 前等待。
  • PointerMoved 只更新 drag session、ghost offset、target key 和必要自动滚动 intent。
  • Measure/Arrange/prepare/recycle 只做 committed index lookup 与 container state 投影。
  • Query no-op 不请求、不刷新 rows;sort state 没有 per-cell 长期订阅。
  • Ready 模板不增加 visual node;prefetch 不触发布局。
  • 同步 Source/cache hit 不发布瞬时 Loading/Refreshing;异步首次加载驱动 Spin,已有 snapshot 的异步刷新不启动自动遮罩。

9.2 复杂度与资源上限

路径 上限
fixed-height offset lookup O(1)
variable-height offset lookup O(log M),M 由 cache/pin/显式状态约束
realized row/group controls visible + editing row + drag row
request concurrency 默认最多 2
active viewport scope 每个 generation 1 个
queued obsolete viewport work 0;scope supersede 时从可发现队列移除并取消
prefetch priority 低于所有 visible 缺口,并绑定产生它的 committed scope
range size Schema.MaximumRangeSize,最大 4096
cache 有限 block LRU,与 total 无关
snapshot-expiry auto retry 每个用户操作最多 1 次

所有 request、cache、pool、subscription 和 sparse state 必须在长时间往返滚动后达到稳态,不能随滚动次数或曾访问 row 数持续 增长。

9.3 AOT

  • Query/Source public 类型只位于 DataGrid package,不引入 Core/Shared 反向依赖。
  • LocalSource 使用静态泛型 descriptor,并把 typed getter 显式投影为 schema DisplayAccessor;generated IDataMemberAccessor 可通过 FromDataMember<TItem> 复用。自动列只消费该 accessor 并生成 compiled binding,绝不把 FieldId 当作 CLR path。
  • 没有 DisplayAccessor 的远端 schema 字段只支持显式 Column/Binding;PropertyChangedName 与 FieldId 相互独立,binding dispose/recycle 时解除 INotifyPropertyChanged 订阅。
  • 不使用 PropertyInfo.GetValue、Expression.Compile、assembly scan、runtime registration、动态泛型构造或字符串 Binding。
  • 不新增 linker root、trimming suppression 或反射 fallback。
  • Source generator 输出只通过输入源和 generator 更新。

10. 维护不变量

维护 DataGrid 时不得破坏:

  • Query、AppliedQuery、Source、Selection、CurrentRowKey 和 PresentationSnapshot 的单一 ownership。
  • QueryRevision/DataGeneration 与 Source identity/snapshot 的迟到结果隔离。
  • Result 精确切片、key 唯一性、totals 恒定和三索引域映射。
  • DesiredViewport 与 CommittedViewport 隔离,layout 热路径零 I/O。
  • 每个 generation 只有一个 active viewport scope;新 scope 先转移共享 block lease,再取消 orphaned visible/prefetch work。
  • visible queue 优先于 prefetch queue;旧 prefetch 不能占据新 visible target 的排队顺序。
  • Cache、pool、height metadata、selection 和 RowDetails 状态的有界或用户显式增长属性。
  • Container reset 完整性,以及 realized/edit/drag/applied block 的 pin 生命周期。
  • Pinned filter 的唯一目标和 Header -> Indicator -> Flyout relay 对称释放。
  • Row reorder 的 DataGrid session、presenter ghost 与 Source mutation 职责分离。
  • 列宽 solver 不依赖 RowsPresenter 可见性;filler 不掩盖 star 分配。
  • ControlTheme key、Template Part、伪类、Token、Semantic Part 和 Ready 视觉优先级。
  • Light/Dark、Browser/Desktop、SizeType、冻结列、RowDetails 和 nested scrolling 的一致语义。
  • 文档、源码 public surface、Gallery、tests 与 generated LLMS 的一致性。

11. 测试与验证

11.1 Query、Source 与状态机

  • Query validation、equality/hash、no-op、sort policy、schema capability 和 QueryChanged reentrancy。
  • Range/result 边界、snapshot/totals consistency、entry payload、key uniqueness 与 index mapping。
  • 旧 Query/Source/request 迟到、忽略 cancellation、snapshot expiry、Source replacement 和 detach。
  • viewport scope 替换、共享 block lease 转移、orphaned request 协作取消、取消后 inflight 不可复用和 visible/prefetch 优先级。
  • Initial error、refreshing error、stale invalidation、Query/page/group/scroll rollback。
  • LocalSource typed filter/sort/group/page、stable tie-break、collection invalidation、cancellation 与 NativeAOT。

11.2 虚拟化与交互

  • 0、1、短尾、分组、collapse 和 int 上限下 SlotCount/WindowDataCount/child index 精确。
  • Source probe 证明 layout、prepare 和 recycle 的 fetch/wait 计数为 0。
  • 固定/自动行高、RowDetails、extent、anchor、滚轮、惯性、thumb、keyboard 与 ScrollIntoView。
  • 快速 viewport 跨多个 Dispatcher turn 时,旧排队/执行请求收到取消且最终 visible target 不等待过时队列;共享 block 不重复读取。
  • cache miss 保留旧 rows,superseded cancellation 不进入 Error/rollback,成功只提交最终 viewport,失败恢复 offset/slot,且无 null/duplicate/flash reset。
  • block pin、container reset、row/group pool 与 sparse heights 在 10,000 次滚动后不增长。
  • selection/current 跨 range/page/recycle;全量选择不枚举数据;无 bulk capability 时不伪造全量命令成功。
  • row reorder threshold、capture lost、session revalidation、key-relative target、source replacement 和异常清理。
  • nested scrolling 在 DataGrid 边界处正确向外层 ScrollViewer 链接。

11.3 视觉、性能和发布

  • Light/Dark、所有 SizeType、普通/分组 header、empty/data、Title/Footer、上下 pagination、冻结列、scrollbar 与 RowDetails。
  • selected/sorted、hover/focus/disabled、Loading/Refreshing 与 rollback 的结构和截图基线。
  • 100 万本地/逻辑远端行的 sort、首屏、连续滚动、快速跳转、cache、并发、offset lookup 与 allocation。
  • DataGrid detail/recycle/offset/extent 测试保持或加强,不删除或放宽既有行为断言。
  • 运行 DataGrid 专用测试、完整 solution tests、DataGrid performance state verifier、LLMS verify、AOT/trim verify、Gallery NativeAOT publish/startup smoke 和 git diff --check。