【Qt笔记】自定义信号槽

上一节咱们详细分析了connect()函数。使用connect()可让咱们链接系统提供的信号和槽。可是,Qt 的信号槽机制并不只仅是使用系统提供的那部分,还会容许咱们本身设计本身的信号和槽。这也是 Qt 框架的设计思路之一,用于咱们设计解耦的程序。本节将讲解如何在本身的程序中自定义信号槽。app

信号槽不是 GUI 模块提供的,而是 Qt 核心特性之一。所以,咱们能够在普通的控制台程序使用信号槽。框架

经典的观察者模式在讲解举例的时候一般会举报纸和订阅者的例子。有一个报纸类Newspaper,有一个订阅者类SubscriberSubscriber能够订阅Newspaper。这样,当Newspaper有了新的内容的时候,Subscriber能够当即获得通知。在这个例子中,观察者是Subscriber,被观察者是Newspaper。在经典的实现代码中,观察者会将自身注册到被观察者的一个容器中(好比subscriber.registerTo(newspaper))。被观察者发生了任何变化的时候,会主动遍历这个容器,依次通知各个观察者(newspaper.notifyAllSubscribers())。函数

下面咱们看看使用 Qt 的信号槽,如何实现上述观察者模式。注意,这里咱们仅仅是使用这个案例,咱们的代码并非去实现一个经典的观察者模式。也就是说,咱们使用 Qt 的信号槽机制来得到一样的效果。工具

//!!! Qt5

// newspaper.h
#include <QObject>
 
class Newspaper : public QObject
{
    Q_OBJECT
public:
    Newspaper(const QString & name) :
        m_name(name)
    {
    }
 
    void send()
    {
        emit newPaper(m_name);
    }
 
signals:
    void newPaper(const QString &name);
 
private:
    QString m_name;
};
 

// reader.h
#include <QObject>
#include <QDebug>
 
class Reader : public QObject
{
    Q_OBJECT
public:
    Reader() {}
 
    void receiveNewspaper(const QString & name)
    {
        qDebug() << "Receives Newspaper: " << name;
    }
};

 
// main.cpp
#include <QCoreApplication>
 
#include "newspaper.h"
#include "reader.h"
 
int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
 
    Newspaper newspaper("Newspaper A");
    Reader reader;
    QObject::connect(&newspaper, &Newspaper::newPaper,
                     &reader,    &Reader::receiveNewspaper);
    newspaper.send();
 
    return app.exec();
}

咱们运行上面的程序时,会看到终端输出 Receives Newspaper: Newspaper A 这样的字样。spa

下面咱们来分析下自定义信号槽的代码。.net

首先看Newspaper这个类。这个类继承了QObject类。只有继承了QObject类的类,才具备信号槽的能力。因此,为了使用信号槽,必须继承QObject。凡是QObject类(不论是直接子类仍是间接子类),都应该在第一行代码写上Q_OBJECT。不论是不是使用信号槽,都应该添加这个宏。这个宏的展开将为咱们的类提供信号槽机制、国际化机制以及 Qt 提供的不基于 C++ RTTI 的反射能力。所以,若是你以为你的类不须要使用信号槽,就不添加这个宏,就是错误的。其它不少操做都会依赖于这个宏。注意,这个宏将由 moc(咱们会在后面章节中介绍 moc。这里你能够将其理解为一种预处理器,是比 C++ 预处理器更早执行的预处理器。) 作特殊处理,不只仅是宏展开这么简单。moc 会读取标记了 Q_OBJECT 的头文件,生成以 moc_ 为前缀的文件,好比 newspaper.h 将生成 moc_newspaper.cpp。你能够到构建目录查看这个文件,看看到底增长了什么内容。注意,因为 moc 只处理头文件中的标记了Q_OBJECT的类声明,不会处理 cpp 文件中的相似声明。所以,若是咱们的NewspaperReader类位于 main.cpp 中,是没法获得 moc 的处理的。解决方法是,咱们手动调用 moc 工具处理 main.cpp,而且将 main.cpp 中的#include "newspaper.h"改成#include "moc_newspaper.h"就能够了。不过,这是至关繁琐的步骤,为了不这样修改,咱们仍是将其放在头文件中。许多初学者会遇到莫名其妙的错误,一加上Q_OBJECT就出错,很大一部分是由于没有注意到这个宏应该放在头文件中。设计

Newspaper类的 public 和 private 代码块都比较简单,只不过它新加了一个 signals。signals 块所列出的,就是该类的信号。信号就是一个个的函数名,返回值是 void(由于没法得到信号的返回值,因此也就无需返回任何值),参数是该类须要让外界知道的数据。信号做为函数名,不须要在 cpp 函数中添加任何实现(咱们曾经说过,Qt 程序可以使用普通的 make 进行编译。没有实现的函数名怎么会经过编译?缘由仍是在 moc,moc 会帮咱们实现信号函数所须要的函数体,因此说,moc 并非单纯的将 Q_OBJECT 展开,而是作了不少额外的操做)code

Newspaper类的send()函数比较简单,只有一个语句emit newPaper(m_name);。emit 是 Qt 对 C++ 的扩展,是一个关键字(其实也是一个宏)。emit 的含义是发出,也就是发出newPaper()信号。感兴趣的接收者会关注这个信号,可能还须要知道是哪份报纸发出的信号?因此,咱们将实际的报纸名字m_name当作参数传给这个信号。当接收者链接这个信号时,就能够经过槽函数得到实际值。这样就完成了数据从发出者到接收者的一个转移。对象

Reader类更简单。由于这个类须要接受信号,因此咱们将其继承了QObject,而且添加了Q_OBJECT宏。后面则是默认构造函数和一个普通的成员函数。Qt 5 中,任何成员函数、static 函数、全局函数和 Lambda 表达式均可以做为槽函数。与信号函数不一样,槽函数必须本身完成实现代码。槽函数就是普通的成员函数,所以做为成员函数,也会受到 public、private 等访问控制符的影响。(咱们没有说信号也会受此影响,事实上,若是信号是 private 的,这个信号就不能在类的外面链接,也就没有任何意义。)继承

main()函数中,咱们首先建立了NewspaperReader两个对象,而后使用QObject::connect()函数。这个函数咱们上一节已经详细介绍过,这里应该可以看出这个链接的含义。而后咱们调用Newspapersend()函数。这个函数只有一个语句:发出信号。因为咱们的链接,当这个信号发出时,自动调用 reader 的槽函数,打印出语句。

这样咱们的示例程序讲解完毕。咱们基于 Qt 的信号槽机制,不须要观察者的容器,不须要注册对象,就实现了观察者模式。

下面总结一下自定义信号槽须要注意的事项:

  • 发送者和接收者都须要是QObject的子类(固然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
  • 使用 signals 标记信号函数,信号是一个函数声明,返回 void,不须要实现函数代码;
  • 槽函数是普通的成员函数,做为成员函数,会受到 public、private、protected 的影响;
  • 使用 emit 在恰当的位置发送信号;
  • 使用QObject::connect()函数链接信号和槽。 

 

本文转载自www.devbean.net,做者devdean