一 信號和槽
GUI 程序除了要繪制控件,還要響應系統和用戶事件,例如重繪、繪制完成、點擊鼠標、敲擊鍵盤等。當事件發(fā)生時,UI 會產生相應的變化,讓用戶直觀地看到。
大部分編程(例如Win SDK、Web前端)中使用回調函數來響應事件,而 Qt 卻獨創(chuàng)了信號和槽機制。所謂回調函數,就是程序員提前定義一個函數,當事件發(fā)生時就調用該函數。
信號和槽是Qt的核心,它讓兩個互不相干的對象連接起來,當一個對象的狀態(tài)改變時,可以通知另一個對象。
我們先通過例子來演示一下信號和槽:
具體的代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #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中創(chuàng)建了三個控件:lineEdit,btn,label,他們都是QMainWindow w的子控件。運行的結果如下:
點擊“取消”按鈕,程序就關閉了,這是第26行代碼的作用;在文本輸入框中輸入一段文本,下面的 Label 會隨時顯示出來,這是第28行代碼的作用。
這兩個對象都是通過信號和槽連接起來的,信號和槽用于兩個對象之間的通信。信號和槽是QT的核心特征,當一個特殊的事情發(fā)生時便可以發(fā)射一個信號,比如demo中的取消按鈕被點擊時,就會發(fā)射clicked()信號;而槽就是一個函數,它在信號發(fā)射后被調用來響應這個信號,Qt的部件類中已經定義了一些信號和槽,但是更常用的做法是子類化部件,然后添加自定義的信號和槽來實現想要的功能。
信號是只有函數聲明、沒有函數體的成員函數。槽是擁有完整函數體的普通成員函數,你可以在槽函數中實現各種功能,與普通函數相比并沒有區(qū)別,例如 quit() 的作用就是退出程序。
connect() 是 QObject 類的靜態(tài)成員函數;QObject 是 Qt 中所有類的基類,它就像“樹根”,從這里派生出了所有其他“樹枝”。
需要注意的是,信號不是事件。當用戶點擊“取消”按鈕時,Qt 會捕獲該點擊事件,進行預處理,然后發(fā)射 clicked() 信號; clicked() 和 quit() 關聯起來了,接下來就會調用 quit() 函數。
信號和槽機制歸根結底也是回調函數,只不過繞了個圈子。在這種機制下,程序員有兩次處理事件的機會,一是在捕獲事件后發(fā)射信號前進行預處理(事件不符合預期可以不發(fā)射信號),二是在槽函數中進行主要處理。
再來看第27行。textChange() 信號會在文本改變時發(fā)出,setText() 槽用來設置 Label 的文本,QString 是要傳遞的數據的類型。當用戶輸入文本時,lineEdit 會發(fā)出 textChange() 信號,該信號將攜帶數據,數據類型為 QString,數據內容為輸入的文本;setText() 槽接收到信號后先解析信號攜帶的數據,獲取用戶輸入的文本,然后填充到 Label 中。
二 信號和槽的關聯
信號和槽的關聯使用的是QObject類的connect()函數,connect() 是 QObject 類的靜態(tài)成員函數,它有多個原型:
1 2 3 4 5 6 7 8 9 | 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 幫助手冊中查看完整的原型。
connect() 函數返回值類型為QMetaObject::Connection
,表示當前連接句柄。最后一個參數為Qt::ConnectionType type = Qt::AutoConnection
,表示連接類型,一般默認即可。
觀察上面的原型,除了最后一個有3個參數,其他都有4個參數,其中:
1) sender 為信號發(fā)送者,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,不容易發(fā)現問題,這是它的一個缺陷。所以在 Qt 5 中我們鼓勵使用第2種原型。
感謝您的閱讀,若有不足之處,歡迎指教,共同學習、共同進步。 博主網址:http://www.cnblogs.com/majianchao/ 如您喜歡,麻煩推薦一下;如您有新想法,歡迎提出,郵箱:1145356699@qq.com。 本博客為博主原創(chuàng),歡迎轉載,但必須注明博客來源。 更多關于游戲開發(fā)的內容也可關注微信公眾號:微浪游戲