Signal與Slot


在開始之前我們先來看一下Qt Signal與Slot的基本機制概念圖
首先我們看到了兩個物件:Object1以及Object2這兩個物件分別有數個signal及數個slot。首先我們要先來講一個最基本的概念,signal與slot會利用connect()函式進行連結,而connect()這個函式是經由QObject繼承所得到的,因此我們在利用Qt Framework進行開發時,如果沒有特別的功能需求,我們會將這個物件繼承QObject不但可以讓你的程式碼在Qt中更好使用,也能獲取signal與slot的相關功能。

Signals

首先我們先來介紹一下Signals,signals在中文被我們翻成信號,可以透過emit進行發送訊號,那在以下的程式將為大家演示如何宣告signals以及使用它。
請注意:Signals與Slots是Qt中獨有的,在一般的C++中是無法使用的。

首先我們先來看一下signals要如何在物件中宣告,以下的範例在Header File裡。

#ifndef SIGNAL_H
#define SIGNAL_H

#include <QObject>

class Signal : public QObject
{
    Q_OBJECT
public:
    explicit Signal(QObject *parent = 0);
    void emitSendNothing(void);
    void emitSendInteger(int);
signals:
    //signals定義在這裡
    void signalSendNothing(void); //傳送未帶資料的signal
    void signalSendInteger(int); //傳送帶有int資料的signal
public slots:
};

#endif SIGNAL_H

我們在signals中定義了兩個函式signalSendNothing()signalSendInteger(int),首先如果我們要傳的信號並沒有要附帶任何資料,那我們就可以將其的參數設為void,另外,如果我們要傳送的信號要帶有資料,例如範例中的signalSendIneteger(int)這樣在送出時就會一併將資料傳出。

傳送Signals

接下來要演示如何將我們定義好的signals傳送出去。
我們來看一下自己定義的Signal物件的Source File:

#include "signal.h"

Signal::Signal(QObject *parent) : QObject(parent)
{
    //建構函式
}
void Signal::emitSendNothing(void){
    emit signalSendNothing();
    //透過emit就可以將signal傳出去。
}
void Signal::emitSendInteger(int m_Value){
    m_Value = (m_Value*100)-12;
    emit signalSendInteger(m_Value);
    //若要在傳送signal時帶有資料,只要把要傳送的資料帶入signal函式。
}

從範例中可以看到,只要透過一個簡單的emit步驟,就可以將我們定義的signals傳出去。

Slots

接下來要介紹slots,slots被我們稱為信號槽或信號插槽。他是負責用來接收signals所傳出的資料。
值得注意的是,與signals不同,slots會利用存取控制public protected以及private來定義他的被存取權限。

為什麼要這麼做?

因為signals是由物件主動發送,而slots是被動接收,因此若不經過存取控制保護,則我們可以隨意傳送signals到任意一個slots,這並不是一個好方法。

那接著我們來看一下要如何定義我們的slots呢?我們再創建第二個物件slot物件。

#ifndef SLOT_H
#define SLOT_H

#include <QObject>

class Slot : public QObject
{
    Q_OBJECT
public:
    explicit Slot(QObject *parent = 0);

signals:

public slots:
    //將public的slots定義在此
    void slotRecieveNothing(void);
    void slotRecieveInteger(int);
private slots:
    //將private的slot定義在此
    void pri_slotRecieveNothing(void);
};

#endif // SLOT_H

從範例中看到我們的slot物件中有兩個public slots函式及一個private slots函式。slotRecieveNothing()用於接收不帶任何資料的signal,而slotRecieveInteger(int)用於接收帶有int資料的signal,因此在本章的signals範例中所使用的signalSendInterger()就是要接到這裡。

定義slots的function body

與singals不一樣的是,signals並不需要定義function body,只需要定義其signals name以及附帶的資料,而slots需要定義function body,因為當slot接收到來自signals的訊號時,我們需要去定義他的響應動作。

我們來看一下slot物件的Source File:

#include "slot.h"
#include <QDebug>

Slot::Slot(QObject *parent) : QObject(parent)
{

}
void Slot::slotRecieveNothing(void){
    qDebug()<<"Slot catches signal";
}
void Slot::slotRecieveInteger(int value){
    qDebug()<<"Slot catches signal and data "<<value;
}

void Slot::pri_slotRecieveNothing(void){
    qDebug()<<"Private slot catches signal";

}

我們在這個檔案中也引入了QDebug,在console裡輸出資訊。
首先我們看slotRecieveNothing(),這個是用來接收無附帶資料的signal,slotRecieveInteger(),用來接收帶有int資料的slot。當signals發送資料後,其連接上的slot的就會顯示出相應的訊息。

Connect

接下來就進入我們的重頭戲了,我們要將我們定義的signals和slots連結在一起,讓他們能夠互相溝通。

首先之前有提到因為connect()屬於Qt Framework最基礎元件QObject的成員函式,因此如果要使用這個函式,通常我們會繼承QObject。因此在本章範例中的兩個範例物件slotsignal,都已經繼承了QObject

首先我們要來測試最基本的slots與signals連結:
我們在slot物件中建立一個signal物件。

#ifndef SLOT_H
#define SLOT_H

#include <QObject>
#incldue "signal.h"

