Qt 5.12文档翻译:在C++中与QML对象进行交互,Interacting with QML Objects from C++
所有 的QML 对象,无论它们是 由引擎内部实现的,还是 由第三方代码定义的 , 都是继承自 QObject 的类型。 这就意味着, QML引擎能够利用Qt 的 元对象系统 来动态地实例化任意QML 对象类型,并对创建出来的对象进行操作 。
这样,就可以在C++代码中创建QML 对象了,可以用于创建一个需要显示出来的可见QML 对象,也可以用于将某个不可见的QML 对象集成到C++程序中。一旦某个QML对象被创建出来了,就可以在C++代码中对它进行操作,以便读取或写入它的属性、调用它的方法、以及接收信号通知。
可通过 QQmlComponent 或 QQuickView 来载入一个QML 文档。 QQmlComponent 会将 QML文档载入 为一个C++对象,使得日后 可通过C++代码对它进行修改。 QQuickView 也会实现相同的功能, 不过,由于 QQuickView 是继承自 QWindow 的类,所以 , 被载入的对象还会同时 被渲染到屏幕上; QQuickView 通常用于 将可显示的QML 对象集成到程序的界面中去。
例如,假设 有以下的某个 MyItem.qml 文件:
import QtQuick 2.0
Item {
width: 100 ; height: 100
}
这个 QML文档, 可使用以下C++代码通过 QQmlComponent 或 QQuickView 来载入。如果使用 的是 QQmlComponent ,就需要调用 QQmlComponent::create ()来创建该组件的一个实例,而如果使用 QQuickView 的话 就会自动创建该组件的一个实例,并且日后可通过 QQuickView::rootObject ()来访问它:
// 使用QQmlComponent QQmlEngine engine; QQmlComponent component(&engine, QUrl ::fromLocalFile( "MyItem.qml" )); QObject * object = component.create(); ... delete object ; |
// Using QQuickView QQuickView view; view.setSource( QUrl ::fromLocalFile( "MyItem.qml" )); view.show(); QObject * object = view.rootObject(); |
此处 的 object ,即是 刚被创建的 MyItem.qml 组件的一个实例。现在 ,可以使用 QObject::setProperty ()或 QQmlProperty::write ()来修改这个元素的属性:
object ->setProperty( "width" , 500 );
QQmlProperty ( object , "width" ).write( 500 );
QObject::setProperty() 和 QQmlProperty::write() 之间有一个差别,那就是,后者,在设置属性值的同时,还会删除对应的绑定关系。例如,假设 以上 对其赋值的宽度( width )属性,还 与高度( height )属性绑定到了一起:
width: height
那么 ,假如在调用了 object->setProperty("width", 500) 之后,这个 Item 对象的高度( height )发生了变化,那么,其宽度( width )将会再次变化,因为它们之间的绑定关系仍然处于活跃状态。然而,如果 是在调用了 QQmlProperty(object, "width").write(500) 之后,高度( height )发生了变化,那么,宽度( width )将不会跟着变化,因为它们之间的绑定关系已经消失。
或者,妳也可以将 此处 的 对象转换成它的实际类型, 并调用它的方法 ,以确保 编译期安全 性 。 在这个示例中, MyItem.qml 的基类对象是 Item ,而它又是由 QQuickItem 类来定义的:
QQuickItem *item = qobject_cast< QQuickItem *>( object );
item->setWidth( 500 );
妳还可以连接 到该组件的任意信号或调用该组件的方法,对应的接口是 QMetaObject::invokeMethod ()和 QObject::connect ()。参考下文 中的 调用QML 的 方法 和 连接 到 QML 的信号 ,以了解更多细节。
QML组件 ,本质上就是对象树,其中的子代对象拥有相邻节点和各自的子代对象。QML 组件 的子代对象,可通过 QObject::findChild ()方法和 QObject::objectName 属性来定位到。例如,假设 MyItem.qml 中的根元素拥有一个 Rectangle 子代元素:
import QtQuick 2.0
Item {
width: 100 ; height: 100
anchors.fill: parent
objectName: "rect"
}
}
那么,可以按照以下代码来定位到这个子代元素:
QObject *rect = object ->findChild< QObject *>( "rect" );
if (rect)
rect->setProperty( "color" , "red" );
注意 ,对于一个对象,可能会有多个拥有相同对象名字( objectName )的子代对象。例如, ListView , 会为它的代理(delegate)创建多个实 例 ,因此 ,如果其代理声明了某个特定的对象名字(objectName),那么,这个 ListView 就会拥有多个具有相同对象名字( objectName )的子代对象。 在这种情况下,可使用 QObject::findChildren ()来找到所有拥有对应对象名字( objectName )的子代对象。
警告 :尽管我们提供了这种能力,使得能够在C++代码中访问QML 对象并且对它们进行操作,但是,我们并不建议这么做,除非是出于测试目的和原型开发目的。将QML 与C++整合起来使用的做法,所具有的其中一个优点就是,能够以 QML 来实现界面,让它与C++侧的逻辑和数据后端相分离。如果在C++侧直接对QML 进行操作,这种分离就失效了。一旦这么做了之后,要想改动QML 界面而又不影响对应的C++代码,就很难做到了。
在QML 对象中声明的任意属性,都会自动成为在C++中可访问的属性。对于以下这个QML 元素:
// MyItem.qml
import QtQuick 2.0
Item {
property int someNumber: 100
}
可通过 QQmlProperty , 或 QObject::setProperty ()和 QObject::property ()配套使用 , 来 对 someNumber 属性的值进行设置和读取:
QQmlEngine engine;
QQmlComponent component(&engine, "MyItem.qml" );
QObject * object = component.create();
qDebug () << "Property value:" << QQmlProperty ::read( object , "someNumber" ).toInt();
QQmlProperty ::write( object , "someNumber" , 5000 );
qDebug () << "Property value:" << object -> property ( "someNumber" ).toInt();
object ->setProperty( "someNumber" , 100 );
妳应当总是使用 QObject::setProperty ()、 QQmlProperty 或 QMetaProperty::write ()来修改某个QML 属性的值, 以确保 QML引擎知道 发生了属性值的改变。例如,假设 妳 的项目中有某个自定义类型 PushButton ,它拥有一个 buttonText 属性,在内部反射的是 m_buttonText 这个成员变量的值。那么 , 不应当 按照以下代码 来直接修改该成员变量的值:
//不应当这样写
QQmlComponent component(engine, "MyButton.qml" );
PushButton *button = qobject_cast< PushButton *>(component.create());
button->m_buttonText = "Click me" ;
以上代码中,直接修改了成员变量的值,这样就导致绕过了 Qt 的 元对象系统 ,使得 QML引擎 不知道发生了属性值的变动。 这就意味着,那些绑定 到了 buttonText 的属性值不会被更新,同时任何的 onButtonTextChanged 处理器也都不会被调用。
所有 的 QML方法 ,都被暴露给了元对象系统,因而 可以在C++代码中通过 QMetaObject::invokeMethod ()来调用。对于QML 的方法参数和返回值,在C++中都会被转换成 QVariant 值来处理。
以下是一个利用 QMetaObject::invokeMethod ()来调用QML 方法的C++程序:
QML |
// MyItem.qml import QtQuick 2.0 Item { function myQmlFunction(msg) { console.log( "Got message:" , msg) return "some return value" } } |
C++ |
// main.cpp QQmlEngine engine; QQmlComponent component(&engine, "MyItem.qml" ); QObject * object = component.create(); QVariant returnedValue; QVariant msg = "Hello from C++" ; QMetaObject ::invokeMethod( object , "myQmlFunction" , Q_RETURN_ARG( QVariant , returnedValue), Q_ARG( QVariant , msg)); qDebug () << "QML function returned:" << returnedValue.toString(); delete object ; |
注意,在调用 QMetaObject::invokeMethod ()时,传递给 Q_RETURN_ARG ()和 Q_ARG ()的参数,必须指定为 QVariant 类型,因为,这是在QML 方法中针对参数和返回值的通用数据类型。
QML 中的所有信号,都是可以在C++代码中使用的,所以 可以像对待普通的Qt C++信号那样,使用 QObject::connect ()来连接。 反过来,任何的 C++信号 ,都可以被QML对象用 信号处理 器 来接收到。
以下的QML组件中,带有一个名为 qmlSignal 的信号,它在发射时,会带有一个字符串类型的参数。 在代码中,使用 QObject::connect ()将这个信号连接到了一个C++对象的信号槽上,这样,每当 qmlSignal 信号被发射时, cppSlot() 方法都会被调用:
// MyItem.qml import QtQuick 2.0 Item { id: item width: 100 ; height: 100 signal qmlSignal( string msg) anchors.fill: parent onClicked: item.qmlSignal( "Hello from QML" ) } } |
class MyClass : public QObject { Q_OBJECT public slots: void cppSlot( const QString &msg) { qDebug () << "Called the C++ slot with message:" << msg; } }; int main( int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView view( QUrl ::fromLocalFile( "MyItem.qml" )); QObject *item = view.rootObject(); MyClass myClass; QObject ::connect(item, SIGNAL(qmlSignal( QString )), &myClass, SLOT(cppSlot( QString ))); view.show(); return app. exec (); } |
如果 将某个 QML对象类型用作信号 的参数,那么,应当将它的类型写作 var , 而在C++代码中,应当以 QVariant 类型来接收它的值:
// MyItem.qml import QtQuick 2.0 Item { id: item width: 100 ; height: 100 signal qmlSignal( var anObject) anchors.fill: parent onClicked: item.qmlSignal(item) } } |
class MyClass : public QObject { Q_OBJECT public slots: void cppSlot( const QVariant &v) { qDebug () << "Called the C++ slot with value:" << v; QQuickItem *item = qobject_cast< QQuickItem *>(v.value< QObject *>()); qDebug () << "Item dimensions:" << item->width() << item->height(); } }; int main( int argc, char *argv[]) { QApplication app(argc, argv); QQuickView view( QUrl ::fromLocalFile( "MyItem.qml" )); QObject *item = view.rootObject(); MyClass myClass; QObject ::connect(item, SIGNAL(qmlSignal( QVariant )), &myClass, SLOT(cppSlot( QVariant ))); view.show(); return app. exec (); } |
HxLauncher: Launch Android applications by voice commands