Qt自定義訊號槽的使用淺析+例項

進擊的汪sir發表於2021-06-13

1. Qt中自定義訊號槽的使用

Qt框架提供的訊號槽在某些特定場景下是無法滿足我們的專案需求的,因此我們還設計自己需要的的訊號和槽,使用connect()對自定義的訊號槽進行連線。

如果想要使用自定義的訊號槽, 首先要編寫新的類並且讓其繼承Qt的某些標準類,我們自己編寫的類想要在Qt中使用使用訊號槽機制, 那麼必須要滿足的如下條件:

  • 這個類必須從QObject類或者是其子類進行派生
  • 在定義類的標頭檔案中加入 Q_OBJECT 巨集

1.1 自定義訊號

要求:
1. 訊號是類的成員函式
2. 返回值是 void 型別
3. 訊號的名字可以根據實際情況進行指定
4. 引數可以隨意指定, 訊號也支援過載
5. 訊號需要使用 signals 關鍵字進行宣告, 使用方法類似於public等關鍵字
6. 訊號函式只需要宣告, 不需要定義(沒有函式體實現)
7. 在程式中傳送自定義訊號: 傳送訊號的本質就是呼叫訊號函式

  • 習慣性在訊號函式前加關鍵字: emit
  • emit只是顯示的宣告一下訊號要被髮送, 沒有特殊含義
  • 底層 emit == #define emit

示例

class Test : public QObject
{
    Q_OBJECT
signals:
    void testsignal();
	// 引數的作用是資料傳遞, 誰呼叫訊號函式誰就指定實參
	// 實參最終會被傳遞給槽函式
    void testsignal(int a);
};

1.2 自定義槽

槽函式就是訊號的處理動作,自定義槽函式和自定義的普通函式寫法是一樣的。

要求:

  1. 返回值是 void 型別
  2. 槽也是函式, 因此也支援過載
  • 槽函式需要指定多少個引數, 需要看連線的訊號的引數個數
  • 槽函式的引數是用來接收訊號傳送的資料的, 訊號傳送的資料就是訊號的引數
  • 舉例:
    訊號函式: void testsig(int a, double b);
    槽函式: void testslot(int a, double b);
  • 總結:
    槽函式的引數應該和對應的訊號的引數個數, 型別一一對應
    訊號的引數可以大於等於槽函式的引數個數 == 訊號傳遞的資料被忽略了
    訊號函式: void testsig(int a, double b);
    槽函式: void testslot(int a);
    這裡槽函式只接受訊號函式中的第一個引數
  1. Qt中槽函式的型別:
    - 類的成員函式
    - 全域性函式
    - 靜態函式
    - lambda表示式(匿名函式)
  2. 槽函式可以使用關鍵字進行宣告: slots (Qt5中slots可以省略不寫)
    - public slots:
    - private slots:
    - protected slots:
// 舉例
// 類中的這三個函式都可以作為槽函式來使用
class Test : public QObject
{
public:
    void testSlot();
    static void testFunc();

public slots:
    void testSlot(int id);
};

1.3 自定義訊號槽例項

現在有一個場景,女朋友餓了,我請她吃飯,那麼實現這個功能應該怎麼做呢
首先明確傳送者,接收者,訊號和槽分別是哪些

  • 傳送者: 女朋友
  • 接收者: 我
  • 訊號: 餓了
  • 槽:請她吃飯

ok,明確了這些,接下來我們就可以開始寫程式碼了

首先建立兩個類,GirlFriend 和 Me
Qt Creator中會自動為我們新增標頭檔案和CPP檔案,目錄結構如下圖
在這裡插入圖片描述

  1. 在GirlFriend類中,新增訊號hungry,程式碼如下
#ifndef GIRLFRIEND_H
#define GIRLFRIEND_H

#include <QObject>

class GirlFriend : public QObject
{
    Q_OBJECT
public:
    explicit GirlFriend(QObject *parent = nullptr);

signals:
    void hungry();

};

#endif // GIRLFRIEND_H

注意圖中的 signals關鍵字,這個就是用來定義訊號的地方,訊號函式只需要定義,不需要實現!

  1. 在Me這個類中新增槽函式eat();
#ifndef ME_H
#define ME_H

#include <QObject>

class Me : public QObject
{
    Q_OBJECT
public:
    explicit Me(QObject *parent = nullptr);

    // 槽函式
public slots:
    // 槽函式
    void eat();

};

#endif // ME_H

