每日激勵:“不設限和自我肯定的心態:I can do all things。 — Stephen Curry”
緒論?:
本章是Qt中的第三章,也是我們理解Qt中必備的點 信號槽,它本質由信號和槽兩個來實現,其中將細致的講述如何自定義信號、槽,以及通過connect函數進行將信號和槽連接起來的實現用戶通過控件和程序進行交互的操作。
————————
早關注不迷路,話不多說安全帶系好,發車啦(建議電腦觀看)。
信號和槽概述
首先了解信號是什么,它由三個部分組成:
- 信號源:誰發的信號
- 信號的類型:那種類別的信號
- 信號的處理方式:注冊信號處理函數,在信號被觸發的時候會自動調用執行。
同樣的Qt中談到信號也涉及到三點:
- 信號源:由那個控件發出的信號
- 信號的類型:用戶進行不同的操作,就可能觸發不同的信號
- 點擊按鈕
- 輸入框匯總移動光標…
GUI程序就是要讓用戶進行操作,就是要和用戶進行交互的,這個過程就需要關注,用戶當前的操作具體是什么樣的操作,也就是通過用戶的操作觸發的信號(信號)從而知道用戶做了什么,根據這個信號進行對應的處理(槽)
- 槽(slot)–> 函數
- Qt 中可以使用 connect 這樣的函數,把一個信號和一個槽關聯起來
- 后續只要信號觸發了,Qt就會自動的執行槽函數(本質就是 回調函數(函數適配器、比較器))
注意:
- 其中一定是 先把信號的處理方式準備好,再觸發信號~
- Qt 中,一定是先關聯 信號 和 槽,然后再觸發信號(順序不能亂)
connect
connect是QObject 提供的靜態成員函數
并且Qt中提供的這些類,本身是存在一定的繼承關系的(如下圖)
其中:QObject 就是其他 Qt 內置類的“祖宗”(Qt4才引入繼承機制…)、所以因為connect是QObject中的函數,所有許多控件都能繼承使用
connect的具體使用
connect (const QObject *sender,const char * signal ,const QObject * receiver ,const char * method ,Qt::ConnectionType type = Qt::AutoConnection )1. sender 前信號來自那個控件
2. signal 信號的類型
3. receiver 信號如何處理的類
4. method 這個對象該如何處理(要處理信號提供的成員函數!)
5. type ?于指定關聯?式,默認的關聯?式為 Qt::AutoConnection 一般不用設置
簡單信號槽實例
- 創建按鈕 QPushButton 變量 button并 構造 傳遞 this
- 設置文本 為關閉
- 設置位置 200 200
- 使用connect設置四個參數
- 傳遞 button 變量,設置信號源
- 信號的類型,根據按鈕對象類域獲取 clicked 信號函數
- 其中注意的是:上述兩個參數必須對應匹配
- 也就是說 button 的類型 如果是 QPushButton*
- 那么 第二個參數的信號 必須是 QPushButton 內置的信號(父類的信號&QPushButton::clicked)
- 處理信號的類對象:此處填寫
this
,代表當前類 - 信號處理的函數,此處調用
Widget::close
(widget繼承的類中的函數)作用:關閉當前窗口/控件
其中的一些問題:
Qt 里面到底提供了許多內置的信號 和 槽 讓我們可以直接使用(如:QPushButton的clicked信號、QWidget 的 close 槽)
通過文檔查看:
其中 假如在某個文檔中沒有找到你想要找到方法、槽函數或者信號時,不妨看看他的父類
其中寫在:
- 其中 abstract 就是抽象類
- Qt 中會提供很多種按鈕,其中QAbstractButton中就會存在許多 “共性” 的內容
- 這樣就能通過繼承的方式讓,多個類中都使用到,并且不需要自行再定義:
其中clicked就在 QAbstractButton 內部:
其中clicked內部會寫也是最要關注的點:什么時候觸發信號?
再次回顧connect函數:
- 其中第二個參數和第四個參數時,發現我們傳遞的是函數指針,但函數顯示需要的參數類型是char *,這不是有問題嗎?
其中C++中是并不允許不同類型間的傳遞的,那么是如何實現的呢?
- 這個函數聲明是以前舊版本的 Qt 的connect 函數聲明
- 老版本中的寫法:需要加上兩個宏 SIGNAL 和 SLOT,將函數指針轉換為 const char*
- 從 Qt 5 中進行了修改,給connect提供了重載,給第二個參數和第四個參數 改成了 泛型參數,允許咱們傳入任意類型的函數指針
自定義槽
所謂的槽 slot 本質就是一個普通的成員函數,所以自定義槽本質就是新增成員函數
其中使用 conncet 連接的時候,將槽函數的參數填成自己的成員函數
代碼實現信號槽
- 聲明函數自己的槽函數:handle
- handle槽函數:
- 具體的實現:通過當前Widget類的this指針調用setWindowsTitle:設置窗口標題
- 通過connect進行PushButton按鈕和handle槽函數的連接
具體信號槽源碼:
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 handle();
private:Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QDebug>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//創建一個PushButton對象QPushButton* button = new QPushButton(this);button->setText("關閉窗口");button->move(200,200);//添加信號槽connect(button,&QPushButton::clicked,this,&Widget::handle);}Widget::~Widget()
{delete ui;
}void Widget::handle(){this->setWindowTitle("按鈕已按下");
}
點擊前:
點擊后:
了解:在以前版本的Qt中,槽函數必須放到 public / private / protected slots
這樣的域內限定符中(現在版本并不需要這樣寫)
- 此處的slots 是 Qt 自己的擴展關鍵字,它的實現基于 Qt 中廣泛使用的元編程技術(基于代碼,生成代碼)
- 元編程:qmake 構建Qt項目的時候,就會專門的掃描器,掃描代碼中的關鍵字,基于這些關鍵字生成相關的代碼
使用ui文件搭建信號槽
在拖拽式處,可以右鍵組件,找到構建槽這個選項
-
彈出的窗口就會列出 QPushButton 給我們提供的所有信號,都是能使用的
-
雙擊后就能使用了,也會自動生成一個函數
并且函數的聲明也會自動生成
-
只需要在函數中編寫即可!
- 方法同上
- 方法同上
其中我們在代碼中發現并沒有connect,所以Qt中除了connect來連接信號槽之外:
還能通過函數名的方式來自動連接(具體如下圖)
- 當名稱出錯后,就無法連接成功,并且還會報錯(Qt 中調用connectSlotsByName的時候,就會觸發上述的自動連接信號槽的規則,該函數就是在 ui_widget.h 中的)
總結:
- 通過界面化的創建控件,還是推薦使用名稱的方式快速連接信號槽
- 但如果是通過代碼的方式來創建控件的話,還是得手動的 connect(因為自己代碼中本身沒有 connectSlotsByName)
-
自定義槽函數,非常關鍵,大部分情況下都是自定義槽函數的
-
槽函數,就是用戶觸發某個操作之后,要進行的業務邏輯
-
而自定義信號是比較少見的,實際開發中會很少需要自定義信號的
-
因為信號就是用戶的某個操作,而對于GUI中,用戶能進行的操作是很少的,可以窮舉的
-
Qt內置的信號,基本就可以應付大部分開發場景的
自定義信號
雖然創建的Widget沒有定義任何信號,但由于繼承了 QWidget 和 QObject,而這兩個類中就提供了一些信號可以直接使用了
- 而Qt中的信號,本質上也是一個函數
- Qt 5 以及更高版本中,槽函數和普通函數之間沒啥區別
- 信號則是非常特殊的,程序員需要寫出函數聲明,告訴Qt這是一個信號
- 這個函數的定義,是Qt編譯過程中,自動生成的(自動生成的過程,程序員無法干涉)
- 信號在Qt中的特殊機制,他需要配合Qt框架做很多操作
自定義信號
信號函數創建的要求
- 這個函數的返回值必須是 void
- 有沒有參數都可以
使用Qt 擴展的關鍵字:signals
進行創建信號
在qmake的時候分析 / 生成工具,將下面的函數聲明認為是信號,并且給這些函數自動生成函數定義
就能將自定義的信號連接槽
建立連接,不代表信號發出來了,而此處只是簡單的將信號和槽連接了,還需要觸發信號
如何觸發自定義信號
Qt 內置的信號,都不需要手動通過代碼觸發,用戶在GUI進行某些操作,就會觸發對應的信號(內置到Qt框架中了)
關鍵字:emit
發射自定義信號
emit 自定義信號
如:emit mySignal
但上述情況會在構造函數中立馬觸發信號,從而立馬執行槽函數
對于發射信號的情況,并不是非要在構造函數中,而是應該根據具體情況,寫在所需要的地方
具體見下面代碼:
在最新版本中,即使不寫emit,信號也能發出去!(mySignal構造中都寫了)
帶參數的信號和槽的注意點
信號和槽 也可以帶參數
- 當信號帶有參數的時候,槽的參數必須和信號的參數一致!
- 此時發射信號的時候,就可以給信號函數傳遞實參,與之對應的這個參數就會被傳遞到對應的槽函數中,也就起到了信號給槽傳參的效果
- 其中:參數必須一致,主要參數必須一致
然后對于實現代碼來說,也需要進行添加參數
對于傳參來說可以起到復用代碼的效果,根據不同的場景傳遞不同的參數
通過發送不同的信號達到不同的效果
Qt 中很多內置的信號,也是帶有信號的(這些參數不是咋們自己傳遞的)
如下圖
- 其中可以允許個數不一致,但要求信號的參數個數必須比槽的參數個數要多
難道不應該嚴格要求一致嗎?
- 因為:信號槽之間的綁定不一定是1 對 1的,一個槽函數,有可能綁定多個信號,如果嚴格要求,也就意味著信號綁定槽的要求變高了
- 換而言之,當下這樣的規則,就允許信號和槽之間的綁定更靈活了,更多的信號可以綁定到這個槽函數上,個數不一致槽函數就會按照參數順序,拿到信號的前N個參數(只能多不能少)
- 并且同樣要保證信號的參數類型和槽的參數類型一致
Q_OBject宏:
Qt 中如果要讓某個類能使用 信號槽(可以在類中定義信號和槽函數)
則必須要在類最開始的地方,寫下 Q_OBject 宏(這個宏給展開成許多額外的代碼)
總結:
所謂的信號槽,終究是要解決的問題,就是響應用戶的操作,信號槽,其實在GUI開發的各種框架中,是一個比較特色的存在
connect機制是為了:
- 解耦合,把觸發用戶操作的控件 和 處理對應用戶的操作邏輯解耦合
- 多對多的效果:一個信號,可以connect到多個槽函數,一個槽函數也可以被多個信號connect
- 而其中的connect本就就像一個關聯表,將多對多的信號和槽的情況連接起來
綜上,Qt 引入信號槽記住,最本質的目的,就是為了能夠讓信號和槽之間按照多對多的方式來進行關聯,其他GUI框架往往不具備這樣特性
而在GUI開發的過程中,“多對多”這件事,其實是個“偽需求”,絕大多數 一對一就夠了
disconnect 信號與槽的斷開
-
使用 disconnect 來斷開信號槽的連接
-
connect和disconne使用的方式是類似的,主動斷開往往是把信號重新綁定到另一個槽函數
具體操作如下:
- 首先第一次點擊pushbutton只會觸發一個槽函數
- 當再次把一個信號綁定一個槽函數后,此時一個信號就會觸發兩個槽函數(也就是驗證了支持多對多!,如下圖)
實現一個斷開連接,然后再次連接新的信號和槽
- 當點擊pushButton2按鈕后就會斷開 第一個 pushButton的信號槽
- 然后連接到新的槽函數handleClick2
- 其中若不進行斷開,則當有一個信號后會觸發兩個槽函數
使用lambda表達式定義槽函數
lambda本質就是一個匿名函數,主要應用于“回調函數”場景(一次性使用)
其中若要使用內部的成員變量/成員函數的話
- 需要給lambda表達式進行 “變量捕獲”
- 假如要使用button,那么在括號中添加button變量
- 假若要捕獲多個變量時,可以使用
=
的形式(值)捕獲(拷貝得到)所有變量
源碼:
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QDebug>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//創建一個PushButton對象QPushButton* button = new QPushButton(this);button->setText("關閉窗口");//添加信號槽connect(this,&Widget::MySignal,this,[=]{qDebug() << "lambda表達式" ;button->move(200,200);});
}
注:
- 如果槽函數比較簡單或者一次使用,那么就使用lambda表達式快速編程
- 其中要確定捕獲到 lambda 內部的變量時有意義的(仍然存儲沒有被銷毀),因為回調函數執行時機是不確定的,所以需要注意捕獲對象生命周期
- 還能進行引用方式捕獲那么就將
=
替代為&
(Qt中很少這樣寫,一般捕獲的就是控件的指針,因為按引用還得跟關注變量的生命周期,變量可能會隨棧直接釋放) - lambda 語法是 C++11 中引用的,對于Qt5及以上版本是默認按 C++11來編譯的,而如果使用Qt4或者更老版本,就需要手動在 .pro 文件中添加 C++11的編譯選項
CONFIG += c++11
總結回顧
- 信號槽是什么:設計三個要素
- 信號源
- 信號的類型
- 信號的處理方式
- 信號槽的使用
- connect
- 如何查閱文檔
- 一個控件內置了那些信號,何時觸發
- 控件內置了那些槽,作用是什么
- 一些需要的信號槽,可能在父類中存在
- 自定義槽函數
- 本質上就是自定義一個普通的成員函數
- 還可以讓 Qt Creator 自動生成(雖然沒顯示寫 connect,但還是可以通過函數名特定規則來完成自動連接)
- 自定義信號
- 本質也是一個成員函數(函數的定義是Qt自己生成的,咱們只需要寫函數聲明)
signals
自定義關鍵字中emit
來完成信號的發射
- 信號和槽還可以帶有參數
- 發射信號的時候,把參數傳遞給對應的槽
- 信號的參數和槽的參數要一致(至少類型匹配,信號的個數要等于或多余槽的參數)
- 信號槽存在的意義
- 解耦合(高內聚低耦合(低耦合:代碼間相互不會影響、高內聚:相關代碼都寫在一起))
- 多對多的效果(類似 mysql 中的多對多),一個信號多個槽、一個槽多個信號
- discount、lambda表達式簡化槽函數的定義
本章完。預知后事如何,暫聽下回分解。
如果有任何問題歡迎討論哈!
如果覺得這篇文章對你有所幫助的話點點贊吧!
持續更新大量C++細致內容,早關注不迷路。
?