基本上,Qt中所有直接或间接继承自QObject的类的构造函数都会指定一个parent参数,如下:
#include
class MyTest : public QObject
{
Q_OBJECT
public:
explicit MyTest(QObject *parent = 0);
signals:
public slots:
void runTest();
};
这个parent参数通常是QObject* 或者是 QWidget* 类型的。很多情况下它都会有一个初始值0。这个参数存在的意义在于:
1. 为所定义的实例指定了父容器。这与继承中的“父类”概念是不同的。举个例子,在某个APP的MainWindow下创建一个Button控件,那么这个Button控件就需要指明父容器。只有这样当MainWindow销毁的时候,MainWindow下的Button(包括所有其他控件)都将被自动销毁。
2. 利用Qt中,父实例销毁,其下所有子实例随之自动销毁的机制。达到类似垃圾回收的效果(这种机制应该比java的垃圾回收机制好多了,不过限制也多)
3. Unfortunately,有时候,应该避免指定parent参数。例如在子线程中新建一个parent参数为this的QObject的实例。运行报错“Cannot create children for a parent that is in a different thread”,如下
//.h
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
ElsClient elsClient;
QQmlApplicationEngine engine;
elsClient.start();
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
//.cpp
void ElsClient::run()
{
//client thread
//这行运行报错,原因在于this的创建线程为主线程,解决办法就是不指定this。。
m_tcpSocket=new QObjectClass (this);
……
}
Qt中信号槽机制是对象间通信的一种重要方式。Qt信号槽机制有以下特点:
1. 类型安全:只有参数匹配的信号与槽才可以连接成功,注意这里的“匹配”也可以是多对少,即一个SIGNAL可以发出比SLOT所需更多的参数,而SLOT会忽略多余参数。但有效参数类型必须匹配。
2. 线程安全:通过借助QT自已的事件机制,信号槽支持跨线程并且可以保证线程安全。可以想到的是,Qt内部为此必然使用了某些线程同步的方式。因此基于GUI效率来讲,Qt信号槽理论上应该是要比普通回调慢一些的。
3. 松耦合:信号不关心有哪些或者多少个对象与之连接;槽不关心自己连接了哪些对象的哪些信号。这些都不会影响何时发出信号或者信号如何处理。然而,这样的松耦合使得调试变得相对困难,因为很难(或者说不方便)找到正确的SLOT。
4. 信号与槽是多对多的关系:一个信号可以连接多个槽,一个槽也可以用来接收多个信号。调试变得更困难了(●’◡’●)。
所有从 QObject 或其子类 ( 例如 Qwidget ) 派生的类都能够包含信号和槽。因为信号与槽的连接是通过 QObject 的 connect() 成员函数来实现的。注意此函数是QObject的一个static方法。
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
其中 sender 与 receiver 是指向对象的指针,SIGNAL() 与 SLOT() 是转换信号与槽的宏。实际上在Qt老些版本上也可以看到不用转换宏而使用函数指针的写法。
普通信号和槽的定义方法如下所示(关键的几个地方已经用高亮标明了):
#include
class MyTest : public QObject //高亮 ,MarkDown无法高亮代码,连本注释都有问题,囧
{
Q_OBJECT //高亮
public:
explicit MyTest(QObject *parent = 0);
signals: //高亮
//这里定义信号,注意在cpp文件中不需要实现定义,qt会在moc文件中自动实现定义。例如
//void funca();
public slots: //高亮
void runTest();//槽函数需要自己在cpp中实现,与普通成员函数基本一样
};
高亮关键字是的Qt在编译的时候会先生成一个moc文件,可以简单的认为这个moc文件就是信号槽的Creator。以下是一个简单的例子。
QObject::connect(&a,SIGNAL(funca()),&b,SLOT(runTest()));
这样,当发出信号:emit funca()后,runTest()函数将被触发执行,这里需要注意的一点是,其所在线程必须有事件循环(即调用了exec())。
相比于Qt4,Qt5的connect也被优化了不少。现在可以选择使用类内函数指针的形式进行连接,这样在编译阶段就可以检查“函数不存在”的错误。格式如下:
connect(sender, &Sender::aa,receiver,&Receiver::bb);
这种连接方式还可以连接非slot函数,并且可以绑定实例,如下:
connect(sender, &Sender::aa,receiver,&receiver::bb);
这种方式缺点也很明显,无法指明参数类型,不能有重载。个人觉得还是用SIGNAL/SLOT宏方便,函数不存在的问题尽量交给IDE。
另外Qt5可以连接 static 函数、全局函数以及 Lambda 表达式。如下 :
connect(sender, &Sender::aa, func);
*************************
voidMyWindow::saveDocumentAs(){
QFileDialog
*dlg=newQFileDialog();
dlg->open();
QObject::connect(dlg,&QDialog::finished,[=](intresult){
……//lamba的好处在于方便访问域外的变量,如dlg
});
}
事实上QObject::connect()还有一个参数,此参数指明了connection的类型。Connect有如下几种连接类型:
//Qt::ConnectionType type
Qt::DirectConnection
Qt::QueuedConnection
Qt::AutoConnection(自动方式)
//其余还有三种,不常用
Qt::DirectConnection:当信号发出后,相应的槽函数将立即被调用。emit语句后的代码将在所有槽函数执行完毕后被执行。(即类似于普通的函数调用)
Qt::QueuedConnection:当信号发出后,排队到信号队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,调用相应的槽函数。emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕。(此时信号被塞到信号队列里了,信号与槽函数关系类似于消息通信,异步执行)
Qt::AutoConnection:此方式也是Qt的默认连接方式,即如果信号的发出和接收对象同属一个线程,那个工作方式与Qt::DirectConnection方式相同;否则工作方式与Qt::QueuedConnection相同。
个人建议显示标明连接方式,因为有些时候需要同线程,有些需要跨线程,如果程序运行不符合预期,会报错,便于查找bug。
QML控件中有一些内置的信号,连接这些信号的方法即在onXxx后写入代码块即可。
Timer {
id:timer
repeat: true
interval: 500
triggeredOnStart: false
onTriggered:{
gameGrid.handleGravity()
}
如果需要自定义一些属性和属性改变时的处理函数,方式如下:
Timer {
id:timer
repeat: true
interval: 500
triggeredOnStart: false
property var name:xxx
onName:{
//some functions
}
}
然而, 某些情况下是无法通过”on\< Signal>”来实现信号绑定的,必须通过显示的Connection:
1. 针对某个信号需要多个处理时,也就是有多个槽关联到同一个信号上。
2. 在信号的发送者的范围外(这里可以理解为发送者的类定义之外)创建连接。
3. 连接的目标不在QML中,如C++注册进入QML的对象。
具体格式如下所示:
Connections{
target:xxx//目标对象id
onDoUpdataRemoteGameStatus:{//目标对象信号
//some functions //处理函数
}
}
2.4.1 QML中调用C++
QML中使用C++对象的常用方式有如下几种:
1, 将C++类的实例注册到QML,此时这个实例就相当于QML中的一个QML控件,所不同的是由于其定义是咋C++中,QML中如果要绑定信号处理函数的话,需要采用显示的Connection。
#include
#include
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
Shape myShape;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myShape",&myShape);//QML中id为myShape
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
2, 将C++类以QML类型的方式注册到QML,这样在 QML中就能像创建普通QML控件一样创建注册类型了。
#include
#include “Shape”
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
//QML中import MyShape 1.0,控件类型为Shape
qmlRegisterType("MyShape", 1, 0, "Shape");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
3, Qt C++发送信号给QML,QML端的Signal Handler进行处理。此方法需要将C++实例注册进QML(具体方法参见上面1).
//.qml
Button{
id:btn3
text: qsTr("emit stringchanged signal")
onClicked: myShape.Xxx="xxxxx"; //这里实质上也可以调用C++类的方法
}
Connections
{
target: myShape
onXxxChanged:label2.text="Signal handler received" //接收C++信号并处理
}
*************************************************
//.cpp
void MyClass::setmyString(QString aString){
if(aString==m_string){
return;
}
m_string=aString;
emit xxxChanged(); //发出信号
}
4, 在Qt C++端创建QML对象,这样C++就可以方便操作QML了。
2.4.2 C++调用QML
Qt在启动QML时,会初始化一个QQmlEngine作为QML引擎,然后使用QQmlComponent对象加载QML文档,QML引擎会提供一个默认的QQmlContext对象作为顶层执行的上下文,用来执行QML文档中定义的函数和表达式。
QQmlEngine::rootContext() 返回当前引擎QML的上下文,唯一的,QQmlContext* QQuickView::rootContext()
QQuickItem* QQuickView::rootObject() 返回当前QQuickView的根节点,也就是QML的根节点
C++调用QML的方法有如下几种:
1,利用QqmlComponent加载QML,将QML转化为C++对象,然后进行调用。
QQmlEngine engine; //QML引擎
QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:///main.qml"))); //加载QML
//用QQmlComponent创建一个组件的实例,并且赋值给object*,这步操作非常关键,Object类型可以转换其他任意类型,比如QQuickItem
QObject* object = component.create();
object->setProperty("width", 500); //元对象系统赋值操作
QQmlProperty(object, "width").write(500); //元对象系统赋值操作
QQuickItem* item = qobject_cast(object); //把 QObject* 转换成 QQuickItem* 类型
tiem->setWidth(500); //QQuickItem* 赋值操作
2,使用QquickView加载,QQuickView是继承QWindow,所有可以加载一个可视化QML对象,并且可以与应用程序的图形用户界面进行融合。
QQuickView view; //QQuickView对象
view.setSource( QUrl(QStringLiteral("qrc:///main.qml"))); //加载QML
view.show(); //QQuickView可以显示可视化QML对象
QQuickItem* item = view.rootObject(); //返回当前QQuickView的根节点
tiem->setWidth(500); //QQuickItem* 赋值操作
3,使用对象名字访问加载的QML对象。QML中的所有节点都会绑定到根节点树上,QObject::objectName这个属性保存特定对象。QML组件的子对象可以在C++中通过 QObject::findChild()查找到在QML中用objectName定义的对象。
QPushButton* button = root.findChild("qml_button")
QObject* object = root.findChild<QObject*>("qml_object")
QQuickItem* item = root.findChild("qml_item")
如果有多个对象使用objectName:”qml_button”同名标记,QObject::findChild返回最后一个标记的QML对象,QObject::findChildren返回所有标记的QML对象存放在QList类型的列表中。
3、使用C++访问QML对象成员。
所有的QML对象都会暴露在Qt的元对象系统,C++可以通过元对象系统的QMetaObject::invokeMethod()调用QML中注册到元对象系统函数。
注意:QMetaObject::invokeMethod()方法中的参数Q_RETURN_ARG()和Q_ARG()都被定义为QVariant类型,此类型是QML函数的的参数和返回值的通用数据类型。
QQmlEngine engine; //QML引擎
QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:///main.qml"))); //加载
QMLQObject* object = component.create();//用QQmlComponent创建一个组件的实例,并且赋值给object*,这步操作非常关键,Object类型可以转换其他任意类型,比如QQuickItem
QVariant rValue;
QVariant msg = "Hello for C++";
QMetaObject::invokeMethod(object, "qmlFunction", Q_RETURN_ARG(QVariant,rValue), Q_ARG(QVariant, msg));
C++可以接收所有的QML信号,QML也可以接收C++信号,在C++中可以使QObject::connect()进行接收信号槽。
qml中定义一个信号:
signal qmlSignal(string msg)
*************************************
//C++进行连接信号:
QQuickView view; //QQuickView对象
view.setSource( QUrl(QStringLiteral("qrc:///main.qml"))); //加载QML
view.show(); //QQuickView可以显示可视化QML对象
QQuickItem* root = view.rootObject(); //返回当前QQuickView的根节点,底下可以绑定很多节点
QObject::connect(root, SIGNAL(qmlSignal(QString)), this, SLOT(Slotqml(QString)));
特别的,对于已经由QqmlApplicationEngine加载了Qml的APP程序。可以由QqmlApplicationEngine的成员函数索引到Qml对象。
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
ApplicationWindow {
objectName: "mainWindow"
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Button{
id:btn1
objectName: "btn1";
text: "aaa"
onClicked: btn1.text="bbbb"
}
}
****************************cpp********************* *******
#include
#include
#include
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
// Step 1: get access to the root object
QObject *rootObject = engine.rootObjects().first();
QObject *qmlObject = rootObject->findChild("btn1")
// Step 2a: set or get the desired property value for the root object
rootObject->setProperty("height", 640);
qDebug() << rootObject->property("height");
// Step 2b: set or get the desired property value for any qml object
qmlObject->setProperty("visible", false);
qDebug() << qmlObject->property("height");
//连接信号的方法
// QObject::connect(qmlObject,SIGNAL(clicked()),&ta1,SLOT(test()));
return app.exec();
}