轉自個人博客
信號與槽是我個人認為QT中最牛的機制之一,最近沒有其他的內容可寫,今天就來細細總結一下這個信號與槽機制。
1. 信號與槽機制概述
信號與槽機制可以理解為QT中的一種通信手段,在運行相關代碼前,分別聲明信號和槽,再利用connect()
方法將信號和對應的槽連接起來,之后再需要的地方使用emit
觸發信號,那么就可以讓槽響應。
其中,槽可以是特定的槽函數,也可以是其他各種普通函數;信號是一種特定對象,其結構類似函數,也可以像函數那樣帶有參數,將值通過參數傳遞槽函數,便于槽函數處理變量,如果使用參數,則信號和槽的參數要保持一致。
示例:
class MyClass : public QObject
{
protected:MyClass(QObject *parent = nullptr);~MyClass();void myFunc();signals:void dataArrived(string data);slots:void onDataArrived(string data);
}///MyClass::MyClass(QObject *parent = nullptr)
{// 在使用信號與槽函數之前連接信號和槽connect(this, &MyClass::dataArrived, this, &myClass::onDataArrived);
}
MyClass::~MyClass()
{// 在不用的時候釋放連接disconnect(this, &MyClass::dataArrived, this, &myClass::onDataArrived);
}void MyClass::onDataArrived(string data)
{// 處理data或其他操作
}void MyClass::myFunc()
{....// 在需要的地方觸發信號,執行onDataArrived槽函數,并傳遞數據emit dataArrived(data);
}
這個示例中,只在一個類中使用信號和槽傳遞數據,其效果與直接調用函數無異,但信號與槽的功能遠超如此。
可以使用信號與槽實現跨類通信、跨線程通信,保證線程不會阻塞,實現異步處理。一個槽可以同時與多個信號連接,便于代碼中應對不同情況,不用命名各種函數。而且信號與槽可以任意連接、解耦,也可以實現在不同的地方將一個信號與不同槽函數連接,輕松改寫結構,而不會影響代碼其他部分。
2. QT中信號與槽
2.1 基礎用法
我這里只提QT5中的用法,舊版本的用法幾乎不再使用。
2.1.1 信號signal
-
聲明的格式
需要在頭文件中使用
signal
關鍵字修飾單一對象,以指定信號對象,或者使用signals:
來批量修飾對象,以批量指定信號對象。另外,Q_SIGNAL
等同于signal
,Q_SIGNALS:
等同于signals:
。信號本體格式等同于函數,但不需要再在CPP文件中定義“函數體”,返回值為void。也可聲明參數,以傳遞數據,但參數格式與槽保持一致,至少參數數目一樣多,并且參數之間可以隱式轉換,好比函數的調用。
示例:
class MyClass : public QObject {// 修飾單一信號對象// Q_SIGNAL等同signalQ_SIGNAL void fileDeleted(); // 批量修飾信號對象 // signals等同Q_SIGNALS signals: // 可聲明參數void fileCreated(string filePath);void fileModifed(); }
-
使用
先使用connect()方法連接信號與槽,在需要的地方,使用
emit
觸發信號,如有參數,就需要保證格式的正確。emit fileDeleted(); Q_EMIT fileCreated(path);
2.1.2 槽slot
-
聲明的格式
可選的在頭文件中使用
slot
關鍵字修飾單一對象,以指定槽函數,或者使用slots:
來批量修飾對象,以批量指定槽函數。另外,Q_SLOT
等同于slot
,Q_SLOTS:
等同于slots:
。槽函數的本身就是函數,即使用函數格式,需要在CPP文件中定義函數體,任意定義返回值以應對其他需要調用此函數的地方。不同于信號必須使用
signal
關鍵字告訴編譯器這是信號對象,槽函數也可以直接使用普通函數作為連接對象。示例:
class MyClass : public QObject { // 普通函數可直接被當作槽函數用connect方法與信號連接 // 可以有返回值 bool onFileModifed();public: // 需要修飾訪問權限// Q_SLOT等同slotQ_SLOT void onFileDeleted();// 需要修飾訪問權限, slots等同Q_SLOTS public slots:// 可聲明參數void onFileCreated(string filePath); }
-
使用
作為一個函數,需要在CPP文件中定義函數體,在需要的時候使用connect()方法連接信號與槽,以便之后被信號調用。
bool MyClass::onFileModifed() {/// 函數體... }void MyClass::onFileCreated(string filePath) {/// 函數體... }
2.1.3 connect()方法
1. QT5中的connect()方法:
使用connect()方法將信號和槽建立連接,以便信號觸發時,執行指定槽函數。
connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction slot, Qt::ConnectionType type = Qt::AutoConnection)
- 參數一sender:信號發出的對象
- 參數二signal:信號所在類名和信號名稱,格式為**
&類名::信號名
** - 參數三receiver:信號接收的對象,槽函數所在的對象
- 參數四slot:槽函數所在類名和槽函數名稱,格式為**
&類名::槽函數名
** - 參數五(在2.2中詳細介紹)
示例:
QLabel *label = new QLabel;
QLineEdit *lineEdit = new QLineEdit;
QObject::connect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);
2. disconnect()方法:
使用disconnect()方法釋放信號和槽連接,便于之后此信號和不同槽函數建立連接,一般不使用此信號和槽時就要記得釋放連接。
disconnect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction slot)
格式與connect時保持一致,但disconnect()方法沒有第五參數,即便connect()時指定了第五參數,disconnect()時直接省略。
示例:
// 建立連接
QObject::connect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);
// 釋放此連接
QObject::disconnect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);
3. 使用Lambda表達式作為槽函數:
當然,前面又說到槽可以為其他函數,也就可以使用Lambda表達式來作為槽函數,使用Lambda表達式可以簡化槽函數的聲明,也可以通過Lambda表達式隨著信號調用參數格式與信號不一致的函數,或增加更多的使用條件。
關于C++ Lambda表達式可見我另一篇文章:C++ Lambda表達式
示例:
class MyClass : public QObject
{
protected:MyClass(QObject *parent = nullptr);void myFunc(string data, int size);signals:void dataArrived(string data);
}///
MyClass::MyClass(QObject *parent = nullptr)
{connect(this, &MyClass::dataArrived, this, [&](){myFunc(data, data.lenght());/// 或進行其他處理});
}
2.2 connect()第五參數與多線程
connnect()
方法通過使用第五個參數來設定槽函數的調用關系,此參數有五個固定的枚舉值:
- Qt::AutoConnection:自動連接,默認模式,根據信號和槽調用的線程自動進行選擇。如果信號和槽在同一線程中,則使用Qt::DirectConnection類型連接;如果在不同線程,則使用Qt::QueuedConnection連接。
- Qt::DirectConnection:直接連接。當信號和槽運行在同一線程中時使用,其效果類似函數調用,直接在信號發送的位置調用槽函數。
- Qt::QueuedConnection:隊列連接。當信號和槽運行在不同線程中時使用,信號發出時,不會立即調用槽,而是將其放入接收者所在線程的事件隊列中,等到接收者當前函數執行完,進入事件循環之后,槽函數才會被調用。
- Qt::BlockingQueuedConnection:阻塞隊列連接。當信號和槽運行在不同線程中時使用,類似于 QueuedConnection,但在調用槽時會阻塞信號的發送者,直到槽執行完成。所以如果信號和槽在同一線程時,就會造成死鎖,適用于需要確保槽完成執行再繼續其他操作的場景。
- Qt::UniqueConnection:唯一連接,可以與上述四個通過或字符
|
結合使用。此枚舉值與上述四個不同,其不指定槽函數的調用方式,而是確保該連接在相同的信號和槽組合中是唯一的。如果嘗試將同一信號和槽再次連接,連接將不會生效。這可以防止重復連接,避免一次觸發信號多次運行槽函數。
示例:
QObject::connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::QueuedConnection | Qt::UniqueConnection);
3. 于QML中使用信號與槽
QML中的信號與槽機制與C++中的使用方法類似。但QML中定義的任意屬性變量都有自己的信號,無需用戶自己定義,幾乎所有組件都是如此,一般使用時,更多情況會使用QML為這些對象設定的信號。當然也可以自定義信號和槽函數以及相互的連接。
3.1 監控QML默認定義的信號
QML中為組件默認屬性或自定義屬性都默認設置了信號,定義對應槽函數就可以在信號觸發后調用。槽函數的格式為**on+信號名(首字母大寫)
**,可由此自定義觸發信號后的執行代碼。
1. 組件屬性
大多數QML組件都有內置信號,這些信號可以直接連接到槽函數來響應特定事件。
一般的默認屬性都有自己的指定觸發方式,比如Button組件通過點擊觸發clicked()信號,當然也可以直接調用clicked()手動觸發信號。
示例:
// Button組件的clicked信號在用戶點擊按鈕時觸發
// 通過onClicked自定義代碼,在信號觸發時輸出指定內容
Button { onClicked: { console.log("Button was clicked!"); }
}
2. 自定義屬性變量
QML中的自定義屬性變量也會自動創建變化信號,當屬性值發生變化時會觸發相應的信號。
對于QML中使用 property
聲明的自定義屬性,首字母必須小寫,其發生改變時觸發信號的槽函數格式為 on+屬性名(首字母大寫)+Changed
。
示例:
property int num: 0 // 自動創建的信號,當num發生變化時觸發
onNumChanged: { console.log("num changed to:", counter);
}
3.2 自定義信號與槽
1. 自定義信號signal
QML中也可以直接使用 signal
關鍵字定義專門的信號屬性,可以帶有參數,格式參考下方示例,同樣首字母小寫。
槽函數格式即為**on+信號名(首字母大寫)
**。
觸發信號直接使用信號名調用信號即可,觸發信號后就直接執行槽函數。
注意: signal
關鍵字定義信號只能在QML文件的根組件使用
示例:
ColumeLayout { // 根組件signal mySignal(string data) //信號觸發時調用,同槽函數onMySignal: { console.log(data) }Button: { text: "Emit mySignal" onClicked: {// 觸發信號mySignal("Hello World!"); }}
}
2. Connections{}
Connections
組件用于處理信號的連接。重要的是它可以在不同作用域之間連接信號和槽,適合在運行時動態處理信號,特別是在定義和使用信號的組件不在同一上下文時。
格式上,使用target
屬性指定信號發出對象,然后就可以監聽發出對象內部的信號,信號對應的槽函數必須用function
修飾。
注意:
- 因為是跨作用域連接信號,對方根組件的信號自然是能監聽,但子組件的信號就監聽不了了。就好比可以調用根組件的屬性變量,調用不了子組件的屬性變量
- 這個方法同樣可以運用于C++注冊給QML的類,監聽C++中暴露的方法。
示例1:
// MyButton.qml
Button {property string myText: ''signal doubleClicked()onDoubleClicked: {console.log(myText + "double clicked")}
}// MyItem.qml
ColumeLayout { // 根組件MyButton {id: myButton}Connections{target: myButton // 信號發出對象// 注意變量的信號名格式,注意function修飾function onTextChanged(){myFunc() // 執行本作用域的函數}function onDoubleClicked(){// 在本作用域再定義一個此信號的槽函數}}function myFunc(){// ...}
}
示例2:
Connections{target: qwidget // C++注冊給QML的名稱// 監聽C++中暴露給QML的變量的變化function onWidthChanged(){// ...}function onHeightChanged(){// ...}
}