在QML刚刚出来不久,参加nokia的一个Qt Quick培训的时候,QML就给我的印象是:解释性脚本语言,没有内存操作的说法,更不用说指针了。当时也就是想想,也没有具体去实践探讨。由于现在在用Qt做产品,UI方面不得不跟QML打交道。QML做UI可以说是又好又快,大大节省了开发周期,但是由于QML处理逻辑的能力较差,所以,对于大量的逻辑处理还是需要Qt C++支持。这就涉及到C++与QML解释性语言之间数据交换。
QML在和C++相互嵌入运行的时候,就需要QML的engine: QDeclarativeEngine。通常我们不会直接与这个engine打交道。我们通常的做法是这样的:
#include
#include "qmlapplicationviewer.h"
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MyClass myObj;
QDeclarativeView view;
QDeclarativeContext *ctxt = view.rootContext();
ctxt->setContextProperty("obj", &myObj);
view.setSource(QUrl("main.qml"));
return app.exec();
}
View已经实现了对engine的封装。如果我们需要在QML中调用C++对象的话,只需要使用setContextProperty().
View和Context提供了QML运行环境。setContextProperty相当于把C++对象在这个Context中注册,QML中只需要使用obj这个别名就可以访问到注册的对象myObj,这种访问包括调用成员函数,当然,成员函数要使用Q_INVOKABLE修饰之后就可以调用了。看到这里,似乎还没看到QML与指针的任何关系。通常情况下,遇到QML需要和C++对象相互嵌入调用的时候,使用setContextProperty,将事先构造好的对象在QML运行环境中进行注册,这样QML就可以调用C++方法了。
并不是所有的对象都可以事先构造好,例如:QML UI上有一个按钮,每点击这个按钮一次就需要构造一个C++对象进行相应的操作,如果点击100下,那岂不是事先要构造好100个对象?在这种情况下,动态创建C++对象无疑是最好的方法。如果对于纯C++,当然简单,new一个对象,并把对象的指针返回就可以对其进行操作了。当时这里是QML,就没那么简单了。i
对于这种需要在QML中动态创建C++对象,并需要对该对象进行操作,我们没有办法每次都调用setContextProperty("obj", &myObj)。因为setContextProperty的第一个参数实际上是在QML中的变量名,QML中就是通过第一个参数找到该对象的。所以该字符创必须唯一。第一次跟我同事讨论方案的时候,我们想,不就是字符串唯一吗,我直接new一个对象,然后将对象的地址转换成字符串,然后调用setContextProperty,并将字符串返回到QML中。这样,QML岂不就是可以自由的操作该对象了。这个方法呢,能满足要求,但是感觉很不专业。要是能操作指针就好了。 最后的方案是通过已经注册的对象,调用 obj.createObject(),返回对象的指针到QML中去。QML通过获取的指针做自己操作。由于QML中无法直接定义指针,所以使用variant这个万能的类型定义变量。
先看代码:
//由于只有一个inline函数,所以只贴出来了头文件,cpp文件实际上什么也没做
//operation.h
#ifndef OPERATION_H
#define OPERATION_H
#include
#include "target.h"
class Operation : public QObject
{
Q_OBJECT
public:
explicit Operation(QObject *parent = 0);
Q_INVOKABLE inline Target* createObject() { return new Target; }//这里我们就是为了返回指针给QML
signals:
public slots:
};
#endif // OPERATION_H
//target.h
#ifndef TARGET_H
#define TARGET_H
#include
#include
class Target : public QObject
{
Q_OBJECT
public:
explicit Target(QObject *parent = 0);
Q_INVOKABLE inline void getAction() { qDebug() << "Hi, I am Harlen"; }
signals:
public slots:
};
#endif // TARGET_H
//main.cpp
#include
#include "qmlapplicationviewer.h"
#include
#include
#include
#include "operation.h"
#include "target.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Operation myObj;
QDeclarativeView view;
QDeclarativeContext *ctxt = view.rootContext();
ctxt->setContextProperty("obj", &myObj);
view.setSource(QUrl("qml/QmlPointer/main.qml"));
view.show();
return app.exec();
}
import QtQuick 1.0
Rectangle {
width: 360
height: 360
property variant pointer
color: "skyblue"
Text {
text: "Harlen Tan"
font.bold: true
font.pointSize: 20
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
pointer = obj.createObject()
pointer.getAction()
}
}
}
注意上面的注释出createObject的签名:
Target* Operation::createObject(),由于obj已经向QML注册过了,所以在QML中直接使用obj.createObject()是绝对可行的。
那么,只要createObject()一调用,QML拿到的绝对就是指针了,然后QML中使用指针操作,就游刃有余了。
调试运行,点击对话框,发现有提示消息: main.qml:19:TypeError: Result of expression 'pointer' [undefined] is not an object.
查看main.qml 的19行:pointer.getAction(),意思是说pointer不是个对象。但是QML中也没有pointer->或者 *pointer这个语法。
怎么办?计量都用完了,只有回去去翻看SDK document. 没有绝望,因为在我心里总有那么一点念头这问题可以解决,只是还没理解透彻。于是看到了帮助文档这么一段话,让我豁然开朗:
Any C++ data that is used from QML - whether as custom properties, or parameters for
signals or functions -must be of a type that is recognizable by QML.
By default, QML recognizes the following data types:
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()
To allow a custom C++ type to be created or used in QML, the C++ class must be registered
as a QML type using qmlRegisterType(), as shown in the Defining new QML elements section above.
我们并没有向QML注册我们的指针类型,上面文档中的类型types:QObject*是可以识别的,那是不是意味着我们注册下 Target就可以了呢?
于是修改main.cpp
#include
#include "qmlapplicationviewer.h"
#include
#include
#include
#include "operation.h"
#include "target.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Operation myObj;
QDeclarativeView view;
QDeclarativeContext *ctxt = view.rootContext();
qmlRegisterType();//只是添加了这么一句话,向QML环境注册自定义类型
ctxt->setContextProperty("obj", &myObj);
view.setSource(QUrl("qml/QmlPointer/main.qml"));
view.show();
return app.exec();
}
点击运行,并点击对话框,调试信息输出:
Hi, I am Harlen
运行成功了。后来发现,由于Target是继承自QObject,所以只需要将createObject返回类型改为QObject *,也可以运行成功。
QML中你所不知道的state
最后一次写QML已经是2010年了,最近由于产品需要,重拾QML。之前nokia给我们培训QML的时候,对于state这个概念理解的不是很透彻。最近在做产品前期的QML热身,发现QML中的state有一种神奇的功能:历史记忆效应
state核心就是体现了一个状态机的原理,处在某一状态去改变某些属性以达到目的。关于state如何使用的我这里就不说了,看看nokia的QML文档就知道state如何使用。我这里主要讲讲state的历史记忆效应。
从代码开始入手说起:
import QtQuick 1.0
Rectangle {
width: 360
height: 360
color: "#E4F3F9"
Rectangle{
id: innerRec
width: 60
height: 30
color: "#59A72C"
state: "pre"
anchors.centerIn: parent
Text {
id: txt
anchors.centerIn: parent
text: "Hello QML"
}
MouseArea{
anchors.fill: parent
onClicked: if (innerRec.state == "pre")innerRec.state = "tag";else innerRec.state = "pre"
}
states: [
State {
name: "tag"
PropertyChanges {
target: innerRec;
color: "#5CB4DA"
}
PropertyChanges {
target: txt;
text: "Hello Qt"
}
}
]
}
}
当我们第一次点击巨型区域的时候,该区域会变色,并字体变为“Hello Qt”。按照设计在点击一次恢复到原始状态,也就是“Hello QML”包括颜色。
比较正常的思维就是在点击一次通过priorityChange把颜色以及字体更改回去。
其实,不用。只需要把state属性更改为原始的“pri”就可以。因为每个state,包括原始的state,都会将当前QML对象的属性记录下来,这些属性包括颜色,文字,大小等等。
所以,上面的代码就能实现不断点击不断交替切换的效果。
效果如下:
参考于:https://blog.csdn.net/feiyinzilgd/article/details/6680116