前言上一篇中学习了两种方法中的第一种方法——如何实现可以被QML访问的 C++ 类,那么这一节我们来学习另外一种方法,QML上下文属性设置以及如何在 C++ 中访问Qml。话不多说,一起学习吧。QML上下文属性设置如果希望直接嵌入一些 C++ 数据来给QML使用应该如何实现呢?这里就用到了QQmlContext类,QQmlContext类定义了Qml引擎内的上下文,上下文允许将数据暴露给由Qml引擎实例化的Qml组件。每个QQmlContext包含一组属性,允许以名称将数据显式地绑定到上下文。通过调用QQmlContext::setContextProperty()来定义和更新上下文属性。看一下接口是如何定义的: void QQmlContext::setContextProperty(const QString &name, const QVariant &value) 简单的上下文属性,对应的值为QVariant类型。 void QQmlContext::setContextProperty(const QString &name, QObject *value) 相对来说稍微复杂一些,QObject*对象类型。 我们分别通过两个例程来看一下复杂和简单的上下文属性是如何设置的。 设置简单的上下文属性新建一个Qt quick工程,然后我们来修改main.cpp文件 #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQuickView> #include <QQmlContext> int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QQuickView view; view.rootContext()->setContextProperty("Data", QString("设置Qml上下文属性")); view.setSource(QUrl(QStringLiteral("qrc:///main.qml"))); view.show(); return app.exec(); } 导入相关的类,设置了一个QString类型的属性,这个Data的值可以由加载Qml组件的 C++ 程序直接设置,使用的就是setContextProperty()函数。然后,我们就可以直接在qml文件中直接使用Data了,不需要导入任何模块。 import QtQuick 2.9 import QtQuick.Window 2.2 Rectangle { width: 640 height: 480 color: "lightgray" Text { id: text anchors.centerIn: parent text: Data } } 注意,因为已经设置了view.show()了,所以这里不需要在Window对象下定义控件了,否则会出来两个界面。还有就是关于调用这个属性的名称,开头要大写。看一下实现效果。 设置对象为上下文属性还是以上一节中的mixing例程,我们继续学习,顺便看一下与上一种用法的区别。先来看一下main.cpp文件。如何设置对象为上下文属性呢? #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQuickView> #include <QQmlContext> #include "mixing.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); Mixing mixing; QQuickView view; view.rootContext()->setContextProperty("mixing", &mixing); view.setSource(QUrl(QStringLiteral("qrc:///main.qml"))); view.show(); return app.exec(); } 可以看到,Mixing mixing从堆上分配了一个mixing对象,然后通过setContextProperty设置为上下文的属性,名称为mixing.qml文件中如何使用呢? import QtQuick 2.9 import QtQuick.Window 2.2 //import an.qt.Mixing 1.0 Rectangle { id:root visible: true width: 640 height: 480 MouseArea{ anchors.fill: parent acceptedButtons:Qt.LeftButton | Qt.RightButton; onClicked: { mixing.start() mixing.number = 10 } } Connections{ target: mixing onColorChanged: { root.color = color mixing.stop(color) } onNumberChanged:{ console.log("new ball number is", mixing.number) // 10 } } } 事实上,因为我们去掉了qmlRegisterType() 调用,所以在 main.qml中不能再访问Mixing类了,比如说不能通过类名来引用它定义的BALL_COLOR枚举类型了,否则会出现如下报错: ReferenceError: Mixing is not defined 从上面的代码可以看出,除了枚举类型的值不可以调用之外,其他的属性,以及关联的信号和槽函数,以及用Q_INVOKABLE 宏修饰的方法都是可以继续使用的。最后我们还需要修改Mixing中start()函数,将mixing.h中的start()参数去掉。变为void start()。 Mixing.cpp文件中修改start函数: void Mixing::start() { QColor color; qDebug() << "start"; //switch (ballColor) { // case BALL_COLOR_BLUE: // color = Qt::blue; // break; // case BALL_COLOR_GREEN: // color = Qt::green; // break; // case BALL_COLOR_YELLOW: // color = Qt::yellow; // break; //} //emit colorChanged(color); emit colorChanged(Qt::blue); } 运行一下,效果如下: C++ 访问Qml的的属性、函数和信号的。同样的,既然可在Qml中访问 C++ 中属性和方法,肯定也是可以在C++ 访问Qml的的属性、函数和信号的。在 C++ 中加载Qml文件可以用QQmlComponent或QQuickView,然后就可以在 C++ 中访问Qml对象了。QQuickView提供了一个显示用户界面的窗口,而QQmlComponent没有。在C++中访问Qml中的属性因为我们还没有用过QQmlComponent,所以这一小节的例程我们来使用QQmlComponent。实现的原理也很简单,还是要基于Qt最核心的一个基础特性,就是元对象系统。在qml文件中对Window对象添加一个Rectangle,设置objectName为“rect”,这个值是为了在C++中能够找到这个Rectangle。 import QtQuick 2.9 import QtQuick.Window 2.2 import an.qt.Mixing 1.0 Window { id:root visible: true width: 640 height: 480 Rectangle { objectName: "rect" anchors.fill: parent } MouseArea{ anchors.fill: parent acceptedButtons:Qt.LeftButton | Qt.RightButton; onClicked: { mixing.start() mixing.number = 10 qmlSignal("这是qml文件中的qml信号") } } Mixing{ id:mixing } } 在main.cpp文件中加载Qml文件并进行组件实例化后,就可以在 C++ 中访问、修改这个实例的属性值了。 #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QtQml> #include "mixing.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); qmlRegisterType<Mixing>("an.qt.Mixing", 1, 0, "Mixing"); //QQmlApplicationEngine engine; //engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); //if (engine.rootObjects().isEmpty()) // return -1; QQmlEngine engine; QQmlComponent compontext(&engine, QUrl(QStringLiteral("qrc:///main.qml"))); QObject *object = compontext.create(); qDebug() << "width value is" << object->property("width").toInt(); object->setProperty("width", 320); qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt(); QQmlProperty::write(object, "height", 240); QObject *rect = object->findChild<QObject*>("rect"); if(rect) { rect->setProperty("color", "orange"); } return app.exec(); } 使用了QObject::property()/setProperty()来读取、修改width属性值。然后还可以用QQmlProperty::read()/write()来读取、修改height属性值。 另外,如果某个对象的类型是QQuickItem,例如QQuickView::rootObject()的返回值,这时就可以使用QQuickItem::width/setWidth()来访问、修改width属性值了。 有时候,QML组件是一个复杂的树型结构,包含兄弟组件和孩子组件,我们可以使用QObject::findchild()/findchildren()来查找。如上面的代码中,我们查找名字为rect的孩子组件,如果找到的话就将其颜色设置为橘黄色。 在C++中访问Qml中的函数与信号在 C++ 中,使用 QMetaObject::invokeMethod() 可以调用QML中的函数,它 是个静态方法,其函数原型如下: bool QMetaObject::invokeMethod(QObject * obj, const char * member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument( 0 ), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()) [static] 它的返回值如果为true则表明调用成功,返回false,则说明参数不正确或被调用函数名错误。我们重点关注它的前四个参数。
其他的就是可选的可以传递给被调用方法的参数。注意:必须使用Q_ARG() 宏来声明函数参数,用Q_RETURN_ARG() 宏来声明函数返回值。 对于要传递给被调用方法的参数,使用QGenericArgument来表示,原型如下: QGenericArgument Q_ARG(Type, const Type & value) 对于返回类型来说,使用QGenericReturnArgument表示,你可以使用 Q_RETURN_ARG 宏来构造一个接收返回指的参数,原型如下: QGenericReturnArgument Q_RETURN_ARG(Type, Type & value) 上述是方法的调用,信号又是如何传递到 C++ 中的呢?更简单了,使用使用 QObject::connect() 可以连接QML中的信号,connect()共有四个重载函数,它们都是静态函数。必须使用SIGNAL()宏来声明信号,SLOT()宏声明槽函数。 使用 QObject::disconnect() 可以解除信号与槽函数的连接。 在qml文件中添加一个信号和方法。 import QtQuick 2.9 import QtQuick.Window 2.2 import an.qt.Mixing 1.0 Window { id:root visible: true width: 640 height: 480 signal qmlSignal(string message) onQmlSignal: console.log("这是一个信号:",message) function qmlFunction(parameter) { console.log("这是一个方法:",parameter) return "function from qml" } Rectangle { objectName: "rect" anchors.fill: parent } MouseArea{ anchors.fill: parent acceptedButtons:Qt.LeftButton | Qt.RightButton; onClicked: { mixing.start() mixing.number = 10 qmlSignal("这是qml文件中的qml信号") } } Mixing{ id:mixing } } 先看信号,定义了qmlSignal信号,参数为message,当单击时,会输出message的值。用法很简单。再看方法,首先方法的名字是qmlFunction,它的参数为parameter,当调用此方法时,会输出parameter的值,同时返回"function from qml"。 修改main.cpp文件,调用信号和方法。 #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QtQml> #include "mixing.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); qmlRegisterType<Mixing>("an.qt.Mixing", 1, 0, "Mixing"); //QQmlApplicationEngine engine; //engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); //if (engine.rootObjects().isEmpty()) // return -1; QQmlEngine engine; QQmlComponent compontext(&engine, QUrl(QStringLiteral("qrc:///main.qml"))); QObject *object = compontext.create(); qDebug() << "width value is" << object->property("width").toInt(); object->setProperty("width", 320); qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt(); QQmlProperty::write(object, "height", 240); QObject *rect = object->findChild<QObject*>("rect"); if(rect) { rect->setProperty("color", "orange"); } QVariant returnedValue; QVariant message = "Hello from c++"; QMetaObject::invokeMethod(object,"qmlFunction", Q_RETURN_ARG(QVariant,returnedValue), Q_ARG(QVariant,message)); qDebug() << "returnedValue is " << returnedValue.toString(); Mixing mixing; QObject::connect(object, SIGNAL(qmlSignal(QString)), &mixing, SLOT(cppSlot(QString))); return app.exec(); } 定义一个QVariant类型的值来接收qmlFunction方法返回的值,定义message的值在调用qmlFunction方法时作为参数传进去。通过invokeMethod函数调用qmlFunction方法,这里大家注意看各个参数的含义,然后打印出返回值。 再通过connect函数将qmlSignal信号和cppSlot槽函数连接起来,所以我们还得在mixing.h中定义cppSlot槽函数。 public slots: //void start(BALL_COLOR ballColor); void start(); void cppSlot(const QString &message) { qDebug() << "这是一个Qml信号:" << message; } 最后,让我们来看一下效果。 最后我们把这两节一直用到的这个例程给大家,大家可以参考一下。 总结至此,我们关于树莓派的教程就全部更新完毕,最后给大家更新一篇如何通过源码的方式安装Qt,这个稍微有点难度,大家可以作为选读的章节进行尝试。 |