QT学习笔记--Qt信号槽机制

1,parent参数

基本上,Qt中所有直接或间接继承自QObject的类的构造函数都会指定一个parent参数,如下:

#include <QObject>
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);
……
}

2,信号槽机制

Qt中信号槽机制是对象间通信的一种重要方式。Qt信号槽机制有以下特点:
1. 类型安全:只有参数匹配的信号与槽才可以连接成功,注意这里的“匹配”也可以是多对少,即一个SIGNAL可以发出比SLOT所需更多的参数,而SLOT会忽略多余参数。但有效参数类型必须匹配。
2. 线程安全:通过借助QT自已的事件机制,信号槽支持跨线程并且可以保证线程安全。可以想到的是,Qt内部为此必然使用了某些线程同步的方式。因此基于GUI效率来讲,Qt信号槽理论上应该是要比普通回调慢一些的。
3. 松耦合:信号不关心有哪些或者多少个对象与之连接;槽不关心自己连接了哪些对象的哪些信号。这些都不会影响何时发出信号或者信号如何处理。然而,这样的松耦合使得调试变得相对困难,因为很难(或者说不方便)找到正确的SLOT。
4. 信号与槽是多对多的关系:一个信号可以连接多个槽,一个槽也可以用来接收多个信号。调试变得更困难了(●’◡’●)。

2.1,同线程内信号槽

所有从 QObject 或其子类 ( 例如 Qwidget ) 派生的类都能够包含信号和槽。因为信号与槽的连接是通过 QObject 的 connect() 成员函数来实现的。注意此函数是QObject的一个static方法。

connect(sender, SIGNAL(signal), receiver, SLOT(slot));

其中 sender 与 receiver 是指向对象的指针,SIGNAL() 与 SLOT() 是转换信号与槽的宏。实际上在Qt老些版本上也可以看到不用转换宏而使用函数指针的写法。
普通信号和槽的定义方法如下所示(关键的几个地方已经用高亮标明了):

#include <QObject>
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
    });
}
2.2,跨线程信号槽

事实上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。

2.3,QML信号槽

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,QML和C++信号槽交互

2.4.1 QML中调用C++
QML中使用C++对象的常用方式有如下几种:
1, 将C++类的实例注册到QML,此时这个实例就相当于QML中的一个QML控件,所不同的是由于其定义是咋C++中,QML中如果要绑定信号处理函数的话,需要采用显示的Connection。

#include <QQmlContext>
#include <QtQml>
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 <QtQml>
#include “Shape”
int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
//QML中import MyShape 1.0,控件类型为Shape
qmlRegisterType<Shape>("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<QQuickItem*>(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<QPushButton*>("qml_button")
QObject* object = root.findChild<QObject*>("qml_object")
QQuickItem* item = root.findChild<QQuickItem*>("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 <QGuiApplication>
#include <QQmlApplicationEngine>
#include <qDebug>
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<QObject*>("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();
}

猜你喜欢

转载自blog.csdn.net/a34140974/article/details/79642192