mix QML and C++

文章属于原创,转载请注明出处,有一部份内容保持英文,方便理解  大笑 

      我们知道 ,通过Qt Declarative module ,C++可以动态创建和操纵QML的组件(cpmponents) ,

我们利用这些API使用C++来拓展我们的QML程序,反过来也可以将QML嵌入到你的C++程序中。

通过Qt的元对象系统( Qt‘s metaobject system),我们可以利用Qt中的信号与槽机制使QML 和 Qt

objects 相互通信(communicate)。另外,QML plugins可以用来创建可复用的QML 组件。 

使用C++和QML混合编程可能出于以下原因:You may want to mix QML and C++ for a number of reasons. For example:

  • To use functionality defined in a C++ source (for example, when using a C++ Qt-based data model,
  • or calling functions in a third-party C++ library)
  • To access functionality in the Qt Declarative module (for example, to dynamically generate images usingQDeclarativeImageProvider)
  • To write your own QML elements (whether for your applications, or for distribution to others

首先我们介绍下Qt Declarative module 中几个核心的类

QDeclarativeEngine: QML的执行环境,负责解释执行QML代码。每一个程序都应至少包含一个engine 实例 ;

QDeclarativeComponent:创建一个封装了QML文件的组件;

QDeclaraContext:提供了一个允许C++程序将数据暴露给由QDeclaraEngine 创建的 QML 组件的上下文环境;

例如:

 QDeclarativeEngine engine;
 QDeclarativeComponent component(&engine, QUrl::fromLocalFile("MyRectangle.qml"));
 QObject *rectangleInstance = component.create();

 // ...
 delete rectangleInstance;
 //QObject * QDeclarativeComponent::create (QDeclarativeContext * context = 0 ) [virtual]

QML我们如何与C++一起使用

我们如何通过C++拓展我们的QML呢?

1.我们可以通过C++动态创建一个QML组件并且我们能够对它进行一些操作

2.我们可以使一个C++对象(比如:继承自QObject的类的对象)和他的属性作为一个QML的组件

3.定义一个QML组件


Loading QML Components from C++

QDeclarativeComponent 将一个QML组件(文件)装载成一个C++  Object(对象)。使用QDeclarativeComponent需要调用QDeclarativeComponent::create()来创建一个组件的实例 ;QDeclarativeView同样也可以装载一个QML组件(文件),但是QDeclarativeView也继承自QGraphicsView ,所以他可以将QML组件或文件显示出来。另外他将自动的创建一个QML Component的实例 (可以通过QDeclarativeView::rootObject()获得)两者用法区别很明显。

 import QtQuick 1.0

 Item {
     width: 100; height: 100
 }

 // Using QDeclarativeComponent
 QDeclarativeEngine engine;
 QDeclarativeComponent component(&engine,
         QUrl::fromLocalFile("MyItem.qml"));
 QObject *object = component.create();
 ...
 delete object;
 // Using QDeclarativeView
 QDeclarativeView view;
 view.setSource(QUrl::fromLocalFile("MyItem.qml"));
 view.show();
 QObject *object = view.rootObject();
object是MyItem.qml的C++实例。我们可以通过 QObject::setProperty() or QDeclarativeProperty: 设置Item的属性

,比如:

 object->setProperty("width", 500);
 QDeclarativeProperty(object, "width").write(500);
另外,我们将object进行类型转换,转换为其原来的类型,比如:
 QDeclarativeItem *item = qobject_cast<QDeclarativeItem*>(object);
 item->setWidth(500);

通过MetaObject::invokeMethod() 和QObject::connect(),我们可以连接、调用定义在component的函数。


下面我们来说如何获得子对象(上面我们得到是其根对象Item)

QObject::objectNameproperty with QObject::findChild()

例如:

 import QtQuick 1.0

 Item {
     width: 100; height: 100

     Rectangle {
         anchors.fill: parent
         objectName: "rect"
     }
 }
上面的例子中裉对象Item有一个子对象Rectangle,我们该如何得到他呢?

 QObject *rect = object->findChild<QObject*>("rect");
 if (rect)
     rect->setProperty("color", "red");
还有一种情况需要考虑,在delegate(ListView,Repeater)或其他需要创建很多实例时会获得很多具有相同名字的子对象,我们用 QObject::findChildren()可以得到所有具有相同名字的子对象。不推荐这么做。


进入的下篇:

Embedding C++ objects into QML components

上面介绍了如何将QML中组件在C++中实例化,之后修改其属性。这一部分我们将介绍如何将C++ 对象嵌入到QML组件中。当我们将QML 载入到C++ 程序中时,能够直接将C++ data 数据传入QML object 是非常有用的。 我们通过QDeclarativeContext,可以将C++数据暴露给QML 组件 的上下文环境中 ,可以让C++ 数据注入到QML中。

例如:我们有一个QML item 需要引用一个currentDateTime 值(这需要Qt 来提供,在QML中无法获取系统时间),

 // MyItem.qml
 import QtQuick 1.0

 Text { text: currentDateTime }
此时我们可以用C++直接设置这个值。首先我们需要通过载入QML 组件(文件)
QDeclarativeView view;
view.rootContext() //获得C++ object 
 然后通过QDeclarativeContext::setContextProperty():设置currentDateTime

完整的:

 QDeclarativeView view;
 view.rootContext()->setContextProperty("currentDateTime", QDateTime::currentDateTime());
 view.setSource(QUrl::fromLocalFile("MyItem.qml"));
 view.show();

上下问属性值可以是QVariant (如上例),也可以是QObject* ,这些对象可以在QML中被读取修改。例如:


 class ApplicationData : public QObject
 {
     Q_OBJECT
 public:
     Q_INVOKABLE QDateTime getCurrentDateTime() const {
         return QDateTime::currentDateTime();
     }
 };

 int main(int argc, char *argv[]) {
     QApplication app(argc, argv);

     QDeclarativeView view;

     ApplicationData data;
     view.rootContext()->setContextProperty("applicationData", &data);

     view.setSource(QUrl::fromLocalFile("MyItem.qml"));
     view.show();

     return app.exec();
 }

 Text {
     text: applicationData.getCurrentDateTime()

     Connections {
         target: applicationData
         onDataChanged: console.log("The application data changed!")
     }
 }

在QML中,也可以通过Connections element 接受来自上下文的信号。如上例,如果applicationData有一个信号dataChanged(),

那么这个信号可以被一个Connnections elemnnt 接收,并有ondataChanged()  handler。

补充:上下文属性是通过 QDeclarativeContext::setContextProperty()来定义和更改(updated)的。在以C++ model中

这些显得极为重要。例如:

 QDeclarativeEngine engine;
 QStringListModel modelData;
 QDeclarativeContext *context = new QDeclarativeContext(engine.rootContext());
 context->setContextProperty("myModel", &modelData);

 QDeclarativeComponent component(&engine);
 component.setData("import QtQuick 1.0\nListView { model: myModel }", QUrl());
 QObject *window = component.create(context);
Qt model 先被绑定到一个上下文,基于此,model可以被QML访问。又如:

 class MyDataSet : ... {
     ...
     Q_PROPERTY(QAbstractItemModel *myModel READ model NOTIFY modelChanged)
     ...
 };

 MyDataSet myDataSet;
 QDeclarativeEngine engine;
 QDeclarativeContext *context = new QDeclarativeContext(engine.rootContext());
 context->setContextObject(&myDataSet);

 QDeclarativeComponent component(&engine);
 component.setData("import QtQuick 1.0\nListView { model: myModel }", QUrl());
 component.create(context);

Defining new QML elements

QML当中的组件可以在C++中定义,这是很有必要的,这样可以写出更适合我们的组件(毕竟,大部分组件都需要我们自己实现,

很多核心组件也是通过

C++定义的)。那么如何创建一个可视的组件呢?我们需要使我们的类继承于 QDeclarativeItem

而不应该是直接继承QObject。 紧接着我们可以实现自己的绘制函数和其他一些在QGraphicsObject

中实现的函数。同时需要注意,QGraphicsItem::ItemHasNoCon 是默认设置的,如果你想自己绘制,你应该修改它。


例如:

 #include <QtCore>
 #include <QtDeclarative>

 class ImageViewer : public QDeclarativeItem
 {
     Q_OBJECT
     Q_PROPERTY(QUrl image READ image WRITE setImage NOTIFY imageChanged)

 public:
     void setImage(const QUrl &url);
     QUrl image() const;

 signals:
     void imageChanged();
 };
接下来我们要注册这个类:

 qmlRegisterType<ImageViewer>("MyLibrary", 1, 0, "ImageViewer");
这样我们就可以在我们的QML中使用了:

import MyLibrary 1.0

 ImageViewer { image: "smile.png" }

Exchanging data between QML and C++

QML and C++ objects can communicate with one another through signals,

slots and property modifications 。对于一个C++对象,其property,信号,slot(槽),和用Q_INVOKABLE声明的函数

都可以在QML中访问。反过来,QML中所有的数据都可以被C++来访问。

Calling functions

1    QML所有的函数都可以被C++调用,比如


// MyItem.qml
 import QtQuick 1.0

 Item {
     function myQmlFunction(msg) {
         console.log("Got message:", msg)
         return "some return value"
     }
 }
 // main.cpp
 QDeclarativeEngine engine;
 QDeclarativeComponent 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;
需要注意的是  Q_RETURN_ARG() and Q_ARG() 参数都应该是QVariant类型的。


2 在QML中如何调用一个C++函数呢?

在QML中,能够被调用的函数有两种情况

(1)slot

(2)用Q_INVOKEABLE宏标记

如下例,其中通过myObject调用的函数已经由 QDeclarativeContext::setContextProperty()处理

 // MyItem.qml
 import QtQuick 1.0

 Item {
     width: 100; height: 100

     MouseArea {
         anchors.fill: parent
         onClicked: {
             myObject.cppMethod("Hello from QML")
             myObject.cppSlot(12345)
         }
     }
 }
 // main.cpp
 QDeclarativeEngine engine;
 QDeclarativeComponent 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;

QML可以调用C++中重载的函数。


Receiving signals

总结一下,QML中一切都可以被C++访问到, (All QML signals are automatically available to C++) ,QML中所有的信号可以被

连接到C++的对象通过QObject::connect()。反过来讲,C++中的信号也可以在QML收到,通过signal handlers.

 // MyItem.qml
 import QtQuick 1.0

 Item {
     id: item
     width: 100; height: 100

     signal qmlSignal(string msg)

     MouseArea {
         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[]) {
     QApplication app(argc, argv);

     QDeclarativeView 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接收C++中的信号:

 class ImageViewer : public QDeclarativeItem
 {
     Q_OBJECT
     Q_PROPERTY(QUrl image READ image WRITE setImage NOTIFY imageChanged)
 public:
     ...
 signals:
     void imageChanged();
     void loadingError(const QString &errorMsg);
 };

 ImageViewer {
     onImageChanged: console.log("Image changed!")
     onLoadingError: console.log("Image failed to load:", errorMsg)
 }

还有一种情况:当Object不是在QML 中创建的时,在QML中我们仅得到对象的一个引用。例如,object是由QDeclarativeContext::setContextProperty()

定义的。这时我们需要用Connections element
来代替signal  handler 

 ImageViewer viewer;

 QDeclarativeView view;
 view.rootContext()->setContextProperty("imageViewer", &viewer);

 view.setSource(QUrl::fromLocalFile("MyItem.qml"));
 view.show();
 // MyItem.qml
 import QtQuick 1.0

 Item {
     Connections {
         target: imageViewer
         onImageChanged: console.log("Image has changed!")
     }
 }

Modifying properties

Any properties declared in a QML object are automatically accessible from C++

 // MyItem.qml
 import QtQuick 1.0

 Item {
     property int someNumber: 100
 }
someNumber 可以通过QDeclarativeProperty 或者QObject::setProperty() 和 QObject::property() 访问(read)和修改(set)

 QDeclarativeEngine engine;
 QDeclarativeComponent component(&engine, "MyItem.qml");
 QObject *object = component.create();

 qDebug() << "Property value:" << QDeclarativeProperty::read(object, "someNumber").toInt();
 QDeclarativeProperty::write(object, "someNumber", 5000);

 qDebug() << "Property value:" << object->property("someNumber").toInt();
 object->setProperty("someNumber", 100
需要注意的是,我们总是应该用 QObject::setProperty(),QDeclarativeProperty or

 QMetaProperty::write()来修改QML中属性的值,这样可以让QML engine

意识到属性值的改变。我们不应该这么做:

 // BAD!
 QDeclarativeComponent component(engine, "MyButton.qml");
 PushButton *button = qobject_cast<PushButton*>(component.create());
 button->m_buttonText = "Click me";
This means property bindings to buttonTextwould not be updated, and any onButtonTextChanged handlers would not be called.

所有Q_PROPERTY()声明过的属性可以在QML中访问到。

class ApplicationData : public QObject
 {
     Q_OBJECT
     Q_PROPERTY(QColor backgroundColor
             READ backgroundColor
             WRITE setBackgroundColor
             NOTIFY backgroundColorChanged)

 public:
     void setBackgroundColor(const QColor &c) {
         if (c != m_color) {
             m_color = c;
             emit backgroundColorChanged();
         }
     }

     QColor backgroundColor() const {
         return m_color;
     }

 signals:
     void backgroundColorChanged();

 private:
     QColor m_color;
 };
 // MyItem.qml
 import QtQuick 1.0

 Rectangle {
     width: 100; height: 100
     color: applicationData.backgroundColor

     MouseArea {
         anchors.fill: parent
         onClicked: applicationData.backgroundColor = "red"
     }
 }

Notice the backgroundColorChanged signal is declared as the NOTIFY

signal for the backgroundColor property.

If a Qt property does not have an associated NOTIFY signal,

the property cannot be used for Property Binding in QML,

as the QML engine would not be notified when the value changes.

If you are using custom types in QML, make sure their properties

have NOTIFY signals so that they can be used in property bindings.


Supported data types

传入QML 的C++数据必须是QML支持的。

默认的:


如果我们想将一个我们自己定义的C++ 类型在QML中使用,我们必须将C++ class 通过 qmlRegisterType() 注册一下。

 #include <QtCore>
 #include <QtDeclarative>

 class ImageViewer : public QDeclarativeItem
 {
     Q_OBJECT
     Q_PROPERTY(QUrl image READ image WRITE setImage NOTIFY imageChanged)

 public:
     void setImage(const QUrl &url);
     QUrl image() const;

 signals:
     void imageChanged();
 };

qmlRegisterType<ImageViewer>("MyLibrary", 1, 0, "ImageViewer");

JavaScript arrays and objects


QVariantList and JavaScript arrays, and QVariantMap and JavaScript objects可以自动转换

例如:

 // MyItem.qml
 Item {
     function readValues(anArray, anObject) {
         for (var i=0; i<anArray.length; i++)
             console.log("Array item:", anArray[i])

         for (var prop in anObject) {
             console.log("Object item:", prop, "=", anObject[prop])
         }
     }
 }
// C++
 QDeclarativeView view(QUrl::fromLocalFile("MyItem.qml"));

 QVariantList list;
 list << 10 << Qt::green << "bottles";

 QVariantMap map;
 map.insert("language", "QML");
 map.insert("released", QDate(2010, 9, 21));

 QMetaObject::invokeMethod(view.rootObject(), "readValues",
         Q_ARG(QVariant, QVariant::fromValue(list)),
         Q_ARG(QVariant, QVariant::fromValue(map)));
 Array item: 10
 Array item: #00ff00
 Array item: bottles
 Object item: language = QML
 Object item: released = Tue Sep 21 2010 00:00:00 GMT+1000 

Using enumerations of a custom type

如果想在QML中使用一个来自C++类型的组件中的枚举类型,我们需要用  Q_ENUMS()声明,将其注册到Qt的元对象

系统。例如:

 class ImageViewer : public QDeclarativeItem
 {
     Q_OBJECT
     Q_ENUMS(Status)
     Q_PROPERTY(Status status READ status NOTIFY statusChanged)
 public:
     enum Status {
         Ready,
         Loading,
         Error
     };

     Status status() const;
 signals:
     void statusChanged();
 };
假设我们已经将  ImageViewer class注册过了(qmlRegisterType()),我们就可以在QML

中使用这个枚举类型

 ImageViewer {
     onStatusChanged: {
         if (status == ImageViewer.Ready)
             console.log("Image viewer is ready!")
     }
 }
注意:The C++ type must be registered with QML to use its enums.

If your C++ type is not instantiable, it can be registered

usingqmlRegisterUncreatableType(). To be accessible from QML,

the names of enum values must begin with a capital letter.

Using enumeration values as signal parameters(未译)


C++ signals may pass enumeration values as signal parameters to QML, providing that the enumeration and the signal are declared within the same class, or that the enumeration value is one of those declared in the Qt Namespace.

Additionally, if a C++ signal with an enum parameter should be connectable to a QML function using the connect() function, the enum type must be registered using qRegisterMetaType().

For QML signals, enum values may be used as signal parameters using the int type:

 ImageViewer {
     signal someOtherSignal(int statusValue)

     Component.onCompleted: {
         someOtherSignal(ImageViewer.Loading)
     }
 }

Automatic type conversion from strings

方便起见,一些基本的QML数据类型可以通过格式化的字符串传递给C++

Type String format Example
QColorColor name, "#RRGGBB", "#RRGGBBAA""red", "#ff0000", "#ff000000"
QDate"YYYY-MM-DD""2010-05-31"
QPoint"x,y""10,20"
QRect"x,y,WidthxHeight""50,50,100x100"
QSize"WidthxHeight""100x200"
QTime"hh:mm:ss""14:22:55"
QUrlURL string"http://www.example.com"
QVector3D"x,y,z""0,1,0"
Enumeration valueEnum value name"AlignRight"

Writing QML plugin

QDeclarativeExtensionPlugin class allows QML extension types to be

dynamically loaded into QML applications.


QDeclarativeExtensionPlugin
 ( QObject * parent = 0 )

To write a QML extension plugin:

An example

Suppose there is a new TimeModel C++ class that should be made available as a new QML element. It provides the current time through hour and minuteproperties, like this:

     ...

To make this class available as a QML type, create a plugin that registers this type with a specific module using qmlRegisterType(). For this example the plugin module will be named com.nokia.TimeExample (as defined in the project file further below).

This registers the TimeModel class with the 1.0 version of this plugin library, as a QML type called Time. The Q_ASSERT statement ensures the module is imported correctly by any QML components that use this plugin.

The project file defines the project as a plugin library and specifies it should be built into the com/nokia/TimeExample directory:

 TEMPLATE = lib
 CONFIG += qt plugin
 QT += declarative

 DESTDIR = com/nokia/TimeExample
 TARGET = qmlqtimeexampleplugin
 ...

Finally, a qmldir file is required in the com/nokia/TimeExample directory that describes the plugin. This directory includes a Clock.qml file that should be bundled with the plugin, so it needs to be specified in the qmldir file:

Once the project is built and installed, the new Time element can be used by any QML component that imports the com.nokia.TimeExample module:



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值