- 桌面应用
- 跨平台
【免费下载链接】nodegui
A 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
QObject 是 Qt 对象模型的基石,也是 NodeGui(用 Node.js + CSS 构建跨平台原生桌面应用的库)中几乎所有组件类的最顶层基类之一。本文以 NodeGui 仓库自动生成的 API 文档 classes/qobject.md 为骨架,结合 TypeScript 封装源码与 C++ 原生绑定实现,系统讲解 NodeGui 中QObject的构造方式、父子对象树、动态属性系统、事件/信号监听、对象标识与生命周期管理、以及计时器 API,帮助读者理解并正确使用这一贯穿所有组件的核心类。
QObject 在 NodeGui 类体系中的位置
在 NodeGui 的 TypeScript 类体系中,QObject的完整泛型签名是QObject<Signals extends QObjectSignals = QObjectSignals>,它继承自EventWidget<Signals>(见 src/lib/QtCore/QObject.ts)。EventWidget又继承自Component,后者是所有组件与布局的最顶层基类,负责持有对原生 C++ 实例(native: NativeElement | null)的引用(见 src/lib/core/Component.ts)。
从 API 文档的 Hierarchy 一节可以看到,QObject处于整棵类树的中枢位置,它派生出大量常用类,包括:
EventWidget(事件基类)→ 进而派生出YogaWidget、QLayout以及所有QWidget系列组件;QAction、QGraphicsEffect、QClipboard、QScreen、QApplication、QMovie、QWindow;- 模型/视图类:
QAbstractItemModel、QItemSelectionModel、QStandardItemModel; - 控件类:
QButtonGroup、QSystemTrayIcon、QShortcut; - 测试辅助类:
CacheTestQObject(对应仓库中的 src/lib/core/test/CacheTestQObject.ts)。
在 C++ 侧,NodeGui 用NObject(继承自QObject并混入EventWidget)作为所有原生对象的基类(见 src/cpp/include/nodegui/QtCore/QObject/nobject.hpp),并通过QObjectWrap(继承自Napi::ObjectWrap,持有QPointer<QObject> instance)完成 N-API 绑定(见 src/cpp/include/nodegui/QtCore/QObject/qobject_wrap.h)。大量通用方法通过QOBJECT_WRAPPED_METHODS_DECLARATION宏批量导出(见 src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h),这也是所有 Qt 组件都能共享QObjectAPI 的原因。
构造 QObject:两种入参形态
QObject的构造函数签名是:
new QObject(nativeElementOrParent?: NativeElement | QObject): QObject构造参数是可选的,支持两种传法,对应 src/lib/QtCore/QObject.ts 中的三种分支逻辑:
- 不传参:创建一个独立的原生
QObject(new addon.QObject()),没有父对象; - 传入一个
QObject:把它当作父对象,创建一个带父对象的新QObject(new addon.QObject(parent.native)),新对象会加入父对象的 children 列表; - 传入
NativeElement(内部使用):在从原生指针恢复封装时,直接把原生实例包装成 JS 对象。
在 C++ 绑定层,QObjectWrap的构造函数同样区分argCount == 0、argCount == 1且参数是External<QObject>、以及参数是父对象包装三种情况(见 src/cpp/lib/QtCore/QObject/qobject_wrap.cpp)。
注意:
QObject本身是一个可用于测试的普通类(仓库测试中直接new QObject(),见 src/lib/QtCore/tests/QObject.test.ts),但在真实应用中通常不需要直接实例化它——更多是创建它的子类组件(如QPushButton、QWidget),然后通过继承来的 API 操作对象。
对象树:parent / children / setParent
Qt 的对象树机制(父子关系决定内存所有权)在 NodeGui 中完整保留,相关的 API 有:
| 方法 | 签名 | 作用 |
|---|---|---|
parent() | (): QObject | 返回当前对象的父对象(若没有父对象则返回null封装的包装) |
setParent(parent) | (parent: QObject): void | 把当前对象挂到指定父对象下;传null可解除父子关系 |
children() | (): QObject[] | 返回直接子对象的数组 |
parent()和children()的返回都经过WrapperCache的getWrapper转换,确保同一个原生QObject指针在 JS 侧始终对应同一个封装实例(见 src/lib/QtCore/QObject.ts)。
setParent的实现比较特殊:在 TypeScript 层它先通过parent.native.__external_qobject__()取出父对象的原生QObject*,再传给原生setParent;在 C++ 宏中setParent支持传入null(解除父子)或External<QObject>(建立父子),见 src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h。
父子关系会带来两条实用能力:
- 对象树诊断:
dumpObjectTree()在控制台打印以当前对象为根的整棵对象树,dumpObjectInfo()打印当前对象的信息摘要,二者直接对应 Qt 的QObject::dumpObjectTree()/dumpObjectInfo(); - 生命周期传导:父对象销毁时其子对象也会一并销毁(Qt 对象树语义),配合下文
delete()与deleteLater()使用。
对象标识与内存调试:native / _id / inherits
native 属性
native: NativeElement | null继承自Component,是连接 JS 世界与 C++ 世界的桥梁,几乎所有方法都通过this.native.xxx()调用原生绑定。调试时可以直接检查native上暴露的绑定方法列表。
_id():定位底层 C++ 对象
_id(): number返回一个标识底层 C++ 对象的数字 ID。文档明确指出:该数字是 C++ 对象内存地址的哈希值,在 C++ 对象存活期内保持有效,可用于配合setLogCreateQObject()/setLogDestroyQObject()调试内存问题。对应实现见 src/lib/QtCore/QObject.ts(构造函数中缓存native.__id__()),C++ 侧由宏中__id__方法通过extrautils::hashPointerTo53bit计算(见 src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h)。
inherits()
inherits(className: string): boolean用于判断当前对象是否继承自指定类名。仓库测试就用到它验证new QObject()确实inherits('QObject')(见 src/lib/QtCore/tests/QObject.test.ts)。它在 C++ 侧直接调用QObject::inherits()(见 src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h)。
动态属性系统:setProperty / property
Qt 允许在运行时给QObject动态挂载任意属性,NodeGui 完整暴露了这对 API:
setProperty(name: string, value: QVariantType): boolean property(name: string): QVariant其中QVariantType是NativeElement | string | string[] | number | boolean | QRect(见 src/lib/QtCore/QVariant.ts)。setProperty返回boolean,表示属性是否设置成功;property返回一个QVariant包装对象,可通过toString()、toInt()、toDouble()、toBool()、toStringList()等方法取回 JS 值(见 src/lib/QtCore/QVariant.ts)。
C++ 侧setProperty先把 JS 值经extrautils::convertToQVariant转成QVariant,再调用QObject::setProperty();property则把取到的QVariant包装成QVariantWrap返回(见 src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h)。
仓库测试验证了典型用法——给objectName属性赋字符串再读回(见 src/lib/QtCore/tests/QObject.test.ts):
const component = new QObject(); component.setProperty('objectName', 'testObjName'); const variant = component.property('objectName'); console.log(variant.toString()); // 'testObjName'对象命名:setObjectName / objectName
setObjectName(objectName: string): void:设置对象名称;objectName(): string:读取对象名称。
对象名在 Qt 中常用于findChild类查找与样式表(QSS)选择器定位,在 NodeGui 的 StyleSheet 中同样可用对象名作为选择器。仓库测试验证了读写闭环:setObjectName('hello')后objectName()返回'hello'(见 src/lib/QtCore/tests/QObject.test.ts)。C++ 实现见 src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h。
事件与信号:addEventListener / removeEventListener
QObject从EventWidget继承了完整的监听体系,这也是 NodeGui 事件模型的核心。addEventListener有两种重载形态:
形态一:监听 Qt 信号(signal)
addEventListener<SignalType extends keyof Signals>( signalType: SignalType, callback: Signals[SignalType], options?: EventListenerOptions ): voidsignalType取自对应类的 Signals 接口。例如QObjectSignals定义了objectNameChanged信号(见 src/lib/QtCore/QObject.ts)。文档给出的示例:
const button = new QPushButton(); button.addEventListener('clicked', (checked) => console.log('clicked')); // clicked 是 QPushButtonSignals 接口中的一个信号C++ 侧该信号通过QOBJECT_SIGNALS_ON_TARGET宏把QObject::objectNameChanged转发到 Node 事件发射器(见 src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h)。
形态二:监听 Qt 事件(QEvent)
addEventListener( eventType: WidgetEventTypes, callback: (event?: NativeRawPointer<'QEvent'>) => void, options?: EventListenerOptions ): voidconst button = new QPushButton(); button.addEventListener(WidgetEventTypes.HoverEnter, () => console.log('hovered'));WidgetEventTypes枚举覆盖了 Qt 的全部事件类型(MouseMove、KeyPress、Paint、Resize等上百个,见 src/lib/core/EventWidget.ts)。
EventListenerOptions 选项:{ afterDefault?: boolean }。当监听 QEvent 且afterDefault: true时,回调会在基类默认事件处理之后执行;默认情况下回调在基类::event()之前执行(见 src/lib/core/EventWidget.ts)。
removeEventListener与addEventListener形态一一对应,用于注销监听。值得注意的是,内部实现会在监听器全部移除后调用native.unSubscribeToQtEvent,停止向 Node 侧转发该事件(见 src/lib/core/EventWidget.ts)。
事件处理标记:eventProcessed / setEventProcessed
这对方法同样继承自EventWidget:
eventProcessed(): boolean:读取当前事件是否已被标记为“已处理”;setEventProcessed(isProcessed: boolean): void:在事件处理器内部调用,标记当前事件已处理完毕。
文档明确指出其语义:当标记为已处理时,NodeGui 的QObject::event()会返回true且不再调用超类的event(),从而阻止该事件被进一步处理。该标记只应在事件处理器中调用才有意义。
EventWidget的实现通过_isEventProcessed私有字段维护状态,并在分发事件时保存/恢复旧值以支持递归事件分发(见 src/lib/core/EventWidget.ts)。典型场景:在MouseMove等事件回调里调用setEventProcessed(true),阻止事件继续传播到默认处理逻辑。
生命周期管理:delete / deleteLater
delete(): void:立即销毁底层 C++ 对象(对应宏中deleteObject对QObject*直接delete,见 src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h);deleteLater(): void:安排对象在事件循环回到事件处理时再销毁(Qt 的QObject::deleteLater()语义,见 src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h)。
deleteLater比delete更安全:它避免了在事件处理过程中直接删除对象可能引发的崩溃。关于 NodeGui 中对象回收与 WrapperCache 的关系,可进一步阅读 website/docs/development/wrapper_caching.md 与 website/docs/guides/understanding-memory.md。
计时器:startTimer / killTimer
startTimer(intervalMS: number, timerType?: TimerType): number killTimer(timerId: number): voidstartTimer以毫秒为单位启动一个定时器,返回定时器 ID(后续用该 ID 停止定时器);timerType默认TimerType.CoarseTimer(见 src/lib/QtCore/QObject.ts);killTimer(timerId)根据 ID 停止对应定时器。
TimerType枚举定义(见 src/lib/QtEnums/TimerType/index.ts):
| 枚举值 | 数值 | 语义 |
|---|---|---|
PreciseTimer | 0 | 尽可能精确地按时触发 |
CoarseTimer | 1 | 允许一定的误差以省电(默认值) |
VeryCoarseTimer | 2 | 允许更大误差,最省电 |
C++ 侧startTimer把timerType从 JS number 转换为Qt::TimerType后调用QObject::startTimer()(见 src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h)。定时器触发后会产生Timer事件(WidgetEventTypes.Timer),可配合addEventListener(WidgetEventTypes.Timer, ...)接收。
小结:QObject API 速查表
| 类别 | API | 关键说明 |
|---|---|---|
| 构造 | new QObject(parent?) | 可选父对象;内部也可由原生指针恢复 |
| 对象树 | parent()/setParent()/children() | 父子关系驱动生命周期,返回均走 WrapperCache |
| 标识 | native/_id()/inherits() | _id为内存地址哈希,可配合日志调试内存 |
| 属性 | setProperty()/property() | 动态属性,值经QVariantType↔QVariant转换 |
| 命名 | setObjectName()/objectName() | 支持 QSS 选择器与查找 |
| 事件 | addEventListener()/removeEventListener() | 信号与 QEvent 双形态,支持afterDefault |
| 事件标记 | eventProcessed()/setEventProcessed() | 在事件处理器内阻止后续默认处理 |
| 生命周期 | delete()/deleteLater() | 立即销毁 vs 事件循环安全销毁 |
| 计时 | startTimer()/killTimer() | 返回定时器 ID,TimerType控制精度 |
| 诊断 | dumpObjectTree()/dumpObjectInfo() | 打印对象树/对象信息 |
QObject是理解 NodeGui 一切组件行为(对象树管理、动态属性、信号事件、内存生命周期)的钥匙。掌握了它,就掌握了 NodeGui 中所有QWidget、QLayout、QAction等组件共用的基础能力;想要深入了解事件在 C++ 与 JS 之间的流转机制,可继续阅读 website/docs/development/signal_and_event_handling.md,想了解封装复用与对象缓存的原理,则可参考 website/docs/development/wrapper_caching.md。
- 桌面应用
- 跨平台
【免费下载链接】nodegui
A 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
相关推荐
NodeGui QTreeWidgetSignals 信号接口完全指南:从 Qt 树控件事件到 Node.js 回调
NodeGui QTreeWidgetSignals 信号接口完全指南:从 Qt 树控件事件到 Node.js 回调 QTreeWidgetSignals 是
桌面应用跨平台Cytoscape.js 事件系统完全指南:事件对象、事件冒泡与全量事件类型详解
Cytoscape.js 事件系统完全指南:事件对象、事件冒泡与全量事件类型详解 Cytoscape.js 是用于图可视化与图分析的 JavaScript 库,
数据可视化bootstrap-datepicker 事件系统完全指南:show、hide、changeDate 等事件的对象、时机与实战用法
bootstrap datepicker 事件系统完全指南:show、hide、changeDate 等事件的对象、时机与实战用法 bootstrap date
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考