一、前言
前面說了Qt最基本的實例創建、控件以及工具集的介紹,相當於對於Qt有了一個初次的認識,這次我們開始認識Qt信號通信的重點之一——信號槽。
二、信號槽
信號槽是 Qt 框架引以為豪的機制之一。熟練使用和理解信號槽,能夠設計出解耦的非常漂亮的程序,有利於增強我們的技術設計能力。
所謂信號槽,實際就是觀察者模式。當某個事件發生之后,比如,按鈕檢測到自己被點擊了一下,它就會發出一個信號(signal)。這種發出是沒有目的的,類似廣播。如果有對象對這個信號感興趣,它就會使用連接(connect)函數,意思是,用自己的一個函數(成為槽(slot))來處理這個信號。也就是說,當信號發出時,被連接的槽函數會自動被回調。這就類似觀察者模式:當發生了感興趣的事件,某一個操作就會被自動觸發。(這里提一句,Qt 的信號槽使用了額外的處理來實現,並不是 GoF 經典的觀察者模式的實現方式。)
1 #include "slottest.h" 2 #include <QtWidgets/QApplication> 3 #include <QPushButton> 4 int main(int argc, char *argv[]) 5 { 6 QApplication a(argc, argv); 7 8 QPushButton button("Quit"); 9 QObject::connect(&button,&QPushButton::clicked,&QApplication::quit); 10 button.show(); 11 //slottest w; 12 //w.show(); 13 return a.exec(); 14 }
我們開始一步一步的分析這短短的幾行代碼。第6行聲明一個QApplication類的對象,用於程序的啟動和調用;第8行聲明了一個按鈕控件QPushButton類的對象,用於產生一個按鈕,並傳遞一個字符串“Quit”命名控件顯示的名稱;第9行用到了QObject中的connect函數,我們先來看這個函數的定義。
1 /*Creates a connection of the given type from the signal in the sender object to the method in the receiver object. Returns a handle to the connection that can be used to disconnect it later. 2 3 You must use the SIGNAL() and SLOT() macros when specifying the signal and the method*/ 4 QMetaObject::Connection connect(const QObject *, const char *, 5 const QObject *, const char *, 6 Qt::ConnectionType); 7 8 QMetaObject::Connection connect(const QObject *, const QMetaMethod &, 9 const QObject *, const QMetaMethod &, 10 Qt::ConnectionType); 11 12 QMetaObject::Connection connect(const QObject *, const char *, 13 const char *, 14 Qt::ConnectionType) const; 15 16 QMetaObject::Connection connect(const QObject *, PointerToMemberFunction, 17 const QObject *, PointerToMemberFunction, 18 Qt::ConnectionType) 19 20 QMetaObject::Connection connect(const QObject *, PointerToMemberFunction, 21 Functor);
從上面可以看到,connect函數一共有五個重載的版本,其中各個參數比較之后sender和receiver沒有什么區別,都是QObject指針,主要是signal和slot形式的區別。再回到我們的調用回來,我們采用的是第五個重載,當我們的Button發出了clicked信號時,會調用QApplication的quit函數,從而使程序退出。
對於信號和槽的要求是參數保持一致,這里所說的一致是參數類型的一致。如果不一致,可以是槽函數的參數可以比信號的少,而且參數的順序必須保持一致。
將上述代碼稍加修改,使用lambda表達式后我們再來看一下效果。
1 QObject::connect(&button, &QPushButton::clicked,/*&QApplication::quit*/[](){qDebug()<< "quit"; });
三、QWidget、QDialog及QMainWindow的區別
QWidget類是所有用戶界面對象的基類。 窗口部件是用戶界面的一個基本單元:它從窗口系統接收鼠標、鍵盤和其它事件,並且在屏幕上繪制自己。每一個窗口部件都是矩形的,並且它們按Z軸順序排列。一個窗口部件可以被它的父窗口部件或者它前面的窗口部件蓋住一部分。
QMainWindow 類提供一個有菜單條、錨接窗口(例如工具條)和一個狀態條的主應用程序窗口。主窗口通常用在提供一個大的中央窗口部件(例如文本編輯或者繪制畫布)以及周圍 菜單、工具條和一個狀態條。QMainWindow常常被繼承,因為這使得封裝中央部件、菜單和工具條以及窗口狀態條變得更容易,當用戶點擊菜單項或者工具條按鈕時,槽會被調用。
QDialog類是對話框窗口的基類。對話框窗口是主要用於短期任務以及和用戶進行簡要通訊的頂級窗口。QDialog可以是模態對話框也可以是非模態對話框。QDialog支持擴展性並且可以提供返回值。它們可以有默認按鈕。QDialog也可以有一個QSizeGrip在它的右下角,使用setSizeGripEnabled()。
QDialog 是最普通的頂級窗口。一個不會被嵌入到父窗口部件的窗口部件叫做頂級窗口部件。通常情況下,頂級窗口部件是有框架和標題欄的窗口(盡管使用了一定的窗口部件標記,創建頂級窗口部件時也可能沒有這些裝飾。)在Qt中,QMainWindow和不同的QDialog的子類是最普通的頂級窗口。
如果是頂級對話框,那就基於QDialog創建,如果是主窗體,那就基於QMainWindow,如果不確定,或者有可能作為頂級窗體,或有可能嵌入到其他窗體中,則基於QWidget創建。
當然了,實際中,你還可以基於任何其他部件類來派生。看實際需求了,比如QFrame、QStackedWidget等等。
四、自定義信號槽
通過系統提供的connect函數可以讓我們連接系統信號和槽,但是你應該會想到我們桐鄉可以自己定制自己的信號槽,這也是Qt架構的設計思路,用於我們設計解耦的程序。信號槽不是GUI模塊提供的,而是Qt核心特性之一,所以我們同樣可以在普通的控制台程序中使用信號槽。
注意:我之所以將頭文件和源文件分開來寫,是因為moc在我的編譯器中必須要將其分開,不然就會報錯。

