一、介绍
为了在C++中使用QML,用到了QtDeclarative中有三个主要的类:
QDeclarativeEngine、QDeclarativeComponent和QDeclarativeContext。很多QML元素也有对应方法获取用C++创建好的元素实例,如:Item<->QDeclarativeItem、Scale<->QGraphicsScale、Blur<->QGraphicsBlurEffect。为了使用QtDeclarative,需要在工程文件中加入QT += declarative
二、QDeclarativeView
这是一个简单易用的显示类,继承自QGraphicsView,主要用于快速的建立应用原型
示例如下:
#include <QApplication>
#include <QtDeclarative/QDeclarativeView>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QDeclarativeView canvas;
canvas.setSource(QUrl("main.qml"));
canvas.show();
return app.exec();
}
三、QDeclarativeEngine
若想要在Qt/C++中访问QML,必须有一个QDeclarativeEngine实例。其提供了在C++中初始化QML控件的环境,通过它配置全局的QML设置。如果要提供不同的QML设置,需要实例化多个QDeclarativeEngine
四、QDeclarativeComponent
它对应了一个QML文档的实例,用来加载QML文件。加载的内容可以是路径,也可以是QML代码(URL可以是本地的文件或者QNetworkAccessManager支持的协议访问的网络文件)。这个类还包含了QML文件的状态信息,如:Null、Ready、Loading、Error等。
五、实例——初始化组件
// Create the engine(root context create automatically as well
QDeclarativeEngine engine;
// Create a QML component associated with the engine
// (Alternatively you could create an empty component and the set
// its contents with setData().)
QDeclarativeComponent component(&engine, QUrl("main.qml"));
// Instantiate the component (as no context is given to create(),
// the root context is used by default
QDeclarativeItem* item = qobject_cast<QDeclarativeItem*>(component.create());
// Add item to a view, etc...
六、QDeclarativeContext
1、每个QML组件初始化都会对应一个QDeclarativeContext(engine会自动建立root context)。子context可以根据需要而创建,它与父类的集成关系是由QDeclarativeEngine管理维护的。QML组件实例的数据都应该加入到engine的root环境中,同时,QML子组件的数据也应该加入到子环境中(sub-context)
2、使用context可以把C++的数据和对象暴露给QML
示例如下:
QQmlApplicationEngine engine;
// engine.rootContext() returns a QDeclarativeContext*
engine.rootContext()->setContextProperty("myBackgroundColor",
QColor(Qt::lightsteelblue));
QDeclarativeComponent component(&engine, "main.qml");
QObject* window = component.create(); // Create using the root context
3、这种机制可以被用来为QML中的View提供C++端的Model
4、
前面提到过,context是具有继承关系的,控件初始化的时候,可以使用对应的context里的数据,也可以访问到祖先context的数据。对于重复定义的数据,子context中的定义将会覆盖context中的定义
示例如下:
QDeclarativeEngine engine;
QDeclarativeContext context1(engine.rootContext());
QDeclarativeContext context2(&context1);
QDeclarativeContext context3(&context2);
context1.setContextProperty("a", 12);
context2.setContextProperty("b", 13);
context3.setContextProperty("a", 14);
context3.setContextProperty("c", 14);
// Instantiate QDeclarativeComponents using the sub-context
component1.create(&context1); // a = 12
component2.create(&context2); // a = 12, b = 13
component3.create(&context3); // a = 14, b = 13, c = 14
七、结构化数据
1、如果有很多数据需要暴露给QML,可以使用默认对象(default object)代替。所有默认对象中定义的属性都可以在QML控件中通过名字访问到。通过这种方式暴露的数据,可以在QML端被修改。同时,使用默认对象的速度比多次调用setContextProperty()快。
2、多个默认对象可以加到同一个QML组件实例中,先添加的默认对象是不会被后面添加的对象所覆盖。于此不同的是,使用setContextProperty()设置的属性,会被新的属性覆盖。
3、Model数据通常都是由C++端代码动态提供的,而不是一个静态QML的数据Model。在delegate里面通过model属性可以访问到数据模型,这是一个默认的属性。
4、可以使用的C++端的数据模型有:
QList<QObject*> —— model.modelData.xxx(xxx是属性)
QAbstractDataModel —— model.display(decoration)
QStringList —— model.modelData
示例如下:
// MyDataSet.h
class MyData : ... {
...
// The NOTIFY signal informs about changes in the property's value
Q_PROPERTY(QAbstractItemModel* myModel READ model NOTIFY modelChanged)
Q_PROPERTY(QString text READ text NOTIFY textChanged)
...
};
// SomeOtherPieceOfCode.cpp exposes the QObject using e.g. a sub-context
QDeclarativeEngine engine;
QDeclarativeContext context(engine.rootContext());
context.addContextObject(new MyDataSet(...));
QDeclarativeComponent component(&engine, "ListView {model = myModel}");
component.create(&context);
八、QML调用C++方法
所有QObject对象的public的槽方法都可以在QML中调用,如果你不想你的方法是槽方法,可以使用Q_INVOKABLE(如:Q_INVOKABLE void myMethod();)
示例一:
// In C++:
class LEDBlinker : public QObject {
Q_OBJECT
// ...
public slots:
bool isRunning();
void start();
void stop();
};
int main(int argc, char *argv[]){
// ...
QDeclarativeContext* context = engine->rootContext();
context->setContextProperty("ledBlinker", new LEDBlinker);
// ...
}
// In QML:
Rectangle {
MouseArea {
anchors.fill: parent
onClicked: {
if (ledBlinker.isRunning())
ledBlinker.stop()
else
ledBlinker.start();
}
}
}
示例二:
需要注意的是,我们完全可以通过声明一个“running”属性来达到同样的效果,这样的话,代码会更加优雅,省略掉了isRunning()和setRunning()两个方法
// In C++:
class LEDBlinker : public QObject {
Q_OBJECT
Q_PROPERTY(bool running READ isRunning WRITE setRunning)
// ...
};
// In QML:
Rectangle {
MouseArea {
anchors.fill: parent
onClicked: ledBlinker.running = !ledBlinker.running
}
}
九、在C++中调用QML方法
很明显,反过来在C++中调用QML的方法也是可以的。在QML中定义的方法在C++中都是一个槽函数,同时,在QML中定义的信号可以与C++中定义的槽函数连接。
十、网络组件
前面讨论过,QML组件可以通过网络加载,这种方式可能会花费些时间,因为网络总是有一定的延时。所以,在C++中初始化网络上的QML控件的时候,需要观察控件的加载状态,只有当状态为Ready后,才能调用created()创建控件。
示例如下:
MyObject::MyObject() {
component = new QDeclarativeComponent(engine,
QUrl("http://www.example.com/mail.qml"));
// Check for status before creating the object - notice that this kind of
// code could (should?) be used regardless of where the component is located!
if(component->isLoading())
connect(component, SIGNAL(statusChanged(QDeclarativeComponent::Status),
this, SLOT(continueLoading())));
else
continueLoading(); // Not a network-based resource, load straight away
}
// A slot that emits the States parameter of the signal and uses the isXxxx()
// functions instead to check the status - both approaches work the same way
void MyObject::continueLoading() {
if(component->isError()){
qWarning() << component->errors();
} else if(component->isReady()) {
QObject* myObject = component->create();
} // The other status checks here ...
}
十一、QML Components in Resource File
在Qt工程中最方便的方法还是把QML组件添加到资源文件中,所有的JavaScript文件也可以被放在资源文件中。这样更加容易访问文件,放入资源文件后,我们无需知道文件的路径,只需要使用一个指向资源文件中的文件的URL就可以了。同时,资源文件可以编译到二进制程序中,这样资源文件就可以与二进制文件一起分发了,非常的方便。
示例如下:
// MyApp.qrc
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource> <file>qml/main.qml</file> </qresource>
</RCC>
// MyObject.cpp
MyObject::MyObject() {
component = new QDeclarativeComponent(engine,
QUrl("qrc:/qml/main.qml"));
if(!component->isError()){
QObject* myObject = component->create();
}
}
Image {
source: "images/background.png"
}