信號和槽
Linux信號 Signal 系統內部的通知機制. 進程間通信的方式.
- 信號源:誰發的信號.
- 信號的類型:哪種類別的信號
- 信號的處理方式:注冊信號處理函數,在信號被觸發的時候自動調用執行.
Qt中的信號和Linux中的信號,雖然不是一樣的概念,但是確實有相似之處
Qt中,談到信號,也是涉及到三個要素信號源:
- 由哪個控件發出的信號
- 信號的類型:用戶進行不同的操作,就可能觸發不同的信號
- 點擊按鈕,觸發點擊信號.
- 在輸入框中移動光標,觸發移動光標的信號,勾選一個復選框
- 選擇一個下拉框都會觸發出不同的信號
咱們寫的GUI程序,就是要讓用戶進行操作.就是要和用戶進行交互這個過程中就需要關注,用戶當前的操作具體是個什么樣的操作
- 信號的處理方式:槽(slot)=>函數
Qt中可以使用connect這樣的函數,把一個信號和一個槽關聯起來后續只要信號觸發了,Qt就會自動的執行槽函數
所謂的"槽函數"本質上也是一種"回調函數"(callback)
最早C語言階段
C 進階=>指針進階=>函數指針.
- 實現轉移表,降低代碼的"圈復雜度”.
- 實現回調函數效果=〉qsort
后來在C++階段~
- STL中,函數對象/仿函數
- lambda 表達式.
后來在Linux中
- 信號處理函數
- 線程的入口函數.
- epoll基于回調的機制
一定是先把信號的處理方式準備好,再觸發信號~
Qt中,一定是先關聯號信號和槽,然后再觸發這個信號.順序不能顛倒,否則信號就不知道如何處理了(錯過了).
信號和槽概述
在Qt中,??和控件的每次交互過程稱為?個事件。?如"??點擊按鈕"是?個事件,"??關閉窗?"也是?個事件。每個事件都會發出?個信號,例如??點擊按鈕會發出"按鈕被點擊"的信號,??關閉窗?會發出"窗?被關閉"的信號。
Qt中的所有控件都具有接收信號的能?,?個控件還可以接收多個不同的信號。對于接收到的每個信號,控件都會做出相應的響應動作。例如,按鈕所在的窗?接收到"按鈕被點擊"的信號后,會做出"關閉??"的響應動作;再?如輸?框??接收到"輸?框被點擊"的信號后,會做出"顯?閃爍的光標,等待??輸?數據"的響應動作。在Qt中,對信號做出的響應動作就稱之為槽。
信號和槽是Qt特有的消息傳輸機制,它能將相互獨?的控件關聯起來。?如,"按鈕"和"窗?"本?是兩個獨?的控件,點擊"按鈕"并不會對"窗?"造成任何影響。通過信號和槽機制,可以將"按鈕"和"窗?"關聯起來,實現"點擊按鈕會使窗?關閉"的效果。
信號的本質
信號是由于??對窗?或控件進?了某些操作,導致窗?或控件產?了某個特定事件,這時Qt對應的窗?類會發出某個信號,以此對??的操作做出反應。因此,信號的本質就是事件。如:
- 按鈕單擊、雙擊
- 窗?刷新
- ?標移動、?標按下、?標釋放
- 鍵盤輸?
那么在Qt中信號是通過什么形式呈現給使?者的呢? - 我們對哪個窗?進?操作,哪個窗?就可以捕捉到這些被觸發的事件。
- 對于使?者來說觸發了?個事件我們就可以得到Qt框架給我們發出的某個特定信號。
- 信號的呈現形式就是函數,也就是說某個事件產?了,Qt框架就會調?某個對應的信號函數,通知使?者。
在Qt中信號的發出者是某個實例化的類對象。
槽的本質
槽(Slot)就是對信號響應的函數。槽就是?個函數,與?般的C++函數是?樣的,可以定義在類的任何位置(public、protected或private),可以具有任何參數,可以被重載,也可以被直接調?(但是不能有默認參數)。槽函數與?般的函數不同的是:槽函數可以與?個信號關聯,當信號被發射時,關聯的槽函數被?動執?。
說明
(1)信號和槽機制底層是通過函數間的相互調?實現的。每個信號都可以?函數來表?,稱為信號函數;每個槽也可以?函數表?,稱為槽函數。例如:"按鈕被按下"這個信號可以?clicked()函數表?,"窗?關閉"這個槽可以?close()函數表?,假如使?信號和槽機制-實現:"點擊按鈕會關閉窗?"的功能,其實就是clicked()函數調?close()函數的效果。
(2)信號函數和槽函數通常位于某個類中,和普通的成員函數相?,它們的特別之處在于:
- 信號函數?signals關鍵字修飾,槽函數?public slots、protected slots 或者private slots修 飾。signals和slots是Qt在C++的基礎上擴展的關鍵字,專??來指明信號函數和槽函數;
- 信號函數只需要聲明,不需要定義(實現),?槽函數需要定義(實現)。
信號函數的定義是Qt?動在編譯程序之前?成的.編寫Qt應?程序的程序猿?需關注.
這種?動?成代碼的機制稱為元編程(Meta Programming).這種操作在很多場景中都能?到.
信號和槽的使?
連接信號和槽
在Qt中,QObject類提供了?個靜態成員函數connect(),該函數專??來關聯指定的信號函數和槽函數
QObject是Qt內置的?類.Qt中提供的很多類都是直接或者間接繼承?QObject.
connect()函數原型:
connect (const QObject *sender, const char * signal , const QObject * receiver , const char * method , Qt::ConnectionType type = Qt::AutoConnection )
參數說明:
- sender:信號的發送者;(哪個控件)
- signal:發送的信號(信號函數);(信號的類型)
- receiver:信號的接收者;(控件)
- method:接收信號的槽函數;(要處理信號的對象提供的成員函數)
- type:?于指定關聯?式,默認的關聯?式為Qt::AutoConnection,通常不需要?動設定
代碼?例:在窗?中設置?個按鈕,當點擊"按鈕"時關閉"窗?"。
#include "widget.h"
#include "ui_widget.h"#include <QPushButton>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QPushButton* button = new QPushButton(this);button->setText("關閉");button->move(200, 200);connect(button, &QPushButton::clicked, this, &Widget::close);
}Widget::~Widget()
{delete ui;
}
所謂的信號也就是Qt中的對象,內部提供的一些成員函數
圖標帶有鋸齒,slot函數,click是一個slot函數,作用就是在調用的時候相當于點擊了一下按鈕
帶有類似wifi的圖標,就是信號函數,clicked,才是要觸發的點擊信號
button, &QPushButton::clicked
connect函數要求,這倆參數是匹配的,button的類型如果是QPushButton*,此時第二個參數的信號必須是QPushButton內置的信號或者父類的信號,不能是其他的類的信號
this, &Widget::close
close是QWidget內置的槽函數,Widget繼承自QWidget,也就繼承了父親的槽函數
close槽函數功能已經是內部實現好的,具體作用就是關閉當前的窗口/控件
connect(button, &QPushButton::clicked, this, &Widget::close);
針對button,進行點擊操作,Widget就會關閉
具體可以查看Qt文檔
connect中的char*
參數
但是傳入的是&QPushButton::clicked, &Widget::close函數指針
void(*)();
bool(*)();
這兩個函數指針的類型也是不同的
這個函數聲明,是以前l日版本的Qt的connect函數的聲明
以前版本中,傳參的寫法和現在其實也是有區別的此時,給信號參數傳參,要搭配一個SIGNAL宏. 給槽參數傳參,搭配一個SLOT宏.
傳入的函數指針轉成char*
connect(button, SIGNAL(&QPushButton::clicked), this, SLOT(&Widget::close));
Qt 5開始,對上述寫法做出了簡化.不再需要寫SIGNAL 和SLOT宏了.
給connect提供了重載版本.重載版本中,第二個參數和第四個參數成了泛型參數.允許咱們傳入任意類型的函數指針了.
QtPrivate::FunctionPointer<Func1>::Object
Qt封裝的類型萃取器
此時connect函數就帶有了一定的參數檢查功能
如果你傳入的第一個參數和第二個參數不匹配,或者第三個參數和第四個參數不匹配.(不匹配,2,4參數的函數指針,不是1,3 參數的成員函數)
此時代碼編譯出錯
自定義槽函數1
widget.cpp
#include "widget.h"
#include "ui_widget.h"#include <QPushButton>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QPushButton* button = new QPushButton(this);button->setText("按鈕");button->move(100, 100);connect(button, &QPushButton::clicked, this, &Widget::handleClicked);
}Widget::~Widget()
{delete ui;
}void Widget::handleClicked()
{//按下按鈕,修改窗口標題this->setWindowTitle("按鈕已經按下");
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void handleClicked();private:Ui::Widget *ui;
};
#endif // WIDGET_H
按下按鈕
所謂的slot就是一個普通的成員函數
所謂的自定義一個槽函數,操作過程和自定義一個普通的成員函數,沒啥區別!在以前版本的Qt中,槽函數必須放到public/private/protected slots:
此處的slots是Qt自己擴展的關鍵字.(不是C++標準中的語法) Qt里廣泛使用了元編程技術. (基于代碼,生成代碼)
qmake 構建Qt項目的時候,就會調用專門的掃描器,掃描代碼中特定的關鍵字.(slots這種) 基于關鍵字自動生成一大堆相關的代碼,
自定義槽函數2
圖形化方式拖入一個PushButton
鼠標右鍵點擊PushButton
點擊轉到槽
這個窗口列出了QPushButton提供的所有信號,包含了QPushButton父類的信號
雙擊clicked
直接生成好了一個函數,聲明也生成好了
可以直接編寫代碼
void Widget::on_pushButton_clicked()
{this->setWindowTitle("按鈕已經按下");
}
在Qt中,除了通過connect來連接信號槽之外,還可以通過函數名字的方式來自動連接
void Widget::on_pushButton_clicked()
?動?成槽函數的名稱有?定的規則。槽函數的命名規則為:on_XXX_SSS,其中:
- 以"on"開頭,中間使?下劃線連接起來;
- "XXX"表?的是對象名(控件的 objectName 屬性)。
- "SSS"表?的是對應的信號。
如:“on_pushButton_clicked()”,pushButton代表的是對象名,clicked是對應的信號。
Qt中調用這個函數的時候,就會觸發上述自動連接信號槽的規則!!正是在自動生成的ui_widget.h中調用的~
如果我們通過圖形化界面創建控件,還是推薦使用這種快速的方式來連接信號槽
如果我們是通過代碼的方式來創建控件,還是得手動connect.(你的代碼中沒有調用connectSlotsByName)
自定義信號
Qt中也充許自定義信號
自定義槽函數,非常關鍵.開發中大部分情況都是需要自定義槽函數的槽函數,就是用戶觸發某個操作之后,要進行的業務邏輯
自定義信號,比較少見.實際開發中很少會需要自定義信號.
信號就對應到用戶的某個操作~
在GUI,用戶能夠進行哪些操作,是可以窮舉的~~
Qt內置的信號,基本上已經覆蓋到了上述所有可能的用戶操作
因此,使用Qt內置的信號,就足以應付大部分的開發場景了,
自定義信號,本身代碼比較簡單的~
Widget雖然還沒有定義任何信號,由于繼承自QWidget,和QObject,這倆類里面已經提供了一些信號了,可以直接使用.
所謂的Qt的信號,本質上也就是一個"函數
Qt5以及更高版本中,槽函數和普通的成員函數之間,沒啥差別了. 但是,信號,則是一類非常特殊的函數,
- 程序員只要寫出函數聲明,并且告訴Qt,這是一個“信號”即可, 1.
這個函數的定義,是Qt在編譯過程中,自動生成的.(自動生成的過程,程序員無法干預)
信號在Qt中是特殊的機制.Qt生成的信號函數的實現,要配合Qt框架做很多既定的操作~~ - 作為信號函數,這個函數的返回值,必須是void.
有沒有參數都可以.甚至也可以支持重載
這個也是Qt自己擴展出來的關鍵字~~
qmake的時候,調用一些代碼的分析/生成工具,
掃描到類中包含signals這個關鍵字的時候,此時,就會自動的把下面的函數聲明認為是信號,并且給這些信號函數自動的生成函數定義
widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();
signals:void mySignal();
public slots:void handleMySignal();private:Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(this, &Widget::mySignal, this, &Widget::handleMySignal);//發送自定義信號emit mySignal();
}Widget::~Widget()
{delete ui;
}void Widget::handleMySignal()
{this->setWindowTitle("處理自定義信號");
}
如何才能觸發出自定義的信號呢?
Qt內置的信號,都不需要咱們手動通過代碼來觸發
用戶在GUI,進行某些操作,就會自動觸發對應信號.(發射信號的代碼已經內置到Qt框架中了)
自定義的信號需要通過emit,來發射信號
emit mySignal();
在啟動的時候直接觸發信號
發送信號的操作,可以在任何合適的代碼中,不一定非要在構造函數中
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(this, &Widget::mySignal, this, &Widget::handleMySignal);}Widget::~Widget()
{delete ui;
}void Widget::handleMySignal()
{this->setWindowTitle("處理自定義信號");
}void Widget::on_pushButton_clicked()
{//發送自定義信號emit mySignal();
}
通過圖形化方式創建一個槽函數
點擊按鈕
點擊按鈕->
QPushButton::clicked->
Widget::on_pushButton_clicked()->
emit mySignal();->
void Widget::handleMySignal()
其實在Qt5中emit 現在啥都沒做
真正的操作都包含在mySignal內部生成的函數定義了
emit mySignal();
即使不寫emit,信號也能發出去!!
即使如此,實際開發中,還是建議大家,把emit都加上
加上代碼可讀性更高,更明顯的標識出,這里是發射自定義的信號了.
自定義的基本語法
在Qt中,允許?定義信號的發送?以及接收?,即可以?定義信號函數和槽函數。但是對于?定義的
信號函數和槽函數有?定的書寫規范。
1、?定義信號函數書寫規范
- ?定義信號函數必須寫到"signals"下;
- 返回值為void,只需要聲明,不需要實現;
- 可以有參數,也可以發?重載;
2、?定義槽函數書寫規范
- 早期的Qt版本要求槽函數必須寫到"public slots"下,但是現在?級版本的Qt允許寫到類的"public"作?域中或者全局下;
- 返回值為void,需要聲明,也需要實現;
- 可以有參數,可以發?重載;
3、發送信號
使?"emit"關鍵字發送信號。"emit"是?個空的宏。"emit"其實是可選的,沒有什么含義,只是為了提醒開發?員。
必須先關聯再發射
原因是,?先關聯信號和槽,?旦檢測到信號發射之后就會??執?關聯的槽函數。反之,若先發射信號,此時還沒有關聯槽函數,當信號發射之后槽函數不會響應
帶參數的信號和槽
Qt的信號和槽也?持帶有參數,同時也可以?持重載.
此處我們要求,信號函數的參數列表要和對應連接的槽函數參數列表?致.
此時信號觸發,調?到槽函數的時候,信號函數中的實參就能夠被傳遞到槽函數的形參當中
signals:void mySignal(const QString& text);public slots:void handleMySignal(const QString& text);
這里的參數必須一致
一致主要是要求類型一致,個數如果不一致也可以,不一致的時候,信號的參數的個數必須比槽的參數的個數更多
void Widget::handleMySignal(const QString& text)
{this->setWindowTitle(text);
}void Widget::on_pushButton_clicked()
{//發送自定義信號emit mySignal("帶參數的信號");
}
點擊按鈕
傳參可以起到復用代碼的效果
有多個邏輯,邏輯上整體一致,但是涉及到的數據不同,
就可以通過函數-參數來復用代碼,并且在不同的場景中傳入不同的參數即可~
新建一個PushButton
轉到槽,新建一個clicked函數
void Widget::on_pushButton_clicked()
{//發送自定義信號emit mySignal("把標題設置為標題1");
}void Widget::on_pushButton_2_clicked()
{emit mySignal("把標題設置為標題2");
}
通過這一套信號槽,搭配不同的參數,就可以起到設置不同標題的效果
Qt中很多內置的信號,也是帶有參數的.(這些參數不是咱們自己傳遞的)
clicked信號就帶有一個參數~~
這個參數表示當前按鈕是否處于“選中”狀態這個選中狀態對于QPushButton沒啥意義,對于QCheckBox復選框,就很有用了,
signals:void mySignal(const QString& text, const QString& text2);public slots:void handleMySignal(const QString& text);
void Widget::on_pushButton_clicked()
{//發送自定義信號emit mySignal("把標題設置為標題1", "");
}void Widget::on_pushButton_2_clicked()
{emit mySignal("把標題設置為標題2", "");
}
信號函數的參數個數,超過了槽函數的參數個數,此時,都是可以正常使用的信號函數的參數個數,少于槽函數的參數個數,此時代碼無法編譯通過
直觀的思考,應該是要求信號的參數個數和槽的參數個數,嚴格一致
此處為啥允許信號的參數比槽的參數多呢?? 一個槽函數,有可能會綁定多個信號
如果我們嚴格要求參數個數一致,就意味著信號綁定到槽的要求就變高了,
換而言之,當下這樣的規則,就允許信號和槽之間的綁定更靈活了,更多的信號可以綁定到這個槽函數上了,
個數不一致,槽函數就會按照參數順序,拿到信號的前N個參數
至少需要確保,槽函數的每個參數都是有值的
要求信號給槽的參數,可以有富裕,但是不能少
帶有參數的信號,要求信號的參數和槽的參數要一致
類型,個數要滿足要求(信號的參數個數要多于槽的參數個數).
widget.h
Qt中如果要讓某個類能夠使用信號槽(可以在類中定義信號和槽函數)
則必須要在類最開始的地方,寫下QOBJECT宏
這個宏能展開成很多額外的代碼
如果不加這個宏,這個類編譯的時候會報錯