news 2026/10/18 3:13:05

如何看懂 Ely Prism Launcher 的分组实例视图?InstanceView 数据模型完整架构解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何看懂 Ely Prism Launcher 的分组实例视图?InstanceView 数据模型完整架构解析

【免费下载链接】Launcher

This fork of Prism Launcher adds integrated support for Ely.by accounts.

项目地址:https://gitcode.com/GitHub_Trending/ely/Launcher
点击查看免费下载

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 多行,却决定了你在界面上看到的顺序:

  1. 构造时(第 25~31 行)配置QCollator:开启NumericMode(自然排序,"2" 排在 "10" 前)、忽略大小写、跟随系统 locale
  2. 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完成闭环:

  1. 命中CheckboxHit时进入ExpandingState/CollapsingState(按下瞬间箭头就已翻转,交互即时)
  2. 释放鼠标时更新collapsed,emit groupStateChanged(组名, 是否折叠)
  3. 该信号连接到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.

项目地址:https://gitcode.com/GitHub_Trending/ely/Launcher
点击查看免费下载
上一篇:Gradio Textbox 前端组件演进全解:从 CHANGELOG 到源码实现的 24 个关键版本
下一篇:如何快速上手React Hook Form:5分钟内构建你的第一个表单

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 3:06:47

acados嵌入式NMPC实战:Python建模、C代码生成与RTI部署

简介&#xff1a;acados 是一套面向非线性最优控制&#xff08;NMPC&#xff09;的快速嵌入式求解器&#xff0c;适合机器人、自动驾驶、无人机、过程控制等对实时性有严格要求的场景&#xff0c;可用于解决大规模非线性优化问题的在线求解。资源提供 C、Python、MATLAB 与 Oct…

作者头像 李华
网站建设 2026/10/18 3:06:39

WinUSB上位机程序原理与实战:自定义USB通信开发指南

简介&#xff1a;这是一份基于C#开发的WinUSB上位机通信程序&#xff0c;面向嵌入式开发、USB设备调试及Windows驱动初学者&#xff0c;解决普通应用程序直连USB设备时缺乏标准接口、需定制驱动的痛点。资源包共58个文件&#xff0c;含12个核心C#源码&#xff08;如DeviceManag…

作者头像 李华
网站建设 2026/10/18 3:05:20

超市偷窃检测数据集与YOLO训练实战:从数据标注到模型部署

做安防监控算法这几年&#xff0c;我接过不少门店防损的项目。刚开始大家都想用目标检测直接“抓小偷”&#xff0c;真做起来才发现&#xff0c;偷窃检测根本不是“检测一个人”那么简单&#xff0c;它得在视频流里把“掏东西塞进包”“把货架商品拆开藏身上”“顺手牵羊放进购…

作者头像 李华
网站建设 2026/10/18 3:03:59

G-Helper电池限制不生效?按这3个场景自查修复

G-Helper电池限制不生效&#xff1f;按这3个场景自查修复 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenbook, Expertbook…

作者头像 李华
网站建设 2026/10/18 3:03:34

人工智能模型是软件吗?从底层实现看两者本质区别

“人工智能模型是软件吗&#xff1f;”——这个问题我最早被问到&#xff0c;是在某次技术分享的Q&A环节。提问的是一名刚从嵌入式岗转过来的同学&#xff0c;他的疑问很直接&#xff1a;既然模型跑在代码框架里&#xff0c;推理时调用的还是那些函数&#xff0c;凭什么说它…

作者头像 李华