GUI 程序除了要繪製控件,還要響應系統和用戶事件,例如重繪、繪製完成、點擊鼠標、敲擊鍵盤等。當事件發生時,UI 會產生相應的變化,讓用戶直觀地看到。
大部分編程(例如Win SDK、Web前端)中使用回調函數來響應事件,而 Qt 卻首創了信號和槽機制。所謂回調函數,就是程序員提早定義一個函數,當事件發生時就調用該函數。
信號和槽是Qt的核心,它讓兩個互不相干的對象鏈接起來,當一個對象的狀態改變時,能夠通知另外一個對象。
咱們先經過例子來演示一下信號和槽:前端
具體的代碼:程序員
#include "mainwindow.h" #include <QApplication> #include <QMainWindow> #include <QLabel> #include <QPushButton> #include <QLineEdit> int main(int argc, char *argv[]) { QApplication app(argc, argv); QMainWindow w; w.setWindowTitle("微浪遊戲"); w.resize(325, 120); QLineEdit lineEdit(&w); lineEdit.setGeometry(30, 20, 180, 36); lineEdit.setPlaceholderText("請輸入文本"); QPushButton btn("取消", &w); btn.setGeometry(220, 20, 70, 36); QLabel label(&w); label.setGeometry(30, 70, 250, 30); //鏈接clicke()信號和quit()槽 QObject::connect(&btn, SIGNAL(clicked()), &app, SLOT(quit())); //鏈接textChanged()信號和setText()槽 QObject::connect(&lineEdit, SIGNAL(textChanged(QString)), &label, SLOT(setText(QString))); w.show(); return app.exec(); }
在上面的demo中建立了三個控件:lineEdit,btn,label,他們都是QMainWindow w的子控件。運行的結果以下:編程
點擊「取消」按鈕,程序就關閉了,這是第26行代碼的做用;在文本輸入框中輸入一段文本,下面的 Label 會隨時顯示出來,這是第28行代碼的做用。app
這兩個對象都是經過信號和槽鏈接起來的,信號和槽用於兩個對象之間的通訊。信號和槽是QT的核心特徵,當一個特殊的事情發生時即可以發射一個信號,好比demo中的取消按鈕被點擊時,就會發射clicked()信號;而槽就是一個函數,它在信號發射後被調用來響應這個信號,Qt的部件類中已經定義了一些信號和槽,可是更經常使用的作法是子類化部件,而後添加自定義的信號和槽來實現想要的功能。函數
信號是隻有函數聲明、沒有函數體的成員函數。槽是擁有完整函數體的普通成員函數,你能夠在槽函數中實現各類功能,與普通函數相比並無區別,例如 quit() 的做用就是退出程序。ui
connect() 是 QObject 類的靜態成員函數;QObject 是 Qt 中全部類的基類,它就像「樹根」,從這裏派生出了全部其餘「樹枝」。
須要注意的是,信號不是事件。當用戶點擊「取消」按鈕時,Qt 會捕獲該點擊事件,進行預處理,而後發射 clicked() 信號; clicked() 和 quit() 關聯起來了,接下來就會調用 quit() 函數。
信號和槽機制歸根結底也是回調函數,只不過繞了個圈子。在這種機制下,程序員有兩次處理事件的機會,一是在捕獲事件後發射信號前進行預處理(事件不符合預期能夠不發射信號),二是在槽函數中進行主要處理。
再來看第27行。textChange() 信號會在文本改變時發出,setText() 槽用來設置 Label 的文本,QString 是要傳遞的數據的類型。當用戶輸入文本時,lineEdit 會發出 textChange() 信號,該信號將攜帶數據,數據類型爲 QString,數據內容爲輸入的文本;setText() 槽接收到信號後先解析信號攜帶的數據,獲取用戶輸入的文本,而後填充到 Label 中。 spa
信號和槽的關聯使用的是QObject類的connect()函數,connect() 是 QObject 類的靜態成員函數,它有多個原型:指針
connect(QObject *sender, char *signal, QObject *receiver, char *method); connect(QObject *sender, PointerToMemberFunction signal, QObject *receiver, PointerToMemberFunction method); connect(QObject *sender, PointerToMemberFunction signal, QObject *context, Functor functor); connect(QObject *sender, QMetaMethod &signal, QObject *receiver, QMetaMethod &method); connect(QObject *sender, PointerToMemberFunction signal, Functor functor);
簡單起見,上面省略了 connect() 的返回值和最後一個參數,以及某些參數前面的 const 修飾符,讀者能夠在 Qt 幫助手冊中查看完整的原型。code
connect() 函數返回值類型爲QMetaObject::Connection
,表示當前鏈接句柄。最後一個參數爲Qt::ConnectionType type = Qt::AutoConnection
,表示鏈接類型,通常默認便可。
觀察上面的原型,除了最後一個有3個參數,其餘都有4個參數,其中:
1) sender 爲信號發送者,receiver 爲信號接收者,它們都是對象指針。
2) 第1個原型中,signal 爲信號,method 爲槽函數,它們都是字符串,必須藉助 SIGNAL() 和 SLOT() 將函數形式轉換爲字符串形式。SIGNAL() 和 SLOT() 是宏,而非函數。上面的示例中就使用了該原型,它是經常使用的原型,初學者必需要掌握。
3) 第2個原型中,PointerToMemberFunction 爲指向成員函數的指針。你能夠將示例中的代碼作以下更改:對象
QObject::connect(&btn, &QPushButton::clicked, &app, &QApplication::quit); QObject::connect(&lineEdit, &QLineEdit::textChanged, &label, &QLabel::setText);
這是 Qt 5 新增的原型,能夠在編譯期間進行檢查,若是信號和槽不存在或者不匹配,則會報錯。而第1種原型是從 Qt 誕生以來一直支持的,不能在編譯期進行檢測,若是信號和槽有誤,只會在程序運行期間給出警告並返回 false,不容易發現問題,這是它的一個缺陷。因此在 Qt 5 中咱們鼓勵使用第2種原型。