【免费下载链接】Launcher
This fork of Prism Launcher adds integrated support for Ely.by accounts.
Ely Prism Launcher 是 Prism Launcher 的增强分支,内置 Ely.by 账号支持。它的左侧主界面把 Minecraft 实例以分组卡片的形式展示:可以折叠、拖拽换组、按名称或游玩时长排序。本文带你从零理解支撑这一体验的InstanceView 分组数据模型架构,包括 InstanceList 数据层、InstanceProxyModel 代理层与 InstanceView 视图层是如何协作的。
三层架构总览:Model-Proxy-View 分工
很多新手会误以为"实例列表"只是一个 QListView。实际上 Ely Prism Launcher 采用了经典的三层结构:
| 层 | 类 | 职责 |
|---|---|---|
| 数据层 | InstanceList(继承QAbstractListModel) | 持有所有实例对象,提供名称、图标、所在分组(GroupRole)等数据 |
| 代理层 | InstanceProxyModel(继承QSortFilterProxyModel) | 负责排序:先按分组名 locale 排序,组内再按用户选定的排序方式 |
| 视图层 | InstanceView(继承QAbstractItemView) | 把扁平的一维数据渲染成带分组标题的二维卡片网格,处理折叠/展开、拖拽换组 |
这种"数据是平的、展示是分组"的设计,正是 Qt Model/View 框架的精髓:数据模型不需要知道任何布局信息,视图也不需要复制一份数据。
组装这三层的代码位于 MainWindow.cpp:
- 第 328 行
proxymodel->setSourceModel(APPLICATION->instances())把 InstanceList 接入代理模型 - 第 332 行
view->setModel(proxymodel)让 InstanceView 消费代理模型 - 第 333~335 行注入分组折叠状态来源,并连接
groupStateChanged信号回写数据层
数据层解析:GroupRole 是分组的关键
InstanceList在标准 Qt 角色之外定义了自定义角色,见 InstanceList.h:
enum AdditionalRoles { GroupRole = Qt::UserRole, // 该实例属于哪个组 InstancePointerRole = ..., // 返回 BaseInstance 指针 InstanceIDRole = ... // 返回实例 ID };data()中case GroupRole直接返回getInstanceGroup(pdata->id())(InstanceList.cpp)- 分组关系存储在
m_instanceGroupIndex(实例 ID → 组名 的 QMap)中,未分组的实例返回空字符串,视图中显示为 "Ungrouped" - 重命名、删除分组(
renameGroup/deleteGroup,InstanceList.cpp)后,会精确地emit dataChanged(..., { GroupRole }),只有分组角色变化才通知视图重排,避免全量刷新
💡 提示:
m_collapsedGroups集合记录了哪些分组处于折叠状态,isGroupCollapsed()供视图在重建布局时恢复折叠记忆。
代理层:InstanceProxyModel 的分组感知排序
InstanceProxyModel 虽然只有 70 多行,却决定了你在界面上看到的顺序:
- 构造时(第 25~31 行)配置
QCollator:开启NumericMode(自然排序,"2" 排在 "10" 前)、忽略大小写、跟随系统 locale lessThan()(第 42~56 行)实现两级排序:- 先比较两者的
GroupRole值——不同组之间按组名 locale 排序,让同组实例天然聚在一起 - 同组内调用
subSortLessThan(),根据设置项InstSortMode在"名称 / LastLaunch(最近启动)/ Playtime(游玩时长)"三种模式间切换
- 先比较两者的
视图层核心:InstanceView 如何把扁平数据变成分组网格
InstanceView是整个模块中最复杂的类(约 1000 行实现)。关键设计有两点:
1. 用 GroupRole 现场"聚合"出视觉分组
布局在updateGeometries()中完成(InstanceView.cpp):
- 遍历模型每一行,读取
InstanceViewRoles::GroupRole(角色枚举定义在 InstanceView.h) - 用
QMap<LocaleString, VisualGroup*>按组名去重聚合——LocaleString是覆写了operator<的 QString 子类,使 QMap 按键按 locale 感知排序,保证分组标题顺序符合当前语言 - 已存在的分组会复制旧对象(
new VisualGroup(old)),从而保留折叠状态;新分组则通过m_fVisibility回调(即数据层的isGroupCollapsed)初始化 - 最后按组顺序累计高度,调用
updateScrollbar()设置滚动条范围
2. VisualGroup:分组内部的行流式布局
每个分组内部的网格排布由 VisualGroup 负责:
VisualGroup持有rows(QList<VisualRow>),每个VisualRow是一行内的若干QModelIndex加上行高、行顶偏移update()(VisualGroup.cpp)按itemsPerRow(可用宽度 ÷ 卡片宽度,随窗口 resize 实时重算)把实例逐个流式填入各行,行高取该组卡片中的最大高度hitScan()用位标志HitResult(TextHit/CheckboxHit/HeaderHit/BodyHit)回答"鼠标点到了分组的哪个部位"——这是折叠箭头点击、组内拖放定位的共同基础drawHeader()绘制分组标题:箭头 + 加粗组名 + 淡色分隔线;空分组名渲染为 "Ungrouped"
折叠/展开与状态回写
点击分组头部箭头时,InstanceView.cpp 的mouseReleaseEvent完成闭环:
- 命中
CheckboxHit时进入ExpandingState/CollapsingState(按下瞬间箭头就已翻转,交互即时) - 释放鼠标时更新
collapsed,emit groupStateChanged(组名, 是否折叠) - 该信号连接到
InstanceList::on_GroupStateChanged,写入m_collapsedGroups持久化记忆
拖拽换组:一个跨层的完整手势
拖动实例卡片放到另一个分组上,涉及三个文件的协作:
- 视图层
startDrag()把选中项渲染成 pixmap 并携带application/x-instanceid类型数据 dropEvent()(InstanceView.cpp)用rowDropPos()解析落点属于哪个VisualGroup- 最终调用数据层
setInstanceGroup(实例ID, 目标组名)(InstanceList.cpp)完成归属变更、计数更新、GroupRole变更通知并落盘
细节与扩展点
- 几何缓存:
m_geometryCache(QCache)缓存行号 → 矩形,visualRect()与indexAt()反复查询时避免重算;任何数据变更都通过scheduleDelayedItemsLayout()合并为一次布局 - 跨组键盘导航:
moveCursor()(InstanceView.cpp)实现了在分组间上下左右的完整键盘移动,且自动跳过已折叠的分组 - 无障碍支持:AccessibleInstanceView 把分组网格暴露给屏幕阅读器,
currentChanged中还会发出QAccessible::Focus事件 - 空态提示:没有实例时
paintEvent绘制 "Welcome! Click Add Instance" 的居中引导卡片 - 彩蛋:视图背景可绘制动态猫(
CatPainter),由设置项TheCat控制
核心文件清单
| 文件 | 说明 |
|---|---|
| InstanceView.h / InstanceView.cpp | 分组视图主体:布局、交互、绘制、拖放 |
| VisualGroup.h / VisualGroup.cpp | 单个分组的行排布、头部绘制与命中检测 |
| InstanceProxyModel.cpp | 分组感知的两级排序与图标装饰 |
| InstanceList.h / InstanceList.cpp | 实例数据模型与分组持久化 |
| InstanceDelegate.h | 单个实例卡片的绘制与行内重命名 |
| MainWindow.cpp | 三层模型的装配点 |
理解了"扁平数据 + GroupRole 聚合 + 视觉行流式布局"这条主线,你就能看懂 Ely Prism Launcher 分组实例视图的全部细节,也能为自己项目的分组列表界面借鉴同一套设计。
【免费下载链接】Launcher
This fork of Prism Launcher adds integrated support for Ely.by accounts.
相关推荐
如何看懂 Ely Prism Launcher 的 macOS 沙盒实现:SecurityBookmarkFileAccess 完整指南
如何看懂 Ely Prism Launcher 的 macOS 沙盒实现:SecurityBookmarkFileAccess 完整指南 Ely Prism L
Ely Prism Launcher实例数据模型:MinecraftInstance与BaseInstance完整解读
Ely Prism Launcher实例数据模型:MinecraftInstance与BaseInstance完整解读 Ely Prism Launcher 是
如何看懂 Ely Prism Launcher 的 POTranslator:PO 翻译文件本地化系统完整解析
如何看懂 Ely Prism Launcher 的 POTranslator:PO 翻译文件本地化系统完整解析 如果你正在研究 Ely Prism Launch
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考