用 VS Code 搞 Qt6:信號、槽,以及QObject

Qt 里面的信號(Signal)和槽(Slot)雖然看著像事件 , 但它實際上是用來在兩個對象之間進行通信的 。既然是通信,就會有發送者和接收者 。
1、信號是發送者,觸發時通過特有的關鍵字“emit”來發出信號 。
2、槽是信號的接收者,它實則是一個方法(函數 )成員,當收到信號后會被調用 。
為了讓C++類能夠使用信號和槽機制,必須從 QObject 類派生 。QObject 類是 Qt 對象的公共基類 。它的第一個作用是讓 Qt 對象之形成一株“對象樹” 。當某個 Qt 對象發生析構時 , 它的子級對象都會發生析構 。比如 , 窗口中包含兩個按鈕,當窗口類析構時,里面的兩個按鈕也會跟著發生析構 。所以 , 在 Qt 的窗口應用程序里面,一般不用手動去 delete 指針類型的對象 。位于對象樹上的各個對象會自動清理 。
QObject 類的另一個關鍵作用是實現信號和槽的功能 。
1、從 QObject 類派生的類,在類內部要使用 Q_OBJECT 宏 。
2、跟在 signals 關鍵字后面的函數被視為信號 。這個關鍵字實際上是 Q_SIGNALS 宏,是 Qt 項目專用的,并不是 C++ 的標準關鍵字 。
3、跟在 slots 或 public slots 后面的成員函數(方法)被認為是槽,當接收到信號時會自動調用 。
信號和槽之間相互不認識,需要找個“媒婆”讓它們走到一起 。因此,在發出信號前要調用 QObject :: connect 方法在信號與槽之間建立連接 。
老周不喜歡說得太復雜,上面的介紹應該算比較簡潔了,接下來咱們來個示例,就好理解了 。
這里老周定義了兩個類:DemoObject 類里面包含了一個 QStack<int> 對象,是個棧集合 , 這個應該都懂,后進先出 。兩個公共方法,AddOne 用來向 Stack 對象壓入元素,TakeOne 方法從 Stack 對象中彈出一個元素 。不過 , 彈出的元素不是經 TakeOne 方法返回,而是發出 GetItem 信號,用這個信號將彈出的元素發送給接收者(槽在 TestRecver 類中) 。第二個類是 TestRecver,對,上面 DemoObject 類發出的 GetItem 信號可以在 TestRecver 類中接收,槽函數是 setItem 。
#include <iostream>#include <qobject.h>#include <qstack.h>class DemoObject : public QObject{// 這個是宏Q_OBJECTprivate:QStack<int> _inner;public:void AddOne(int val){_inner.push(val);}void TakeOne(){if(_inner.empty()){return;}int x = _inner.pop();// 發出信號emit GetItem(x);}// 信號signals:void GetItem(int n);};class TestRecver : public QObject{// 記得用這個宏Q_OBJECT// 槽public slots:void setItem(int n){std::cout << "取出項:" << n << std::endl;}};在 main 函數中,先創建 DemoObject 實例,用 AddOne 方法壓入三個元素 。然后創建 TestRecver 實例,用 connect 方法建立信號和槽的連接 。
int main(int argc, char **argv){DemoObject a;a.AddOne(50);a.AddOne(74);a.AddOne(80);TestRecver r;// 信號與槽連接QObject::connect(&a, &DemoObject::GetItem, &r, &TestRecver::setItem);// 下面這三行會發送GetItem信號a.TakeOne();a.TakeOne();a.TakeOne();return 0;}下面是 CMakeLists.txt 文件:
【用 VS Code 搞 Qt6:信號、槽,以及QObject】cmake_minimum_required(VERSION 3.0.0)project(myapp LANGUAGES CXX)find_package(Qt6 REQUIRED COMPONENTS Core)set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_AUTOMOC ON)add_executable(myapp main.cpp)target_link_libraries(myapp PRIVATE Qt6::Core)注意 , 這里一定要把 CMAKE_AUTOMOC 選項設置為 ON , 1 , 或者 YES 。因為我們用到了 Q_OBJECT 宏,它需要 MOC 生成一些特定C++代碼和元數據 。這個示例只用到 QtCore 模塊的類,所以 find_package 和 target_link_libraries 中只要引入這個就行 。
當你興奮異常地編譯和運行本程序時,會發生錯誤:

用 VS Code 搞 Qt6:信號、槽,以及QObject

文章插圖
這個錯誤是因為 MOC 生成的代碼最終要用回到我們的程序中的,但代碼文件沒有包含這些代碼 。所以你看上面已經提示你了,解決方法是包含 main.moc 。這個文件名和你定義 DemoObject 類的代碼文件名相同 。我剛剛的代碼文件是 main.cpp,所以它生成的代碼文件就是 main.moc 。
不過 , #include 指令一定要寫在 DemoObject 和 TestRecver 類的定義之后,這樣才能正確放入生成的代碼 。# include 放在文件頭部仍然會報錯的 , 此時,DemoObject 和 TestRecver 類還沒有定義,無法將 main.moc 中的源代碼插入到 main.cpp 中(會找不到類) 。
#include <iostream>#include <qobject.h>#include <qstack.h>class DemoObject : public QObject{// 這個是宏Q_OBJECT……};class TestRecver : public QObject{// 記得用這個宏Q_OBJECT……};#include "main.moc"int main(int argc, char **argv){……return 0;}

推薦閱讀