【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 

相關文章
相關標籤/搜索