Qt信号槽中connect五个重载函数详细说明,连接类型Qt::DirectConnection,Qt::QueuedConnection,附详细代码

想说在前面的两句话。

  • 信号槽是 Qt 框架引以为豪的机制之一。熟练使用和理解信号槽,能够设计出解耦的非常漂亮的程序,有利于增强我们的程序设计能力。
  • 信号与槽是Qt学习的重点,但不是难点。

本篇介绍Qt5中信号槽中connect函数的使用方法,以及connect第五个参数Qt::ConnectionType的使用。

1. QObject::connect函数

Qt中信号与槽与经典的观察者模式非常相似,首先我们复习下观察者模式,有一个报纸类Newspaper,有一个订阅者类Subscriber。
Subscriber可以订阅Newspaper。这样,当Newspaper有了新的内容的时候,Subscriber可以立即得到通知。
在这个例子中,观察者是Subscriber,被观察者是Newspaper。在经典的实现代码中,观察者会将自身注册到被观察者的一个容器中(比如subscriber.registerTo(newspaper))。
被观察者发生了任何变化的时候,会主动遍历这个容器,依次通知各个观察者(newspaper.notifyAllSubscribers()) 但Qt的信号槽使用了额外的处理来实现,并不是 GoF 经典的观察者模式的实现方式。
按F1查看Qt助手,可以看到connect有五个函数重载,分别如下:

   //1.QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
   QLabel *label = new QLabel;
   QScrollBar *scrollBar = new QScrollBar;
   QObject::connect(scrollBar, SIGNAL(valueChanged(int)), label,  SLOT(setNum(int)));

   //2.QObject::connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type = Qt::AutoConnection)

   //3.QObject::connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type = Qt::AutoConnection) const

   //4.QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)
   QLabel *label2 = new QLabel;
   QLineEdit *lineEdit = new QLineEdit;
   QObject::connect(lineEdit, &QLineEdit::textChanged, label2,  &QLabel::setText);

   //5.QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
   void someFunction();
   QPushButton *button = new QPushButton;
   QObject::connect(button, &QPushButton::clicked, someFunction);

第一个,sender 类型是const QObject *,signal 的类型是const char *,receiver 类型是const QObject *,slot 类型是const char *。这个函数将 signal 和 slot 作为字符串处理。
第二个,sender 和 receiver 同样是const QObject *,但是 signal 和 slot 都是const QMetaMethod &。我们可以将每个函数看做是QMetaMethod的子类。因此,这种写法可以使用QMetaMethod进行类型比对。
第三个,sender 同样是const QObject *,signal 和 slot 同样是const char *,但是却缺少了 receiver。这个函数其实是将 this 指针作为 receiver。
第四个,sender 和 receiver 也都存在,都是const QObject *,但是 signal 和 slot 类型则是PointerToMemberFunction。看这个名字就应该知道,这是指向成员函数的指针。
第五个,前面两个参数没有什么不同,最后一个参数是Functor类型。这个类型可以接受 static 函数、全局函数以及 Lambda 表达式。
其中第1,4,5形式都是比较常用的写法。
通过connect函数连接信号与槽,这很简单,下面我们看下平时写代码中很少用到第五个参数,也就是连接类型,因为这个参数有默认传参,所以我们几乎不用管它,用默认的Qt::AutoConnection即可。但有些特殊情况还是需要设置不同的连接类型的,下面我们看一下。

2. Qt::ConnectionType

ConnectionType 的枚举

enum ConnectionType {
        AutoConnection,
        DirectConnection,
        QueuedConnection,
        BlockingQueuedConnection,
        UniqueConnection =  0x80
    };

Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。

Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。

Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。

Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。

Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。

如果有过window下多线程开发经验,就会知道子线程一般是不给直接操作UI的,那咋办?像MFC都是通过API函数::SendMessage,子线程给UI线程发消息,把这个消息投递到UI的消息队列里,等UI消息执行这个消息的时候,完成更新UI动作。
了解这个原理后,在看一下以上ConnectionType枚举的介绍,好像能明白一点。子线程想要更新UI,如果在子线程中采用DirectConnection连接,则更新UI的动作就是在子线程完成,这种操作虽然可以,但是有潜在危险,极不推荐。这种操作类似.net中开启跨线程调用是一个道理,都是不建议使用的。

 //指定不再捕获对错误线程的调用
 Control.CheckForIllegalCrossThreadCalls = false;

下面我们使用QThread开启子线程,然后通过打印当前线程的id来具体看一下不同连接类型的区别,帮助理解

  1. 新建Qt Dialog工程;
  2. 添加QThread的子类ChildThread;
#ifndef CHILDTHREAD_H
#define CHILDTHREAD_H

#include 

typedef struct tagMSG
{
    QThread* thread;              //当前线程
    QString process_status;     //完成状态
} MSG;

class ChildThread : public QThread
{
    Q_OBJECT
public:
    explicit ChildThread();
    ~ChildThread();

protected:
    void run() override;

signals:
    void send2UI(const MSG& msg);
};

#endif // CHILDTHREAD_H

childthread.cpp

#include "childthread.h"
#include 

ChildThread::ChildThread()
{

}

ChildThread::~ChildThread()
{

}

void ChildThread::run()
{
    int count = 0;
    MSG msg;
    msg.thread = QThread::currentThread();
    msg.process_status = "waiting...";
    while(count++<5)
    {
        emit send2UI(msg);
        QThread::sleep(1);
    }
    msg.thread = QThread::currentThread();
    msg.process_status = "finished";
    emit send2UI(msg);
}

在主对话框中,添加一个按钮,并实现槽函数如下:

void Dialog::on_pushButtonChildThread_clicked()
{
    ChildThread* ch = new ChildThread;
    connect(ch, SIGNAL(send2UI(MSG)), this, SLOT(onShowInfo(MSG)), Qt::AutoConnection);
    ch->start();
    qDebug()<<"UI Thread:"<<QThread::currentThreadId();
}

void Dialog::onShowInfo(const MSG& msg)
{
    qDebug()<<"childThread:"<<msg.thread->currentThreadId();
    ui->lineEdit1->setText(msg.process_status);
}

连接类型为AutoConnection的运行结果:
Qt信号槽中connect五个重载函数详细说明,连接类型Qt::DirectConnection,Qt::QueuedConnection,附详细代码_第1张图片DirectConnection的运行结果:
Qt信号槽中connect五个重载函数详细说明,连接类型Qt::DirectConnection,Qt::QueuedConnection,附详细代码_第2张图片
QueuedConnection的运行结果同AutoConnection

不知道我描述的是不是很清楚,如果不清楚还是没有理解,建议动手写,去理解下。

你可能感兴趣的:(Qt,c++开发实战,qt,connect,AutoConnection)