class Slot : public QObject
{
    Q_OBJECT
public:
    explicit Slot(QObject *parent = 0);

signals:

public slots:
    //將public的slots定義在此
    void slotRecieveNothing(void);
    void slotRecieveInteger(int);
private slots:
    //將private的slot定義在此
    void pri_slotRecieveNothing(void);
private:
    Signal *mySignal;
};

#endif // SLOT_H

接下來我們來在Source File中定義connect。 在用之前我們要先看一下connect的函式介面:

//第一種,這個函式是`QObject`中的Static Public Function
connect(const QObject * sender, const QMetaMethod & signal, const QObject * receiver, const QMetaMethod & method, Qt::ConnectionType type = Qt::AutoConnection)

第一個參數 : 傳送者物件指標,此物件必須繼承QObject
第二個參數 : 傳送者的signal。
第二個參數 : 接收者物件指標,此物件必須繼承QObject
第四個參數 : 接收者的slot。
第五個參數 : 連結類型,預設為Qt::AutoConnection

或是你也可以利用QObject中的成員函式:connect()
其函式介面如下:

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

你也可以在物件內直接定義signals的來源。

那我們可以開始來看如何連接signal與slot了。我們來看看slot物件中的定義:

#include "slot.h"
#include <QDebug>

Slot::Slot(QObject *parent) : QObject(parent)
{
    //建構函式
    mySignal = new Signal();
    connect(mySignal,SIGNAL(signalSendNothing()),this,SLOT(slotRecieveNothing())); //利用第一種的static public fuction來做連結。
    connect(mySignal,SIGNAL(signalSendInteger),SLOT(slotRecieveInteger())); //利用QObject的成員函式來做連結。
}
void Slot::slotRecieveNothing(void){
    qDebug()<<"Slot catches signal";
}
void Slot::slotRecieveInteger(int value){
    qDebug()<<"Slot catches signal and data "<<value;
}

void Slot::pri_slotRecieveNothing(void){
    qDebug()<<"Private slot catches signal";
}

SLOT()和SIGNAL()是巨集(macro)函式,因此不需引入任何東西

首先我們看一個方式,因為連結物件的位置,也許不會在這兩個物件裡,有可能會用其他物件來進行連結,這時候就要用第一種static public function來做連結了。再來,像今天的範例中我們是在slot物件中與signal做連結,這樣就可以利用第二種member function的方式,直接指定接收的slot即可。

那如果今天使用的是沒有繼承QObject的物件怎辦?

如果我們要進行connect,但是卻不能使用QObject的member function時(例如在int main()函式中),那我們只要呼叫QObject中的static public function的connect即可。

int main(){
    //...(略)...
    Signal mySignal;
    Slot mySlot;
    QObject::connect(&mySignal,SIGNAL(signalRecieveNothing()),&mySlot,SLOT(slotRecieveNothing()));
}

disconnect

當我們想要斷開signal與slot的連結只要利用diconnect()斷開連結即可。 用法與connect()一模一樣:

QObject::disconnect(&mySignal,SIGNAL(signalRecieveNothing()),&mySlot,SLOT(slotRecieveNothing()))

利用QPushButton實做Signals與Slots

因為剛才的示範都是在純文字上運作,很難演示出signal與slot的效果,接下來要利用Qt widget來進行signals與slots的實戰。

首先我們要來建立一個Qt Widgets Application,接著我們進入Design Mode來加入一個按鈕。 運行後會出現一個按鈕: 接著我們要在Design Mode中開啟這個按鈕的OnClicked的動作。
在Design Mode中按下對這個我們拉進來的按鈕點擊右鍵,選擇Go to Slot(跳到信號槽)並選擇clicked() 按下之後你就會發現在我們的MainWindow物件中出現了一個private slot:void on_pushButton_clicked(),其實當QPushButton被按下時,會emit一個clicked()的訊號來連接這個slot。 不過我們要實做的不是這個部份。 這就是它自動新增的slot函式。 請注意:on_pushButton_cliecked()這個函式可能因為你的按鈕名稱不同而函式名稱不同,若想要改變這個函式名稱,請直接到Design Mode中進行更改即可。

接下來我們要來建立另一個物件了,我們要把它命名為Reciever並繼承QObject

然後我們來這樣定義我們的Reciever物件:

#ifndef RECIEVER_H
#define RECIEVER_H

#include <QObject>

class Reciever : public QObject
{
    Q_OBJECT
public:
    explicit Reciever(QObject *parent = 0);

signals:

public slots:
    void slotRecieveButtonClicked(); //用來接收按鈕送出的訊號。
};

然後來看看Source File:

#include "reciever.h"

#include <QDebug>

Reciever::Reciever(QObject *parent) : QObject(parent)
{

}
void Reciever::slotRecieveButtonClicked(){
    qDebug()<<"Button Clicked!";
}

slotRecieveButtonClicked()這個slot中我們定義若收到訊息,就在console中顯示"〝Button Clicked!"。

接下來我們來在MainWindow物件中定義要連接slotRecieveButtonClicked()的signal:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include "reciever.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

signals:
    void connectToReciever();

private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;
    Reciever *reciever;
};

#endif // MAINWINDOW_H

接下來我們來看看MainWindow物件的Source File:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    reciever = new Reciever;
    connect(this,SIGNAL(connectToReciever())    ,reciever,SLOT(slotRecieveButtonClicked()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    emit connectToReciever();
}

接下來我們按下運行,並點擊按鈕。 恭喜你完成了~ 這樣我們就完成了signal與slot的實戰。

results matching ""

    No results matching ""