1 #include<QObject> 2 3 /////////////////newspaper.h 4 class Newsspaper :public QObject{ 5 6 Q_OBJECT 7 public: 8 Newsspaper(const QString& name) :m_name(name) 9 { 10 11 } 12 void send(); 13 signals: 14 void newPaper(const QString& name); 15 private: 16 QString m_name; 17 };

1 #include "newspaper.h" 2 3 void Newsspaper::send() 4 { 5 emit newPaper(m_name); 6 }

1 #include<QObject> 2 #include<QDebug> 3 4 class Reader :public QObject 5 { 6 Q_OBJECT 7 public: 8 Reader(); 9 void receiveNewspaper(const QString& name); 10 };

1 #include "reader.h" 2 3 Reader::Reader(){} 4 5 void Reader::receiveNewspaper(const QString& name) 6 { 7 qDebug() << "Receive Newspaper:" << name; 8 }

1 #include "slottest.h" 2 #include <QtWidgets/QApplication> 3 #include <QPushButton> 4 #include <QObject> 5 #include <QCoreapplication> 6 #include "newspaper.h" 7 #include "reader.h" 8 9 int main(int argc, char *argv[]) 10 { 11 QApplication a(argc, argv); 12 Newsspaper newspaper("NewsPaper A"); 13 Reader reader; 14 QObject::connect(&newspaper,&Newsspaper::newPaper,&reader,&Reader::receiveNewspaper); 15 newspaper.send(); 16 17 return a.exec(); 18 19 }
運行結果:
Receive Newspaper: "NewsPaper A"
根據代碼我們對幾個重點進行解釋:1、Q_OBJECT是Qt的一個宏,能夠使程序具有信號槽機制,由moc進行處理,會生成以moc為開頭的文件,moc是一種預處理器;2、emit是Qt的另一個宏,用於表示發射信號,使槽函數能夠接受信號;3、使用connect函數進行信號槽的連接,槽函數在獲取到消息體和信號參數后做出相應,這里就是響應打印消息。4、信號槽的機制類似於廣播,可以參照廣播模式來理解代碼。5、使用 signals 標記信號函數,信號是一個函數聲明,返回 void,不需要實現函數代碼;6、槽函數是普通的成員函數,作為成員函數,會受到 public、private、protected 的影響。
感謝你的耐心閱讀,如果有哪些地方錯誤,請提出來,謝謝!