注意!
這裡用public slots主要是為了提醒開發者,這是一個槽函式,事實上,可以不用單獨用public slots,可以直接將這個槽函式放到public中,與普通函式一樣,槽函式不僅需要定義,也需要實現。

  1. 到me.cpp中實現Me類的槽函式eat()
#include "me.h"
#include <QDebug>
Me::Me(QObject *parent) : QObject(parent)
{

}

void Me::eat()
{
    qDebug() << "帶你去吃麻辣燙...";
}

ok,現在槽函式和訊號函式都已經定義實現了,那怎麼樣實現事件的響應呢,一個簡單的想法是,設定一個按鈕,點選按鈕傳送訊號:hungry,然後讓eat()響應

  1. 到mainwindow中新增一個按鈕Hungry,取名為hungry
    在這裡插入圖片描述
    如果這個時候出現在mainwindow.cpp中,無法識別這個按鈕,可以參考我的這篇部落格
    Qt專案ui檔案新新增的控制元件在程式碼中不識別的問題解決

  2. 新增這個按鈕後,我們需要在mainwindow類中新增兩個成員指標
    在這裡插入圖片描述

  3. 在mainwindow.cpp中通過connect函式來繫結

在這裡我再複習一遍Qt中的 connect() 函式

QMetaObject::Connection QObject::connect(
    	const QObject *sender, PointerToMemberFunction signal, 
        const QObject *receiver, PointerToMemberFunction method, 
		Qt::ConnectionType type = Qt::AutoConnection);
- 引數:
	- sender: 發出訊號的物件
	- signal: 屬於sender物件, 訊號是一個函式, 這個引數的型別是函式指標, 訊號函式地址
    - receiver: 訊號接收者
	- method: 屬於receiver物件, 當檢測到sender發出了signal訊號, 
              receiver物件呼叫method方法,訊號發出之後的處理動作
                  
// connect函式相對於做了訊號處理動作的註冊
// 呼叫conenct函式的sender物件的訊號並沒有產生, 因此receiver物件的method也不會被呼叫
// method槽函式本質是一個回撥函式, 呼叫的時機是訊號產生之後, 呼叫是Qt框架來執行的
// connect中的sender和recever兩個指標必須被例項化了, 否則conenct不會成功
connect(const QObject *sender, &QObject::signal, 
        const QObject *receiver, &QObject::method);

知道connect函式的用法之後,我們先將my_girl傳送訊號,m_me(m_girl和m_me 是上面加的兩個成員指標)接受訊號繫結在一起
在mainwindow.cpp的建構函式中新增如下語句

	m_me = new Me;
    m_girl = new GirlFriend;

    // hungry訊號是自定義的,它不能由框架去傳送,因為框架壓根就不知道有這個訊號的存在,因此需要在特定的時機,使用者自己去發射這個訊號
    connect(m_girl,&GirlFriend::hungry,m_me,&Me::eat);
    

注意connect上面的註釋
hungry訊號是自定義的,它不能由框架去傳送,因為框架壓根就不知道有這個訊號的存在,因此需要在特定的時機,使用者自己去發射這個訊號

簡單理解就是,你的girl要傳送hungry這個訊號是不能自動完成的,因為Qt框架不知道hungry這個訊號,你要通過點選按鈕來讓girl傳送訊號,因此需要另外一個函式,取名為hungrySlot(),用來實現點選按鈕,讓girl傳送訊號

  1. 在mainwindow中定義並實現函式hungrySlot,函式定義的程式碼我就不放了,大家自己去定義
    下面是實現程式碼
void MainWindow::hungrySlot()
{
    // 發射自定義訊號
    emit m_girl->hungry();
}

注意!
這裡的emit關鍵字也是可有可不有的 ,但是還是建議大家寫,用來提醒開發人員這個函式是發射自定義訊號的函式

  1. 現在是最關鍵的一步啦,將按鈕與girl發射hungry訊號的函式繫結,按鈕被點選這個事件是可以由框架實現傳送的,所以不需要我們擔心

在mainwindow的建構函式中繫結,程式碼如下


connect(ui->hungry,&QPushButton::clicked,this,&MainWindow::hungrySlot);

這樣,自定義訊號槽的使用就歐克啦

接下來我們去測試一下,run一手

  1. 執行結果

在這裡插入圖片描述
可以看到,每次當我點選Hungry按鈕,底下就會出現 “帶你去吃麻辣燙”,證明我們自定義訊號槽成功了

編寫不易,大家要是轉載啥的記得標明一下哦~

相關文章