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
。因此在本章範例中的兩個範例物件slot
與signal
,都已經繼承了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的實戰。