我们在书写qml程序的时候经常需要使用到qml的cpp的对象注册,以此来在qml中使用cpp的对象,下面来详细了解一下qml程序的运行逻辑,尤其是qml对象的注册
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qRegisterMetaType<MapData *>("MapData*");
qmlRegisterType<gameTools::MapRenderer>("RpgGame", 1, 0, "MapRenderer");
qmlRegisterType<gameTools::MapLoading>("RpgGame", 1, 0, "MapLoading");
qmlRegisterType<gameTools::Player>("RpgGame", 1, 0, "Player");
qmlRegisterType<gameTools::GameRenderer>("RpgGame", 1, 0, "GameRenderer");
qmlRegisterType<gameTools::Camera>("RpgGame", 1, 0, "Camera");
qmlRegisterType<gameTools::NPC>("RpgGame", 1, 0, "Npc");
qmlRegisterType<CustomTableModel>("RpgGame", 1, 0, "CustomTableModel");
qmlRegisterType<UserConsole>("RpgGame", 1, 0, "UserConsole");
QQmlApplicationEngine engine;
qmlRegisterSingletonType<TeamManager>("RpgGame", 1, 0, "TeamManager",
[](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject *
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return TeamManager::instance();
});
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]()
{ QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("RpgGame", "Main");
return app.exec();
}
下面我们进行详细的拆解
QGuiApplication app(argc, argv);
创建了qt gui程序的核心管理对象,用来初始化qml系统,包括资源和界面,同时接收运行程序的时候传递进来的参数。
需要注意的是这个代码要放到程序main函数的第一位,并且只能有一个这个对象,必须在主线程创建
qmlRegisterType<gameTools::MapRenderer>("RpgGame", 1, 0, "MapRenderer");
如果没有注册时的问题:
-
无法在跨线程信号槽中使用
-
无法在QML中访问
-
无法在QVariant中存储
-
Qt的元对象系统无法识别该类型
engine.loadFromModule("RpgGame", "Main");
return app.exec();
加载rpggame模块下的main.qml
QGuiApplication进行事件循环处理,接收事件处理事件
注册的另外两种方式
一、基本区别对比
| 特性 | setContextProperty |
静态类/Singleton |
|---|---|---|
| 创建时机 | 在 main() 中显式创建 | 第一次访问时创建(延迟初始化) |
| 生命周期 | 由 C++ 控制(main 函数内) | 由 QML 引擎管理 |
| QML 访问方式 | 直接使用变量名 | 通过导入的类型名访问 |
| 内存管理 | 开发者负责 | QML 引擎管理 |
| 可扩展性 | 需要手动添加新属性 | 可以在单例中扩展方法 |
| 线程安全 | 取决于实现 | 提供函数中可实现线程安全 |
一般来说我们直接使用类的单例即可
6. qvariant注意事项
-
类型注册 :自定义类型必须使用
Q_DECLARE_METATYPE和qRegisterMetaType -
类型匹配 :使用
value<T>()时,类型必须完全匹配或可转换 -
性能考虑:频繁的类型检查可能影响性能
-
线程安全:自定义类型需要线程安全地使用
7转换规则
QML var 转换为 QVariant 的详细规则
当 QML 中的 var 类型数据赋值给 C++ 的 QVariant 属性时,Qt 引擎会自动进行类型转换。以下是详细的转换规则:
- **基本类型转换**
property var data1: 42 // int → QVariant(int)
property var data2: 3.14 // double → QVariant(double)
property var data3: "hello" // string → QVariant(QString)
property var data4: true // bool → QVariant(bool)
property var data5: null // null → QVariant() (无效的 QVariant)
property var data6: undefined // undefined → QVariant() (无效的 QVariant)
```
- **对象转换规则**
// JavaScript 对象 → QVariantMap
property var obj: {
"x": 10, // int
"y": 20.5, // double
"name": "test", // QString
"visible": true, // bool
"list": [1, 2, 3], // QVariantList
"nested": { "a": 1 } // QVariantMap
}
// 转换为:QVariantMap<QString, QVariant>
- **数组转换规则**
// JavaScript 数组 → QVariantList
property var arr1: [1, 2, 3] // [int, int, int] → QVariantList
property var arr2: ["a", "b", "c"] // [QString, QString, QString] → QVariantList
property var arr3: [1, "text", true, {x:1}] // 混合类型 → QVariantList
property var arr4: [] // 空数组 → 空 QVariantList
```
4. **Qt 对象类型转换**
import QtQuick 2.15
property var rect: Qt.rect(10, 20, 100, 50) // QRectF → QVariant(QRectF)
property var point: Qt.point(5, 10) // QPointF → QVariant(QPointF)
property var size: Qt.size(200, 300) // QSizeF → QVariant(QSizeF)
property var vector: Qt.vector3d(1, 2, 3) // QVector3D → QVariant(QVector3D)
property var color: Qt.rgba(0.5, 0.5, 0.5, 1) // QColor → QVariant(QColor)
property var date: new Date(2024, 0, 1) // JavaScript Date → QVariant(QDateTime)
```
- **函数类型转换**
// 函数会被转换为 QVariant(QJSValue)
property var func: function() {
return "hello";
}
// 注意:这种转换可能不完全,通常需要特殊处理
-
**注意事项**
-
**性能考虑**:
-
大量数据转换可能影响性能
-
复杂嵌套结构的转换比简单类型慢
- **内存管理**:
-
QVariant 会创建数据的副本
-
大对象转换时注意内存使用
- **类型安全**:
-
始终检查 QVariant 是否包含预期类型
-
使用 `canConvert<T>()` 进行类型检查
- **线程安全**:
-
QVariant 可以在线程间传递
-
但内部的复杂类型可能不是线程安全的
不需要总是返回 QVariant。QML 可以直接支持:
-
基本类型(int, double, bool, QString)
-
Qt 核心类型(QColor, QRect, QPoint, QUrl 等)
-
容器类型(QList, QVariantList, QVariantMap)
-
QObject 派生类指针
需要使用 QVariant 的情况:
-
返回不确定类型的动态数据
-
返回非 QObject 的自定义类型
-
泛型编程或模板函数
-
需要向后兼容旧代码
最佳实践:尽量使用具体的类型,这样代码更清晰、类型更安全、性能更好。只有在必要时才使用 QVariant。