文章属于原创,转载请注明出处,有一部份内容保持英文,方便理解
我们知道 ,通过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()获得)两者用法区别很明显。
| |
,比如:
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()
例如:
上面的例子中裉对象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中无法获取系统时间),
此时我们可以用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();
}
在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中使用了:
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++调用,比如
// 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()处理
// 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.
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);
};
还有一种情况:当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();
Modifying properties
Any properties declared in a QML object are automatically accessible from C++。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;
};
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支持的。默认的:
- bool
- unsigned int, int
- float, double, qreal
- QString
- QUrl
- QColor
- QDate, QTime, QDateTime
- QPoint, QPointF
- QSize, QSizeF
- QRect, QRectF
- QVariant
- QVariantList, QVariantMap
- QObject*
- Enumerations declared with Q_ENUMS()
如果我们想将一个我们自己定义的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可以自动转换
例如:
// 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
中使用这个枚举类型
注意: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:
Automatic type conversion from strings
方便起见,一些基本的QML数据类型可以通过格式化的字符串传递给C++
Type | String format | Example |
---|---|---|
QColor | Color 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" |
QUrl | URL string | "http://www.example.com" |
QVector3D | "x,y,z" | "0,1,0" |
Enumeration value | Enum value name | "AlignRight" |
Writing QML plugin
QDeclarativeExtensionPlugin class allows QML extension types to bedynamically loaded into QML applications.
QDeclarativeExtensionPlugin ( QObject * parent = 0 ) |
To write a QML extension plugin:
- Subclass QDeclarativeExtensionPlugin, implementregisterTypes() method to register types usingqmlRegisterType(), and export the class using theQ_EXPORT_PLUGIN2() macro
- Write an appropriate project file for the plugin
- Create a qmldir file to describe the 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: