ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

NodeGui MyModel 类 API 全解析:基于 QAbstractTableModel 打造自定义表格模型

NodeGui MyModel 类 API 全解析:基于 QAbstractTableModel 打造自定义表格模型 桌面应用跨平台【免费下载链接】nodeguiA library for building cross-platform native desktop applications with Node.js and CSS . React NodeGui : https://react.nodegui.org and Vue NodeGui: https://vue.nodegui.org项目地址https://gitcode.com/gh_mirrors/no/nodegui点击查看免费下载MyModel是 NodeGui 官方 API 文档中给出的一个典型自定义模型类示例它继承自QAbstractTableModel代表了 Qt Model/View 架构在 Node.js Qt 场景下的标准落地形态。本文以 MyModel API 文档 为骨架完整梳理其继承层次、全部属性与方法签名并结合仓库内src/examples/下 6 个 modelview 示例讲解如何从零实现只读模型、数据格式化、动态刷新、表头、可编辑单元格与 buddy 编辑联动。读完本文你将能基于 NodeGui 写出结构完整、可直接挂在QTableView上的自定义数据模型。继承层次MyModel 在整个模型体系中的位置根据 MyModel API 文档 的 Hierarchy 一节MyModel位于一条明确的继承链上QObject → QAbstractItemModel → QAbstractTableModel → MyModel其中QObject是所有 NodeGui 组件的基类提供对象名、父子关系、属性系统、定时器等基础设施见 QObject 文档QAbstractItemModel定义了模型-视图接口的核心契约rowCount、columnCount、data、index、parentModelIndex等并在 TypeScript 侧通过 NodeDispatcher 把 JS 子类方法桥接到原生 Qt 调用见 QAbstractItemModel 文档 与 源码QAbstractTableModel针对二维表格做了扁平化简化默认index()用createIndex(row, column)生成索引parentModelIndex()恒返回无效索引、sibling()直接落到index()见 源码MyModel则是应用层自定义类只需覆写少量虚方法即可交付完整模型。从 qabstracttablemodel.md 的继承关系可以反推出MyModel在该体系中就是QAbstractTableModel的直接子类、QAbstractItemModel的间接子类——仓库中的 6 个 modelview 示例如 modelview_1_readonly.ts都以class MyModel extends QAbstractTableModel的方式实践了这一结构。构造函数与 native 属性new MyModel(): MyModel构造函数继承自QAbstractItemModel并覆写QObject的构造器。调用super(new addon.QAbstractItemModel())创建原生对象后基类构造器会执行this.native.initNodeDispatcher(dispatcher)见 源码dispatcher 是一个针对index、parent、rowCount、columnCount、data、flags、headerData、setData、buddy九个虚方法的转发开关——当 Qt 侧回调这些虚方法时dispatcher 会将其转换成对 JS 子类同名方法的调用。这解释了为什么自定义模型只需要在 JS 里覆写方法Qt 视图就能读到数据因为原生侧每次查询数据都会反向调用到你的 TypeScript 实现。native属性类型为NativeElement | null继承自Component见 Component 文档保存底层 C 对象的句柄_id()返回该 C 对象内存地址的哈希数字可结合setLogCreateQObject()/setLogDestroyQObject()调试内存泄漏见 MyModel 文档 _id 节。核心契约方法让表格有数据可读模型能工作取决于四个基础虚方法的覆写。以 modelview_1_readonly.ts 的最小实现为例class MyModel extends QAbstractTableModel { rowCount(parent new QModelIndex()): number { return 2; } columnCount(parent new QModelIndex()): number { return 2; } data(index: QModelIndex, role ItemDataRole.DisplayRole): QVariant { if (role ItemDataRole.DisplayRole) { return new QVariant(Row${index.row() 1}, Column${index.column() 1}); } return new QVariant(); } }各方法要点如下rowCount(parent: QModelIndex new QModelIndex()): number返回行数parent默认无效索引对扁平表格模型通常直接返回固定值或数据长度。注意QAbstractItemModel基类默认返回0见 源码不覆写则视图渲染为空。columnCount(parent: QModelIndex new QModelIndex()): number返回列数默认同样为0。data(index: QModelIndex, role ItemDataRole.DisplayRole): QVariant按角色返回单元格数据未处理的分支应返回new QVariant()空变体表示无数据。index(row, column, parent new QModelIndex()): QModelIndex由QAbstractTableModel实现为hasIndex(row, column, parent) ? createIndex(row, column) : new QModelIndex()其中createIndex(row, column)是受保护方法用于构造合法索引见 源码。parentModelIndex(child): QModelIndex对应 Qt 的parent()表格模型直接返回new QModelIndex()无效索引 根节点视图据此认定模型无树形层级。sibling(row, column, index): QModelIndex与hasChildren(parent): booleansibling复用index()hasChildren仅对根索引parent.isValid()为假返回行列数均大于 0否则返回false即叶子单元格永远无子节点见 QAbstractTableModel 源码。flags(index): ItemFlag继承自QAbstractTableModel在基类结果上为有效索引追加ItemFlag.ItemNeverHasChildren。checkIndex(index): boolean用于校验索引合法性行、列、parent 一致性在示例中常配合data/setData的防御式取值。将模型挂到视图只需两行见 modelview_1_readonly.tsconst tableView new QTableView(); const model new MyModel(); tableView.setModel(model); tableView.show();数据格式化用 ItemDataRole 定制单元格外观data()的第二个参数role默认ItemDataRole.DisplayRole是 Qt 的角色机制——同一个单元格在不同角色下返回不同类型的数据。文档中data的签名是data(index: QModelIndex, role ItemDataRole.DisplayRole): QVariantmodelview_2_formatting.ts 演示了在同一switch (role)中按角色分发角色返回值类型效果ItemDataRole.DisplayRole字符串单元格显示文本ItemDataRole.FontRoleQFont的 native 句柄改变该单元格字体如setBold(true)ItemDataRole.BackgroundRoleQBrush的 native 句柄改变单元格背景色如new QBrush(GlobalColor.red)ItemDataRole.TextAlignmentRoleAlignmentFlag位组合改变文本对齐如AlignRight \| AlignVCenterItemDataRole.CheckStateRoleCheckState在单元格内渲染复选框注意QVariant的构造约定原生对象QFont.native、QBrush.native必须用.native包装后传入new QVariant(...)枚举值CheckState.Checked、AlignmentFlag位组合可直接传入。这与 NodeGui 的变体封装规则一致也是示例代码反复出现.native的原因。可编辑模型flags 与 setData 的配合只读模型只需要data可编辑模型还必须覆写flags与setDataflags(index: QModelIndex): ItemFlag { return ItemFlag.ItemIsEditable | super.flags(index); } setData(index: QModelIndex, value: QVariant, role ItemDataRole.EditRole): boolean { if (role ItemDataRole.EditRole) { if (!this.checkIndex(index)) return false; this._data[index.row()][index.column()] value.toString(); return true; } return false; }见 modelview_5_edit.ts。要点setData默认角色为ItemDataRole.EditRole见 文档签名必须返回boolean表示是否接受编辑基类默认实现返回false见 源码不覆写则编辑永远失败。flags()通过super.flags(index)保留基类语义含ItemNeverHasChildren再叠加ItemIsEditable才允许进入编辑模式。编辑产生的数据经 dispatcher 的setData分支从原生回传到 JSthis.setData(new QModelIndex(args[0]), new QVariant(args[1]), args[2])见 源码。建议在写入前用checkIndex(index)校验索引这也是示例的通用做法。表头headerData 与 Orientation表头文本由headerData提供文档签名如下headerData(section: number, orientation: Orientation, role: number): QVariantmodelview_4_headers.ts 的实现headerData(section: number, orientation: Orientation, role: number): QVariant { if (role ItemDataRole.DisplayRole orientation Orientation.Horizontal) { switch (section) { case 0: return new QVariant(first); case 1: return new QVariant(second); case 2: return new QVariant(third); } } return new QVariant(); }section是表头序号orientation为Orientation.Horizontal列头或Orientation.Vertical行头。基类默认返回空QVariant见 源码因此不覆写时表头显示为空。其余调用细节同样经 dispatcher 的headerData分支转发this.headerData(args[0], args[1], args[2]).native。动态数据刷新emitDataChanged 与 begin/end 保护方法当模型数据随时间变化需要主动通知视图重绘。MyModel文档列出了三条相关方法组emitDataChanged(topLeft, bottomRight, roles): void发出数据变更信号通知视图刷新topLeft到bottomRight矩形范围内的单元格见 源码。createIndex(row, column): QModelIndex构造变更范围的索引。结构变更保护对beginInsertRows/endInsertRows、beginRemoveRows/endRemoveRows、beginInsertColumns/endInsertColumns、beginRemoveColumns/endRemoveColumns、beginMoveRows/endMoveRows返回boolean、beginMoveColumns/endMoveColumns、以及beginResetModel/endResetModel——它们在结构变化前后锁定/解锁模型保证视图不会读到中间态见 源码。所有begin*方法都必须与对应的end*成对调用。modelview_3_changingmodel.ts 给出了一个完整的最小刷新回路timerHit(): void { const topLeft this.createIndex(0, 0); this.emitDataChanged(topLeft, topLeft, [ItemDataRole.DisplayRole]); }配合外部驱动setInterval(() { model.timerHit(); }, 1000)见该示例第 10-12 行data()中第 (0,0) 单元格返回的时间字符串便每秒刷新一次——timerHit()在文档中被列为MyModel自身的方法正是这种定时驱动模型通知视图模式的入口。buddy编辑联动与模型间协作buddy(child: QModelIndex): QModelIndex用于把编辑焦点从只读单元格转移到可编辑单元格。modelview_buddy.ts 中当用户尝试编辑 (1,1) 时模型返回 (1,0) 作为伙伴索引让编辑器落到 (1,0) 上buddy(index: QModelIndex): QModelIndex { if (index.row() 1 index.column() 1) { return this.createIndex(1, 0); } return index; }基类buddy()直接调用原生_super_buddy见 源码默认返回自身dispatcher 的buddy分支负责把原生返回值转换回QModelIndex见 源码。QObject 生命周期与调试方法MyModel继承了完整的QObject对象模型方法均见 MyModel 文档 Index生命周期delete()与deleteLater()——立即或延迟事件循环空闲时释放原生对象对象命名与层级objectName(): string/setObjectName(name)、parent(): QObject/setParent(parent)、children(): QObject[]属性系统property(name): QVariant/setProperty(name, value: QVariantType): boolean——用于在 Qt 属性系统上读写自定义元数据类型与调试inherits(className: string): boolean判断对象是否继承自某类dumpObjectInfo()/dumpObjectTree()打印对象信息与对象树是排查对象层级问题的利器定时器startTimer(intervalMS, timerType TimerType.CoarseTimer): number返回定时器 IDkillTimer(timerId)停止定时器timerHit()在定时器触发时被调用QObject层面即文档列出的timerHit方法示例中也被复用作数据刷新钩子。事件监听addEventListener 与事件处理标志MyModel作为EventWidget的后代具备两套事件监听入口见 EventWidget 文档 与 EventListenerOptions 接口信号式监听addEventListenerSignalType(signalType, callback, options?)signalType取自组件的信号接口回调对应信号签名。文档以QPushButton举例const button new QPushButton(); button.addEventListener(clicked, (checked) console.log(clicked));原生事件监听addEventListener(eventType: WidgetEventTypes, callback, options?)回调接收event?: NativeRawPointerQEvent例如button.addEventListener(WidgetEventTypes.HoverEnter, () console.log(hovered));对应地removeEventListener也提供这两个重载用于移除已注册的监听器。事件处理标志方面eventProcessed(): boolean读取当前事件已处理标志setEventProcessed(isProcessed: boolean)将其置位。文档特别说明一旦置位NodeGui 的QObject::event()会返回true并跳过超类event()从而阻止事件继续向上传递——该方法只能在事件处理器内调用才有意义。这一机制是高级事件拦截如拖放、按键吞掉的关键开关具体使用方式可参考 drag-drop 指南。总结MyModel虽名为示例类实则是 NodeGui 表格模型的完整 API 缩影rowCount/columnCount/data撑起只读展示flags/setData开启编辑headerData定制表头emitDataChanged与begin*/end*对维护数据一致性buddy实现编辑联动而QObject/EventWidget的继承方法则提供生命周期、属性、定时器与事件处理的全套底座。仓库中 modelview_1_readonly.ts 到 modelview_buddy.ts 六个示例从简到繁逐步叠加这些能力是理解MyModel每个方法实战语义的最佳配套阅读材料深入其底层可继续研读 QAbstractItemModel.ts 的 dispatcher 桥接逻辑与 QAbstractTableModel.ts 的扁平化实现。赞分享桌面应用跨平台【免费下载链接】nodeguiA library for building cross-platform native desktop applications with Node.js and CSS . React NodeGui : https://react.nodegui.org and Vue NodeGui: https://vue.nodegui.org项目地址https://gitcode.com/gh_mirrors/no/nodegui点击查看免费下载相关推荐如何在5分钟内为Blender安装终极3MF格式支持插件如何在5分钟内为Blender安装终极3MF格式支持插件 想在Blender中无缝处理3D打印专用的3MF文件吗Blender3mfFormat插件正是你需要桌面应用跨平台NocoBase数据表字段类型全解析从基础类型到自定义字段NocoBase数据表字段类型全解析从基础类型到自定义字段 NocoBase作为一款极易扩展的无代码/低代码开发平台其数据表字段类型系统为用户提供了从基础数低代码后端前端人工智能AI 应用工作流自动化3步打造Python类型安全网Pyright自定义类型守卫完全指南3步打造Python类型安全网Pyright自定义类型守卫完全指南 你是否还在为Python动态类型带来的运行时错误头疼当你接手一个大型项目面对复杂的条件开发工具静态分析代码质量上一篇突破多设备同步难题Intel RealSense D400系列相机的精准协同方案下一篇终极指南彻底解决Atmosphere-NX游戏崩溃难题从系统底层到实际修复的完整方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表