信號槽是 Qt 框架引以為豪的機制之一。
所謂信號槽,實際就是觀察者模式。當某個事件發生之后,比如,按鈕檢測到自己被點擊了一下,它就會發出一個信號(signal)。這種觸發是沒有目的的,類似廣播。如果有對象對這個信號感興趣,它就會使用連接(connect)函數,意思是,將想要處理的信號和自己的一個函數(稱為槽(slot))綁定來處理這個信號。也就是說,當信號發出時,被連接的槽函數會自動被回調。這就類似觀察者模式:當發生了感興趣的事件,某一個操作就會被自動觸發。(這里提一句,Qt 的信號槽使用了額外的處理來實現,並不是 GoF 經典的觀察者模式的實現方式。)
信號和槽是Qt特有的信息傳輸機制,是Qt設計程序的重要基礎,它可以讓互不干擾的對象建立一種聯系。
槽的本質是類的成員函數,其參數可以是任意類型的。和普通C++成員函數幾乎沒有區別,它可以是虛函數;也可以被重載;可以是公有的、保護的、私有的、也可以被其他C++成員函數調用。唯一區別的是:槽可以與信號連接在一起,每當和槽連接的信號被發射的時候,就會調用這個槽。
信號與槽的連接:
1
2 3 4 5 6 7 8 |
static
QMetaObject::Connection connect( const QObject *sender, // 參數一:發送者 const QMetaMethod &signal, // 參數二:信號 const QObject *receiver, // 參數三:接收者 const QMetaMethod &method, // 參數四:槽 Qt::ConnectionType type = Qt::AutoConnection); // 參數五:連接類型 |
注意:信號槽要求信號和槽的參數一致,所謂一致,是參數類型一致。如果不一致,允許的情況是,槽函數的參數可以比信號的少,即便如此,槽函數存在的那些參數的順序也必須和信號的前面幾個一致起來。這是因為,你可以在槽函數中選擇忽略信號傳來的數據(也就是槽函數的參數比信號的少),但是不能說信號根本沒有這個數據,你就要在槽函數中使用(就是槽函數的參數比信號的多,這是不允許的)。
信號槽的書寫方式:
- Qt4的書寫方式
QPushButton* button = new QPushButton("Quit");
connect(button, SIGNAL(clicked()), &a, SLOT(quit()));
這種寫法沒有編譯錯誤,而是在運行時給出錯誤,無疑會增加程序的不穩定性。
只有在 Debug 模式下運行時才會提示槽函數不存在,Release 模式下運行時不會給予任何錯誤提示。
- Qt5的書寫方式
QPushButton button("Quit");
QObject::connect(&button, &QPushButton::clicked, &app,&QApplication::quit);
- C++11新方式: Lambda表達式,需要再Pro項目文件中加入 CONFIG += C++ 11
QPushButton* button = new QPushButton("Quit");
connect(button, SIGNAL(clicked()),[=](QString str){
qDebug() << str;});
關於Lambda表達式的知識:
簡單地說,Lambda表達式就是匿名函數。
以一對方括號[]開始,稱為Lambda表達式的引入符。
引入符后面可以添加Lambda表達式的返回值類型。
接着是參數列表,最后是Lambda表達式的函數體。
引入符描述函數體如何“獲得”外部變量!
所謂“外部變量”指的是函數體以外的變量,這些變量需要在引入符可見的作用域中有定義。
Lambda表達式構成圖解:
- Capture clause: 捕獲子句
- Parameter list: 參數列表 可選
- Mutable specification 可選
- Exception specification 可選
- Return type: 返回類型 可選
- Lambda Body
需要注意的一點:在進行信號槽綁定時,如果有重載,需要對成員函數進行類型轉換,可以使用 C++ 的 static_cast 類型轉換(編譯時進行語法檢查),也可以使用傳統的 C 語言的強制類型轉換(編譯時不進行語法檢查,運行時才檢查),或者 C++11 的 QOverload::of,C++14 的 qOverload:
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 |
QComboBox *comboBox =
new
QComboBox(); comboBox->addItem( "Michael" ); comboBox->addItem( "Kobe" ); comboBox->addItem( "James" ); comboBox->show(); // [1] QObject::connect(comboBox, static_cast < void (QComboBox::*)( int )>(&QComboBox::activated), []( int index) { qDebug() << index; }); // [2] QObject::connect(comboBox, static_cast < void (QComboBox::*)( const QString &)>(&QComboBox::activated), []( const QString &text) { qDebug() << text; }); // [3]: QOverload<> 里面是參數列表,of() 里面是成員函數地址 QObject::connect(comboBox, QOverload< const QString &>::of(&QComboBox::activated), []( const QString &text) { qDebug() << text; }); |